Tengo una llamada API que puede devolver algunos datos o puede devolver algo falso si no existen datos. Si hay datos, tap salir de la secuencia y hacer algunos efectos secundarios, pero si es falso, no quiero que ocurran efectos secundarios pero aún mostrar mi código de plantilla.
Estoy usando una canalización asíncrona para obtener esos datos en la plantilla, pero si los datos son falsos, no se mostrarán.
He visto la técnica para envolver el ngIf con un objeto para que se evalúe como verdadero, pero no parece la solución correcta para mi código.
Mi plantilla:
<form *ngIf="loadedData$ | async"> ... </form>Mi clase:
loadedData$: Observable<boolean>; ngOnInit(): void { this.loadedData$ = this.getCurrentBranding().pipe( tap(branding => { if (branding) { // Do side effects } }), // current hack to make the template show map(() => true) ); } private getCurrentBranding(): Observable<Branding> { // API call, may return an object, or null }Siempre puedes tratar de mantenerlo lo más semántico posible. Su enfoque usando el map no es una mala idea, es solo que en realidad pierde el acceso a sus datos. Probablemente sea mejor dividirlo en dos Observables :
readonly data$: Observable<BrandingData> = this.getCurrentBranding().pipe( tap((branding) => { /* Do side effects */ }), shareReplay({ refCount: true, bufferSize: 1 }) ); readonly loadingData$: Observable<boolean> = this.data$.pipe( mapTo(false), startWith(true) );Luego puede mostrar su formulario verificando si los datos se están cargando:
<form *ngIf="(loadingData$ | async) === false"></form> Lo que sucede aquí es que al suscribirse a loadingData$ en la plantilla, en realidad también se suscribe a data$ observable, lo que activará la solicitud de API. Al usar shareReplay , se asegura de que esta solicitud solo se realice una vez en cada suscripción.
lo "típico" es usar un objeto como "si" en la forma
<!--see that the if is always "true" -is an object- --> <form *ngIf="{response:loadedData$ | async} as res"> <!--you has the response in res.response--> <ng-container *ngIf="res.response===false"> the result of the call is false </ng-container> <ng-container *ngIf="res.response!===false"> {{res.response|json}} </ng-container> </form>Me he acostumbrado a tener un solo observable para mi componente que fusiona múltiples flujos, combinándolos en un solo estado. Por lo general, esto involucra al operador de escaneo , pero en este caso no es necesario (aunque tal vez también desee fusionar estos efectos secundarios).
En el siguiente código, getCurrentBranding se ejecutará cuando esté suscrito por la canalización asíncrona en el componente. Para evitar ejecuciones múltiples, puede tener un elemento raíz que use Angular como expresión para convertir la emisión en una variable de plantilla. Alternativamente, podría usar shareReplay .
El operador startWith se utiliza para proporcionar un valor inicial, por lo que siempre se mostrará el elemento raíz. Inicialmente, el formulario estará oculto hasta que la API devuelva el resultado. Los datos provendrán del mismo observable y accederán desde la variable de plantilla creada en el elemento raíz.
this.state$ = this.getCurrentBranding().pipe( tap(branding => { /* Do side effects */ ), map(data => ({ data, isLoaded: true}), startWith(({ data: [], isLoaded: false}) ); <ng-container *ngIf="(state$ | async) as state"> <form *ngIf="state.isLoaded"> <!-- ... --> </form> <ol *ngFor="let o of state.data"> <!-- ... --> </ol> </ng-container>