Добавляем спиннер при загрузке новых роутов в Angular
В этом посте вы узнаете, как создать спиннер, который будет отображаться и скрываться в зависимости от состояния загрузки вашего приложения. Для этого мы будем использовать Angular Router и подключаться к некоторым из предоставленных событий.
Сприннер обычно лучше всего отображается в корневом компоненте, которым обычно является наш AppComponent
.
Во-первых, давайте начнем с пустого компонента, импортируем и внедрим наш Router
. Это позволит нам подписаться на события маршрутизатора:
import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
@Component({
selector: 'app-root',
template: `
<div class="app">
<h1>My App</h1>
<router-outlet></router-outlet>
</div>
`,
})
export class AppComponent implements OnInit {
constructor(private router: Router) {}
ngOnInit() {}
}
Внутри нашего хука жизненного цикла OnInit мы настроим подписку на свойство events
в Router:
@Component({...})
export class AppComponent implements OnInit {
constructor(private router: Router) {}
ngOnInit() {
this.router.events.subscribe(console.log);
}
}
Затем в нашей инструментальной консоли разработчика мы увидим что-то вроде (с различными метаданными внутри каждого объекта класса):
▶ NavigationStart {...}
▶ RouteConfigLoadStart {...}
▶ RouteConfigLoadEnd {...}
▶ RoutesRecognized {...}
▶ GuardsCheckStart {...}
▶ ActivationStart {...}
▶ GuardsCheckEnd {...}
▶ ResolveStart {...}
▶ ResolveEnd {...}
▶ ActivationEnd {...}
▶ NavigationEnd {...}
Это много событий! И это хорошо.
Чтобы найти события, которые имеют отношение к нашей цели создания счетчика загрузки, мы будем следовать простому процессу фильтрации событий, которые нам действительно нужны, а затем соответствующим образом переключать состояние loading
.
Во-первых, давайте определим новое свойство loading$
, которое содержит суффикс $
для обозначения наблюдаемого типа. Затем мы создадим новый Observable, используя of()
:
import { Observable, of } from 'rxjs';
@Component({...})
export class AppComponent implements OnInit {
loading$: Observable<boolean> = of(false);
constructor(private router: Router) {}
ngOnInit() {
this.router.events.subscribe(console.log);
}
}
Затем мы можем подписаться в шаблоне на наш Observable loading$
с помощью Async Pipe:
<div *ngIf="loading$ | async">Loading...</div>
Наш следующий шаг заключается в поиске событий которые нас интересуют с помощью pipe()
и filter()
:
import { Observable, of } from 'rxjs';
import { filter } from 'rxjs/operators';
@Component({...})
export class AppComponent implements OnInit {
loading$: Observable<boolean> = of(false);
constructor(private router: Router) {}
ngOnInit() {
this.router.events.pipe(
filter(
(e) =>
e instanceof NavigationStart ||
e instanceof NavigationEnd ||
e instanceof NavigationCancel ||
e instanceof NavigationError
)
)
// ONLY runs on:
// NavigationStart, NavigationEnd, NavigationCancel, NavigationError
.subscribe(console.log);
}
}
Поскольку Observables ленивы по дизайну, любая дополнительная логика, которую мы пишем, будет выполняться только при обнаружении этих событий навигации, а не для любого типа события маршрутизатора.
Итак, теперь последний шаг - просто установить loading$
как true
, когда мы находимся внутри NavigationStart
, и как только сработает NavigationEnd
, NavigationCancel
или NavigationError
мы снова установим его в false
.
Для этого мы можем просто вернуть новое значение в map()
и проверить, является ли событие NavigationStart
тем, которое мы получили.
import { Observable, of } from 'rxjs';
import { filter, map } from 'rxjs/operators';
@Component({...})
export class AppComponent implements OnInit {
loading$: Observable<boolean> = of(false);
constructor(private router: Router) {}
ngOnInit() {
this.router.events.pipe(
filter(
(e) =>
e instanceof NavigationStart ||
e instanceof NavigationEnd ||
e instanceof NavigationCancel ||
e instanceof NavigationError
),
map((e) => e instanceof NavigationStart)
)
// true or false
.subscribe(console.log);
}
}
По мере использования map()
мы будем возвращать новое значение нашему .subscribe()
при каждом срабатывании. Чтобы закончить, нам нужно указать нашему Observable на this.loading$
и удалить ручную подписку:
import { Observable, of } from 'rxjs';
import { filter, map } from 'rxjs/operators';
@Component({...})
export class AppComponent implements OnInit {
loading$: Observable<boolean> = of(false);
constructor(private router: Router) {}
ngOnInit() {
this.loading$ = this.router.events.pipe(
filter(
(e) =>
e instanceof NavigationStart ||
e instanceof NavigationEnd ||
e instanceof NavigationCancel ||
e instanceof NavigationError
),
map((e) => e instanceof NavigationStart)
);
}
}
Красиво и чисто! Эти события будут срабатывать при переходе между компонентами вашего приложения. Для достижения наилучших результатов используйте Route Guard для загрузки данных для определенной страницы, так как это заставит событие NavigationEnd
срабатывать только после разрешения данных.