angular - ¿Usar el pipe async para sondear Observables?
El escenario:
Tengo un servicio que consulta una URL cada 2 segundos:
export class FooDataService {
...
public provideFooData() {
const interval = Observable.interval(2000).startWith(0);
return interval
.switchMap(() => this.http.get(this.requestUrl))
.map(fooData => fooData.json())
}
}
Ahora tengo un componente donde quiero mostrar estos datos sondeados:
export class FooComponent implements OnInit {
constructor(private fooDataService: FooDataService) {}
public fooData$: Observable<FooData>;
ngOnInit() {
this.fooData$ = this.fooDataService.provideFooData();
}
}
En la plantilla del componente usaría un pipe asíncrono para recuperar y pasar el valor a un subcomponente:
<foo-data-viewer [data]="fooData$ | async"></foo-data-viewer>
El problema con este enfoque:
Implementarlo así no es verificable con Protractor (ver mi última pregunta sobre este tema o este artículo). Todo el código se ejecuta dentro de ngZone y Protractor esperará a que todas las acciones en cola se terminen antes de continuar. Pero Observable.interval() pondrá en cola un número ilimitado de acciones, lo que llevará a un tiempo de espera en Protractor.
La solución común:
La solución que leo con más frecuencia es usar runOutsideAngular así:
export class FooComponent implements OnInit, OnDestroy {
constructor(private ngZone: NgZone,
private fooDataService: FooDataService) {}
public fooData: FooData;
public fooDataSubscription: Subscription<FooData>;
ngOnInit() {
this.ngZone.runOutsideAngular(() => {
this.fooDataSubscription =
this.fooDataService.provideFooData()
.subscribe(
fooData => this.ngZone.run(() => this.fooData = fooData)
);
});
}
ngOnDestroy(): void {
this.fooDataSubscription.unsubscribe();
}
}
Al ejecutar el intervalo fuera del ngZone, protractor no espera a que la sondeo termine antes de continuar y, por lo tanto, no se agota el tiempo de espera.
Esto significa, sin embargo:
- No puedo usar el pipe
async, tengo que suscribirme manualmente. - Tengo que gestionar la suscripción manualmente y limpiarla yo mismo cuando el componente se destruye.
- No puedo mantener el estilo Observable bonito y limpio y el código se vuelve mucho más enrevesado, especialmente si tengo múltiples servicios de sondeo.
Mi pregunta:
¿Existe una manera de mantener el estilo funcional de rxjs y seguir trabajando con el pipe async (o un equivalente) mientras se ejecuta el intervalo fuera de ngZone?
Me topé con este proyecto de GitHub que parece exactamente lo que quiero, pero no pude hacerlo funcionar.
Lo que necesito es un ejemplo funcional para salir y volver a entrar en la zona como en mi escenario sin tener que gestionar la suscripción yo mismo.
Respuesta #1
import { Injectable } from '@angular/core';
import { interval, of, timer } from 'rxjs';
import { startWith, switchMap, delay, map, share, shareReplay } from 'rxjs/operators';
import { HttpClient } from '@angular/common/http';
@Injectable()
export class DataService {
private fakeRequest$ = timer(1000).pipe(map(() => new Date().getTime()))
private pollingData$ = interval(2000)
.pipe(
switchMap((index) => this.fakeRequest$),
shareReplay(1)
)
constructor() { }
public getData$() {
return this.pollingData$;
}
}
Stackblitz
