Angular 2에서 경로 사이를 탐색 할 때 로딩 화면 표시


답변:


196

현재 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조건부 스피너를 보여주기 위해, 우리는 각도의를 활용할 수 NgZoneRenderer각도의 변화를 감지 우회 우리는 회 전자의 상태를 변경합니다 스피너 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(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
좋습니다. 접근 해 주셔서 감사합니다. 그것은 앞에서 나를 공격하고 내가 되돌릴했던 것보다 내가 통제를 잃었다 becuase 나는이 매력적인 아이디어를 내 긴 방법을 대체 router navigation하고 it triggering the spinner다음 그것을 멈출 수없는. navigationInterceptor해결책처럼 보이지만 다른 것이 깨지기를 기다리고 있는지 확실하지 않습니다. async requests그것 과 섞이면 다시 문제가 생길 것 같아요.
Ankit Singh

1
어쩌면 이것이 어느 시점에서 효과가 있었습니까? Angular 2.4.8에서는 현재 작동하지 않습니다. 페이지는 동기식입니다. Spinner는 전체 페이지 / 상위 구성 요소가 렌더링 될 때까지 렌더링되지 않고 NavigationEnd에서 렌더링됩니다. 그 패배 회 전자의 점
techguy2000

1
불투명도를 사용하는 것은 좋은 선택이 아닙니다. 시각적으로는 괜찮지 만 Chrome에서는 로딩 이미지 / 아이콘이 버튼이나 텍스트 위에 있으면 버튼에 액세스 할 수 없습니다. 또는 을 사용 display하도록 변경했습니다 . noneinline
im1dermike

2
나는이 접근법을 좋아한다, 잘 했어! 하지만 문제가 있고 NavigationEnd에서 애니메이션을 전환하지 않으면 스피너 로딩을 볼 수 있지만 false로 전환하면 경로가 너무 빨리 변경되어 애니메이션도 볼 수없는 이유를 이해할 수 없습니다. 연결 속도를 늦추기 위해 네트워크 스로 텔링도 있지만 여전히 동일합니다 :( 전혀로드되지 않습니다. 이에 대한 제안을 해주시겠습니까? 로딩 요소에서 클래스를 추가하고 제거하여 애니메이션을 제어합니다. 감사합니다
d123546

1
코드를 복사 할 때이 오류가 발생했습니다 'md-spinner' is not a known element:. 저는 Angular를 처음 사용합니다. 실수가 뭔지 말씀해 주 시겠어요?
Manu Chadha

40

업데이트 : 3 이제 새 라우터로 업그레이드 했으므로CanDeactivate 가드 를 사용하면 @borislemke 의 접근 방식이 작동하지 않습니다 . 예전 방식으로 타락 해이 ie:대답

UPDATE2는 : 새로운 라우터 모습 약속하고있는 라우터 이벤트 대답 하여 @borislemke이 스피너 구현의 주요 측면을 커버하는 것, 나는 그것을 테스트 havent't하지만 난 그것을 권장합니다.

UPDATE1 : 나는 시대에이 대답을 썼다Old-Routerroute-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 { }

스피너 구성 요소 (스피너 서비스에 가입하여 그에 따라 활성 값을 변경)

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 (이 서비스를 부트 스트랩)

변경시 상태를 변경하기 위해 spinner-component에 의해 구독 될 Observable을 정의하고, spinner를 활성 / 비활성 상태로 알고 설정하는 기능을 정의합니다.

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
실제로,이 굉장와 작품, 테스트 목적으로 내 팁 당신은 ngOnInit에서의 setTimeout (() => this.spinner.stop (), 5000)와 회 전자의 정지를 지연시킬 수 있습니다
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;
}

여기서 트릭은 새로운 경로와 구성 요소가 항상 router-outlet 이후에 나타나 므로 간단한 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 로딩 바처럼 보입니다. 방금 찾아서 내 프로젝트에 추가했습니다.


리졸버에게 도움이됩니까? 내 문제는 리졸버가 데이터를 가져 오는 동안 실제 대상 구성 요소 ngOninit가 아직 호출되지 않았기 때문에 어디에서나 스피너를 표시 할 수 없다는 것입니다 !! 내 아이디어는 ngOnInit에 스피너를 표시하고 경로 구독에서 해결 된 데이터가 반환되면 숨기는 것이 었습니다.
kuldeep
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.