ฉันจะแสดงหน้าจอโหลดได้อย่างไรเมื่อฉันเปลี่ยนเส้นทางใน 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) {
this.router.events.subscribe((e : RouterEvent) => {
this.navigationInterceptor(e);
})
}
// 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
เพื่อแสดงสปินเนอร์ตามเงื่อนไขเราสามารถใช้ประโยชน์จาก Angular NgZone
และRenderer
เพื่อเปิด / ปิดสปินเนอร์ซึ่งจะข้ามการตรวจจับการเปลี่ยนแปลงของ Angular เมื่อเราเปลี่ยนสถานะของสปินเนอร์ ฉันพบว่าสิ่งนี้ทำให้ภาพเคลื่อนไหวราบรื่นขึ้นเมื่อเทียบกับการใช้*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(this._navigationInterceptor)
}
// 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>
router navigation
และit triggering the spinner
จากนั้นไม่สามารถที่จะหยุดมัน navigationInterceptor
ดูเหมือนจะเป็นทางออก แต่ฉันไม่แน่ใจว่ามีอย่างอื่นรอให้ทำลายอยู่หรือเปล่า ถ้าผสมกับasync requests
มันจะแนะนำปัญหาอีกครั้งฉันคิดว่า
display
อย่างใดอย่างหนึ่งnone
หรือinline
.
'md-spinner' is not a known element:
ผมได้รับข้อผิดพลาดนี้เมื่อผมคัดลอกโค้ดของคุณ ฉันค่อนข้างใหม่สำหรับ Angular คุณช่วยบอกฉันได้ไหมว่าอะไรคือความผิดพลาด
อัปเดต: 3ตอนนี้ฉันได้อัปเกรดเป็นเราเตอร์ใหม่แล้ววิธีการของ@borislemkeจะไม่ทำงานหากคุณใช้CanDeactivate
ยาม ฉันรู้สึกแย่กับวิธีการเดิม ๆ ของฉันie:
คำตอบนี้
UPDATE2 : เหตุการณ์ของเราเตอร์ในเราเตอร์ใหม่มีแนวโน้มดีและคำตอบของ@borislemkeดูเหมือนจะครอบคลุมประเด็นหลักของการใช้งานสปินเนอร์ฉันยังไม่ได้ทดสอบ แต่ฉันแนะนำ
UPDATE1:ฉันเขียนคำตอบนี้ในยุคที่Old-Router
เคยมีเพียงเหตุการณ์เดียวที่route-changed
แจ้งผ่านrouter.subscribe()
. ฉันรู้สึกว่าวิธีการด้านล่างมีมากเกินไปและพยายามทำโดยใช้เพียงอย่างเดียวrouter.subscribe()
และมันก็กลับมาทำงานเพราะไม่มีทางตรวจจับcanceled navigation
ได้ ดังนั้นฉันจึงต้องเปลี่ยนกลับไปใช้วิธีการที่ยาว (ทำงานสองครั้ง)
หากคุณรู้จักวิธีการใน Angular2 นี่คือสิ่งที่คุณต้องการ
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]);
ส่วนประกอบหลัก - (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 (จะสมัครสมาชิก Spinner-service เพื่อเปลี่ยนค่าที่ใช้งานอยู่)
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 บริการนี้)
กำหนดสิ่งที่สังเกตได้ที่จะสมัครใช้งานโดยส่วนประกอบของสปินเนอร์เพื่อเปลี่ยนสถานะของการเปลี่ยนแปลงและฟังก์ชันที่จะทราบและตั้งค่าตัวหมุนที่ใช้งาน / ไม่ใช้งาน
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;
}
}
ส่วนประกอบของเส้นทางอื่น ๆ ทั้งหมด
(ตัวอย่าง):
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();
}
}
spinner-service
แล้วในตอนนี้คุณเพียงแค่ต้องการให้ส่วนอื่น ๆ ทำงานได้ และจำไว้ว่าสำหรับangular2-rc-1
ทำไมไม่ใช้ css ง่ายๆ:
<router-outlet></router-outlet>
<div class="loading"></div>
และในสไตล์ของคุณ:
div.loading{
height: 100px;
background-color: red;
display: none;
}
router-outlet + div.loading{
display: block;
}
หรือแม้กระทั่งเราสามารถทำสิ่งนี้สำหรับคำตอบแรก:
<router-outlet></router-outlet>
<spinner-component></spinner-component>
แล้วก็แค่
spinner-component{
display:none;
}
router-outlet + spinner-component{
display: block;
}
เคล็ดลับในที่นี้คือเส้นทางและส่วนประกอบใหม่ ๆ จะปรากฏหลังเต้ารับเราเตอร์เสมอดังนั้นด้วยตัวเลือก css ที่เรียบง่ายเราสามารถแสดงและซ่อนการโหลดได้
หากคุณมีตรรกะพิเศษที่จำเป็นสำหรับเส้นทางแรกเท่านั้นคุณสามารถทำสิ่งต่อไปนี้:
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');
});
ฉันต้องการสิ่งนี้เพื่อซ่อนส่วนท้ายของหน้าซึ่งจะปรากฏที่ด้านบนของหน้า นอกจากนี้หากคุณต้องการตัวโหลดสำหรับหน้าแรกเท่านั้นคุณสามารถใช้สิ่งนี้ได้
คุณยังสามารถใช้โซลูชันที่มีอยู่นี้ การสาธิตอยู่ที่นี่ ดูเหมือนแถบโหลด youtube ฉันเพิ่งค้นพบและเพิ่มลงในโครงการของฉันเอง