当服务器响应新数据时,管道永远不会执行

Pipe never gets executed when server response with new data

我在我的模板中创建了一个订阅来监视对象的变化。对象的初始加载显示 属性 tags 的正确数据,当我将项目添加到数据时,它会转到 Web 服务器和 returns 所有 tags 附加到项目(以保持项目与服务器同步)。但是,新添加的项目不会反映在页面上。我不是 100% 确定为什么。我认为这是因为我的 of() 声明,但我不确定。我看到的是 zip().pipe() 永远不会被执行。

我需要使用 of 以外的东西吗?

注意:我正在尝试遵循声明模式来消除 .subscribe()

的使用

子注:一旦我开始工作,我计划尝试删除这一行的 subscribe this.server.file().subscribe

Stackblitz

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))
    );
  }
}

尝试完全转换 webserver.service.ts 以提供标签和 FileInfo 的可观察性,如下所示:

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);
  }
}

现在您的 AppComponent 可以是

@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);
  }
}

警告:如果您在未订阅 WebServer.tags$ 或下游时调用 WebServer.save,则不会发生任何事情。在您的情况下,没什么大不了的,因为模板中的 | async 已订阅。但是,如果您曾经将其拆分,以便在不同的组件中保存标签,则需要对该服务进行稍微修改,以确保仍然进行“保存新标签”服务器 API 调用。

你误用了 observable。在你订阅它之后,在带有异步管道的模板中,你不应该更新它的引用。

如果需要更新数据,必须使用Subject。

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 }));
  }
}

此外,您的服务可以更简单:

@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);
  }
}

这是工作代码:

https://stackblitz.com/edit/angular-ivy-my3wlu?file=src/app/app.component.ts

听起来你想要做的是拥有一个单一的可观察源,在添加新标签时发出对象的最新状态。然后,您可以使用异步管道简单地订阅模板中的这个单个可观察对象。

为了实现这一点,您可以创建一个专用流来表示文件标签的更新状态。

这是一个例子:

  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);
  }

这是一个 StackBlitz 演示。