Creé una suscripción en mi plantilla para observar cambios en un objeto. La carga inicial del objeto muestra los datos correctos para las tags de propiedad, cuando agrego un elemento a los datos, va a un servidor web y devuelve una lista de todas las tags que están adjuntas al elemento (para mantener el elemento sincronizado). con el servidor). Sin embargo, el elemento recién agregado no se refleja en la página. No estoy 100% seguro de por qué. Creo que se debe a mi instrucción of() pero no estoy seguro. Lo que veo es que zip().pipe() nunca se ejecuta.
¿Necesito usar algo que no sea of ?
Nota: estoy tratando de seguir el patrón declarativo para eliminar el uso de .subscribe()
Nota secundaria: una vez que lo haga funcionar, planeo intentar eliminar la subscribe en esta línea this.server.file().subscribe
export interface FileInfo { tags: string[]; } @Component({ selector: 'my-app', template: ` <input #tag /><button (click)="addTag(tag.value)">Add Tag</button> <div *ngIf="data$ | async as data"> <div *ngFor="let tag of data.tags">{{ tag }}</div> </div> `, }) export class AppComponent { data$ = new Observable<FileInfo>(); constructor( // Used to mimic server responses private readonly server: WebServer ) {} ngOnInit() { // I plan on removing this subscribe once I get a grasp on this this.server.file().subscribe((img) => { this.data$ = of(img); }); } addTag(newTag: string) { const data$ = this.server.save(newTag); this.data$.pipe(concatMap((i) => this.zip(data$))); } private zip(tags$: Observable<string[]>) { return zip(this.data$, tags$).pipe( tap((i) => console.log('zipping', i)), map(([img, tags]) => ({ ...img, tags } as FileInfo)) ); } }Estás haciendo mal uso del observable. Después de suscribirse con él, en la plantilla con la canalización asíncrona, no debe actualizar su referencia.
Si necesita actualizar los datos, debe utilizar un Asunto.
export class AppComponent { private readonly data = new BehaviorSubject<FileInfo>(null); data$ = this.data.asObservable(); constructor( // Used to mimic server responses private readonly server: WebServer ) {} ngOnInit() { this.server.file().subscribe((result) => this.data.next(result)); } addTag(newTag: string) { this.server .save(newTag) .subscribe((tags) => this.data.next({ ...this.data.value, tags })); } }Además, su servicio podría ser mucho más simple:
@Injectable({ providedIn: 'root' }) export class WebServer { private readonly tags = ['dog', 'cat']; file(): Observable<FileInfo> { return of({ tags: this.tags }); } save(tag: string) { this.tags.push(tag); return of(this.tags); } }Aquí está el código de trabajo:
https://stackblitz.com/edit/angular-ivy-my3wlu?file=src/app/app.component.ts
Parece que lo que quiere hacer es tener una única fuente observable que emita el último estado de su objeto a medida que se agregan nuevas etiquetas. Luego, simplemente puede suscribirse a este único observable en la plantilla utilizando la canalización asíncrona.
Para lograr esto, puede crear una secuencia dedicada que represente el estado actualizado de las etiquetas de su archivo.
Aquí hay un ejemplo:
private initialFileState$ = this.service.getFile(); private addTag$ = new Subject<string>(); private updatedfileTags$ = this.addTag$.pipe( concatMap(itemName => this.service.addTag(itemName)) ); public file$ = this.initialFileState$.pipe( switchMap(file => this.updatedfileTags$.pipe( startWith(file.tags), map(tags => ({ ...file, tags })) )) ); constructor(private service: FileService) { } addTag(tagName: string) { this.addTag$.next(itemName); }Aquí hay una demostración de StackBlitz .
Intente convertir completamente webserver.service.ts para proporcionar observables de etiquetas y FileInfo como este:
import { Injectable } from '@angular/core'; import { concat, Observable, of, Subject } from 'rxjs'; import { delay, map, shareReplay, tap } from 'rxjs/operators'; import { FileInfo } from './app.component'; // best practice is to move this to its own file, btw @Injectable({ providedIn: 'root' }) export class WebServer { private fakeServerTagArray = ['dog', 'cat']; private readonly initialTags$ = of(this.fakeServerTagArray); private readonly tagToSave$: Subject<string> = new Subject(); public readonly tags$: Observable<string[]> = concat( this.initialTags$, this.tagToSave$.pipe( tap(this.fakeServerTagArray.push), delay(100), map(() => this.fakeServerTagArray), shareReplay(1) // performant if more than one thing might listen, useless if only one thing listens ) ); public readonly file$: Observable<FileInfo> = this.tags$.pipe( map(tags => ({tags})), shareReplay(1) // performant if more than one thing might listen, useless if only one thing listens ); save(tag: string): void { this.tagToSave$.next(tag); } }y ahora su AppComponent puede ser simplemente
@Component({ selector: 'my-app', template: ` <input #tag /><button (click)="addTag(tag.value)">Add Tag</button> <div *ngIf="server.file$ | async as data"> <div *ngFor="let tag of data.tags">{{ tag }}</div> </div> `, }) export class AppComponent { constructor( private readonly server: WebServer; ) {} addTag(newTag: string) { this.server.save(newTag); } } Advertencia: si alguna vez llama a WebServer.save mientras WebServer.tags$ o downstream no están suscritos, no pasará nada. En tu caso, no es gran cosa, porque el | async en su plantilla se suscribe. Pero si alguna vez lo divide para guardar una etiqueta en un componente diferente, el servicio deberá modificarse ligeramente para garantizar que la llamada a la API del servidor "guardar nueva etiqueta" todavía se realice.