Angular 2에서 경로를 탐색 할 때 로딩 화면 표시
Angular 2에서 경로를 변경할 때 로딩 화면을 표시하려면 어떻게합니까?
현재 Angular Router는 탐색 이벤트를 제공합니다. 이를 구독하고 이에 따라 UI를 변경할 수 있습니다. 라우터 전환이 실패 할 경우 스피너 NavigationCancel
와 같은 다른 이벤트를 계산 NavigationError
하고 중지해야합니다.
app.component.ts- 루트 컴포넌트
...
import {
Router,
// import as RouterEvent to avoid confusion with the DOM Event
Event as RouterEvent,
NavigationStart,
NavigationEnd,
NavigationCancel,
NavigationError
} from '@angular/router'
@Component({})
export class AppComponent {
// Sets initial value to true to show loading spinner on first load
loading = true
constructor(private router: Router) {
router.events.subscribe((event: RouterEvent) => {
this.navigationInterceptor(event)
})
}
// Shows and hides the loading spinner during RouterEvent changes
navigationInterceptor(event: RouterEvent): void {
if (event instanceof NavigationStart) {
this.loading = true
}
if (event instanceof NavigationEnd) {
this.loading = false
}
// Set loading state to false in both of the below events to hide the spinner in case a request fails
if (event instanceof NavigationCancel) {
this.loading = false
}
if (event instanceof NavigationError) {
this.loading = false
}
}
}
app.component.html- 루트 뷰
<div class="loading-overlay" *ngIf="loading">
<!-- show something fancy here, here with Angular 2 Material's loading bar or circle -->
<md-progress-bar mode="indeterminate"></md-progress-bar>
</div>
성능 향상 답변 : 성능에 관심이 있다면 더 나은 방법이 있지만 구현하는 것이 약간 지루하지만 성능 향상은 추가 작업의 가치가 있습니다. 대신에 사용하는 *ngIf
조건부 스피너를 보여주기 위해, 우리는 각도의를 활용할 수 NgZone
및 Renderer
각도의 변화를 감지 우회 우리는 회 전자의 상태를 변경합니다 스피너 ON / OFF를 전환 할 수 있습니다. 이것을 사용하여 *ngIf
또는 async
파이프에 비해 애니메이션을 매끄럽게 만드는 것으로 나타났습니다 .
이것은 약간의 조정으로 이전 답변과 유사합니다.
app.component.ts- 루트 컴포넌트
...
import {
Router,
// import as RouterEvent to avoid confusion with the DOM Event
Event as RouterEvent,
NavigationStart,
NavigationEnd,
NavigationCancel,
NavigationError
} from '@angular/router'
import {NgZone, Renderer, ElementRef, ViewChild} from '@angular/core'
@Component({})
export class AppComponent {
// Instead of holding a boolean value for whether the spinner
// should show or not, we store a reference to the spinner element,
// see template snippet below this script
@ViewChild('spinnerElement')
spinnerElement: ElementRef
constructor(private router: Router,
private ngZone: NgZone,
private renderer: Renderer) {
router.events.subscribe((event: RouterEvent) => {
this._navigationInterceptor(event)
})
}
// Shows and hides the loading spinner during RouterEvent changes
private _navigationInterceptor(event: RouterEvent): void {
if (event instanceof NavigationStart) {
// We wanna run this function outside of Angular's zone to
// bypass change detection
this.ngZone.runOutsideAngular(() => {
// For simplicity we are going to turn opacity on / off
// you could add/remove a class for more advanced styling
// and enter/leave animation of the spinner
this.renderer.setElementStyle(
this.spinnerElement.nativeElement,
'opacity',
'1'
)
})
}
if (event instanceof NavigationEnd) {
this._hideSpinner()
}
// Set loading state to false in both of the below events to
// hide the spinner in case a request fails
if (event instanceof NavigationCancel) {
this._hideSpinner()
}
if (event instanceof NavigationError) {
this._hideSpinner()
}
}
private _hideSpinner(): void {
// We wanna run this function outside of Angular's zone to
// bypass change detection,
this.ngZone.runOutsideAngular(() => {
// For simplicity we are going to turn opacity on / off
// you could add/remove a class for more advanced styling
// and enter/leave animation of the spinner
this.renderer.setElementStyle(
this.spinnerElement.nativeElement,
'opacity',
'0'
)
})
}
}
app.component.html- 루트 뷰
<div class="loading-overlay" #spinnerElement style="opacity: 0;">
<!-- md-spinner is short for <md-progress-circle mode="indeterminate"></md-progress-circle> -->
<md-spinner></md-spinner>
</div>
UPDATE:3 Now that I have upgraded to new Router, @borislemke's approach will not work if you use CanDeactivate
guard. I'm degrading to my old method, ie:
this answer
UPDATE2: Router events in new-router look promising and the answer by @borislemke seems to cover the main aspect of spinner implementation, I havent't tested it but I recommend it.
UPDATE1: I wrote this answer in the era of Old-Router
, when there used to be only one event route-changed
notified via router.subscribe()
. I also felt overload of the below approach and tried to do it using only router.subscribe()
, and it backfired because there was no way to detect canceled navigation
. So I had to revert back to lengthy approach(double work).
If you know your way around in Angular2, this is what you'll need
Boot.ts
import {bootstrap} from '@angular/platform-browser-dynamic';
import {MyApp} from 'path/to/MyApp-Component';
import { SpinnerService} from 'path/to/spinner-service';
bootstrap(MyApp, [SpinnerService]);
Root Component- (MyApp)
import { Component } from '@angular/core';
import { SpinnerComponent} from 'path/to/spinner-component';
@Component({
selector: 'my-app',
directives: [SpinnerComponent],
template: `
<spinner-component></spinner-component>
<router-outlet></router-outlet>
`
})
export class MyApp { }
Spinner-Component (will subscribe to Spinner-service to change the value of active accordingly)
import {Component} from '@angular/core';
import { SpinnerService} from 'path/to/spinner-service';
@Component({
selector: 'spinner-component',
'template': '<div *ngIf="active" class="spinner loading"></div>'
})
export class SpinnerComponent {
public active: boolean;
public constructor(spinner: SpinnerService) {
spinner.status.subscribe((status: boolean) => {
this.active = status;
});
}
}
Spinner-Service (bootstrap this service)
Define an observable to be subscribed by spinner-component to change the status on change, and function to know and set the spinner active/inactive.
import {Injectable} from '@angular/core';
import {Subject} from 'rxjs/Subject';
import 'rxjs/add/operator/share';
@Injectable()
export class SpinnerService {
public status: Subject<boolean> = new Subject();
private _active: boolean = false;
public get active(): boolean {
return this._active;
}
public set active(v: boolean) {
this._active = v;
this.status.next(v);
}
public start(): void {
this.active = true;
}
public stop(): void {
this.active = false;
}
}
All Other Routes' Components
(sample):
import { Component} from '@angular/core';
import { SpinnerService} from 'path/to/spinner-service';
@Component({
template: `<div *ngIf="!spinner.active" id="container">Nothing is Loading Now</div>`
})
export class SampleComponent {
constructor(public spinner: SpinnerService){}
ngOnInit(){
this.spinner.stop(); // or do it on some other event eg: when xmlhttp request completes loading data for the component
}
ngOnDestroy(){
this.spinner.start();
}
}
Why not just using simple css :
<router-outlet></router-outlet>
<div class="loading"></div>
And in your styles :
div.loading{
height: 100px;
background-color: red;
display: none;
}
router-outlet + div.loading{
display: block;
}
Or even we can do this for the first answer:
<router-outlet></router-outlet>
<spinner-component></spinner-component>
And then simply just
spinner-component{
display:none;
}
router-outlet + spinner-component{
display: block;
}
The trick here is, the new routes and components will always appear after router-outlet , so with a simple css selector we can show and hide the loading.
If you have special logic required for the first route only you can do the following:
AppComponent
loaded = false;
constructor(private router: Router....) {
router.events.pipe(filter(e => e instanceof NavigationEnd), take(1))
.subscribe((e) => {
this.loaded = true;
alert('loaded - this fires only once');
});
I had a need for this to hide my page footer, which was otherwise appearing at the top of the page. Also if you only want a loader for the first page you can use this.
You could also use this existing solution. The demo is here. It looks like youtube loading bar. I just found it and added it to my own project.
'Programming' 카테고리의 다른 글
루비는 문자열을 캡처 된 정규식 패턴으로 대체 (0) | 2020.07.28 |
---|---|
Mockito 매처는 어떻게 작동합니까? (0) | 2020.07.28 |
C ++에서 int와 long의 차이점은 무엇입니까? (0) | 2020.07.28 |
정규식 옵션 문자를 일치시키는 방법 (0) | 2020.07.28 |
Firefox 및 Opera의 웹 폰트 스무딩 및 앤티 앨리어싱 (0) | 2020.07.28 |