¿Cómo muestro una pantalla de carga cuando cambio una ruta en Angular 2?
¿Cómo muestro una pantalla de carga cuando cambio una ruta en Angular 2?
Respuestas:
El enrutador angular actual proporciona eventos de navegación. Puede suscribirse a estos y hacer cambios en la interfaz de usuario en consecuencia. Recuerde contar en otros eventos como NavigationCancel
y NavigationError
para detener su ruleta en caso de que falle la transición del enrutador.
app.component.ts : su componente raíz
...
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 : su vista raíz
<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>
Respuesta de rendimiento mejorado : si le importa el rendimiento, existe un método mejor, es un poco más tedioso implementarlo, pero la mejora del rendimiento valdrá la pena el trabajo extra. En lugar de usar *ngIf
para mostrar condicionalmente la ruleta, podríamos aprovechar Angular's NgZone
y Renderer
encender / apagar la ruleta que evitará la detección de cambios de Angular cuando cambiemos el estado de la ruleta. Encontré esto para hacer que la animación sea más suave en comparación con el uso *ngIf
o una async
tubería.
Esto es similar a mi respuesta anterior con algunos ajustes:
app.component.ts : su componente raíz
...
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 : su vista raíz
<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
y it triggering the spinner
y luego no ser capaz de detenerlo. navigationInterceptor
Parece una solución, pero no estoy seguro de si hay algo más esperando para romperse. Si se mezcla con el async requests
, volverá a presentar el problema, creo.
display
cualquiera none
o inline
.
'md-spinner' is not a known element:
. Soy bastante nuevo en Angular. ¿Podría decirme cuál podría ser el error?
ACTUALIZACIÓN: 3 Ahora que he actualizado al nuevo enrutador, el enfoque de @borislemke no funcionará si usa CanDeactivate
guardia. Estoy degradando a mi antiguo método, ie:
esta respuesta
ACTUALIZACIÓN2 : Los eventos del enrutador en el nuevo enrutador parecen prometedores y la respuesta de @borislemke parece cubrir el aspecto principal de la implementación del spinner, no lo he probado pero lo recomiendo.
ACTUALIZACIÓN1: Escribí esta respuesta en la era de Old-Router
, cuando solía haber solo un evento route-changed
notificado a través de router.subscribe()
. También sentí una sobrecarga del siguiente enfoque e intenté hacerlo solo router.subscribe()
, y resultó contraproducente porque no había forma de detectarlo canceled navigation
. Así que tuve que volver a un enfoque largo (doble trabajo).
Si conoce bien Angular2, esto es lo que necesitará.
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]);
Componente raíz: (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 (se suscribirá a Spinner-service para cambiar el valor de active en consecuencia)
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 (arranque este servicio)
Defina un observable para ser suscrito por el componente spinner para cambiar el estado del cambio, y funcione para conocer y configurar el spinner activo / inactivo.
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;
}
}
Todos los componentes de otras rutas
(muestra):
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
ahora, solo necesita otras partes para que funcione. Y recuerda que es paraangular2-rc-1
¿Por qué no simplemente usando CSS simple:
<router-outlet></router-outlet>
<div class="loading"></div>
Y en tus estilos:
div.loading{
height: 100px;
background-color: red;
display: none;
}
router-outlet + div.loading{
display: block;
}
O incluso podemos hacer esto para la primera respuesta:
<router-outlet></router-outlet>
<spinner-component></spinner-component>
Y luego simplemente
spinner-component{
display:none;
}
router-outlet + spinner-component{
display: block;
}
El truco aquí es que las nuevas rutas y componentes siempre aparecerán después de la salida del enrutador , por lo que con un simple selector css podemos mostrar y ocultar la carga.
Si solo necesita una lógica especial para la primera ruta, puede hacer lo siguiente:
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');
});
Tenía la necesidad de que esto ocultara el pie de página, que aparecía en la parte superior de la página. Además, si solo desea un cargador para la primera página, puede usarlo.
También podría usar esta solución existente . La demo está aquí . Parece la barra de carga de youtube. Lo encontré y lo agregué a mi propio proyecto.