แสดงหน้าจอโหลดเมื่อนำทางระหว่างเส้นทางใน Angular 2


125

ฉันจะแสดงหน้าจอโหลดได้อย่างไรเมื่อฉันเปลี่ยนเส้นทางใน Angular 2



4
คำถามที่เชื่อมโยงข้างต้น (ความคิดเห็นของ AndrewL64) เป็นเรื่องเกี่ยวกับ AngularJS ไม่ใช่ "Angular" (2+)
Gary McGill

คำตอบ:


197

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>

1
เยี่ยมมากขอขอบคุณแนวทางของคุณ มันได้ตีฉันก่อนและฉันแทนที่วิธีการที่มีความยาวของฉันกับความคิดที่น่าสนใจนี้กว่าผมจะกลับเพราะผมสูญเสียการควบคุมrouter navigationและit triggering the spinnerจากนั้นไม่สามารถที่จะหยุดมัน navigationInterceptorดูเหมือนจะเป็นทางออก แต่ฉันไม่แน่ใจว่ามีอย่างอื่นรอให้ทำลายอยู่หรือเปล่า ถ้าผสมกับasync requestsมันจะแนะนำปัญหาอีกครั้งฉันคิดว่า
Ankit Singh

1
อาจจะได้ผลในบางจุด? ตอนนี้ใช้ไม่ได้กับ Angular 2.4.8 หน้าเป็นแบบซิงโครนัส Spinner ไม่ได้รับการแสดงผลจนกว่าทั้งหน้า / องค์ประกอบหลักจะแสดงผลและที่ NavigationEnd ที่เอาชนะจุดหมุน
techguy2000

1
การใช้ความทึบไม่ใช่ทางเลือกที่ดี มองเห็นได้ดี แต่ใน Chrome หากรูปภาพ / ไอคอนกำลังโหลดอยู่ด้านบนของปุ่มหรือข้อความคุณจะไม่สามารถเข้าถึงปุ่มได้ ฉันเปลี่ยนให้ใช้displayอย่างใดอย่างหนึ่งnoneหรือinline.
im1dermike

2
ฉันชอบแนวทางนี้คนเก่ง! แต่ฉันมีปัญหาและไม่เข้าใจว่าทำไมถ้าฉันไม่เปลี่ยนแอนิเมชั่นบน NavigationEnd ฉันสามารถเห็นการโหลดสปินเนอร์ แต่ถ้าฉันเปลี่ยนเป็นเท็จเส้นทางจะเปลี่ยนเร็วมากจนฉันไม่เห็นภาพเคลื่อนไหวใด ๆ เลย :( ฉันได้ลองแล้ว แม้เครือข่ายจะทำให้การเชื่อมต่อช้าลง แต่ก็ยังคงเหมือนเดิม :( ไม่มีการโหลดเลยช่วยให้คำแนะนำเกี่ยวกับเรื่องนี้ได้ไหมฉันควบคุมภาพเคลื่อนไหวโดยการเพิ่มและลบคลาสที่องค์ประกอบการโหลดขอบคุณ
d123546

1
'md-spinner' is not a known element:ผมได้รับข้อผิดพลาดนี้เมื่อผมคัดลอกโค้ดของคุณ ฉันค่อนข้างใหม่สำหรับ Angular คุณช่วยบอกฉันได้ไหมว่าอะไรคือความผิดพลาด
Manu Chadha

40

อัปเดต: 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();
  }
}

ดูสิ่งนี้ด้วย ฉันไม่สนับสนุนแอนิเมชั่นเชิงมุมเท่าไหร่โดยค่าเริ่มต้น
Ankit Singh

คุณช่วยเขียนบริการสำหรับสปินเนอร์ได้ไหม ฉันสามารถทำแอนิเมชั่นและทำอะไรไม่ได้ใน CSS ด้วยตัวเอง แต่มันจะดีมากถ้าคุณสามารถช่วยฉันด้วยบริการนี้ได้
Lucas

เห็นการดำเนินการนี้เช่นกัน แต่ไม่ตรง
Ankit Singh

1
ฉันได้เพิ่มโค้ดไปspinner-serviceแล้วในตอนนี้คุณเพียงแค่ต้องการให้ส่วนอื่น ๆ ทำงานได้ และจำไว้ว่าสำหรับangular2-rc-1
Ankit Singh

1
จริงๆแล้วนี่ยอดเยี่ยมและใช้งานได้คำแนะนำของฉันสำหรับวัตถุประสงค์ในการทดสอบคุณสามารถชะลอการหยุดสปินเนอร์ด้วย setTimeout (() => this.spinner.stop (), 5000) ใน ngOnInit
Jhonatas Kleinkauff

10

ทำไมไม่ใช้ 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 ที่เรียบง่ายเราสามารถแสดงและซ่อนการโหลดได้


<router-outlet> ไม่อนุญาตให้เราส่งผ่านค่าจากคอมโพเนนต์ไปยังคอมโพเนนต์พาเรนต์ดังนั้นการซ่อน div การโหลดจะค่อนข้างซับซ้อน
Praveen Rana

1
นอกจากนี้อาจเป็นเรื่องที่น่ารำคาญอย่างยิ่งหากแอปพลิเคชันของคุณมีการเปลี่ยนแปลงเส้นทางจำนวนมากเพื่อดูตัวหมุนทุกครั้งในทันที ควรใช้ RxJ และตั้งเวลาเดบิตเพื่อให้ปรากฏหลังจากดีเลย์สั้น ๆ เท่านั้น
Simon_Weaver

2

หากคุณมีตรรกะพิเศษที่จำเป็นสำหรับเส้นทางแรกเท่านั้นคุณสามารถทำสิ่งต่อไปนี้:

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');
                   });

ฉันต้องการสิ่งนี้เพื่อซ่อนส่วนท้ายของหน้าซึ่งจะปรากฏที่ด้านบนของหน้า นอกจากนี้หากคุณต้องการตัวโหลดสำหรับหน้าแรกเท่านั้นคุณสามารถใช้สิ่งนี้ได้


0

คุณยังสามารถใช้โซลูชันที่มีอยู่นี้ การสาธิตอยู่ที่นี่ ดูเหมือนแถบโหลด youtube ฉันเพิ่งค้นพบและเพิ่มลงในโครงการของฉันเอง


มันช่วยเรื่อง resolvers หรือไม่? ปัญหาของฉันคือในขณะที่ตัวแก้ไขนำข้อมูลกลับมาฉันไม่สามารถแสดงสปินเนอร์ได้ทุกที่เพราะยังไม่ได้เรียกส่วนประกอบเป้าหมายที่แท้จริงของ ngOninit !! ความคิดของฉันคือการแสดงสปินเนอร์ใน ngOnInit และซ่อนไว้เมื่อข้อมูลที่แก้ไขแล้วถูกส่งคืนจากการสมัครเส้นทาง
kuldeep
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.