是否有可能在 angular 等待订阅?

Is there a possibility to await a subscription in angular?

我想显示 post 的列表,如下所示: Post List

为了显示哪个 post 受到用户的青睐,我需要来自 mongodb 数据库中两个不同 collections 的数据。 目前我的 post-list.component.ts 文件的 ngOnInit 看起来像这样:

ngOnInit() {
    this.isLoading = true;
    this.postsService.getPosts(this.postsPerPage, this.currentPage);
    this.favoritesService.getFavorites(this.postsPerPage, this.currentPage);
    this.userId = this.authService.getUserId();
    this.postsSub = this.postsService
      .getPostUpdateListener()
      .subscribe((postData: { posts: Post[]; postCount: number }) => {
        this.totalPosts = postData.postCount;
        this.posts = postData.posts;
        console.log("Posts fetched successful!");
      });
    this.favoritesSub = this.favoritesService
      .getFavoriteUpdateListener()
      .subscribe(
        (favoriteData: { favorites: Favorite[]; postCount: number }) => {
          this.isLoading = false;
          this.favorites = favoriteData.favorites;
          this.fetchFavorites();
          console.log("Favorites fetched successful!");
        }
      );
    this.userIsAuthenticated = this.authService.getIsAuth();
    this.authStatusSub = this.authService
      .getAuthStatusListener()
      .subscribe((isAuthenticated) => {
        this.userIsAuthenticated = isAuthenticated;
        this.userId = this.authService.getUserId();
      });
  }
我的 post 列表只有在 post 数据先到达时才能正确显示。由于订阅的异步性,我无法控制哪些数据先到达。 我已经尝试过的是使用订阅的完整功能,但它从未被执行过。另一种方法是将收藏夹部分外包到一个自己的函数中,并在获取 post 之后执行它。两种方法都陷入了无尽的加载循环。

是否可以先等待post数据到达?

您有多种选择来实现您想要的行为。

选项 1:

您可以使用 RxJS 运算符 switchMap,它会在订阅发出后立即执行,并 returns 一个新的 Observable。有关 switchMap 的更多信息,请参阅 here ;)

我以你的电话 getPostUpdateListenergetFavoriteUpdateListener 为例,所以它看起来像这样:

...

this.postsSub = this.postsService
  .getPostUpdateListener()
  .pipe(
    switchMap((postData: { posts: Post[]; postCount: number }) => {
      this.totalPosts = postData.postCount;
      this.posts = postData.posts;
      console.log("Posts fetched successful!");

      return this.favoritesService.getFavoriteUpdateListener();
    })
  )
  .subscribe((favoriteData: { favorites: Favorite[]; postCount: number }) => {
    this.isLoading = false;
    this.favorites = favoriteData.favorites;
    this.fetchFavorites();
    console.log("Favorites fetched successful!");
  });

...

选项 2:

您可以使用 firstValueFromlastValueFrom 来承诺您的 Observable,然后您可以等待它的执行,例如async/await。有关详细信息,请参阅 here ;)

这看起来像下面这样:

async ngOnInit() {
  ...

  const postData: { posts: Post[]; postCount: number } = await firstValueFrom(this.postsService.getPostUpdateListener());
  
  this.totalPosts = postData.postCount;
  this.posts = postData.posts;
  console.log("Posts fetched successful!");

  const favoriteData: { favorites: Favorite[]; postCount: number } = await firstValueFrom(this.favoritesService.getFavoriteUpdateListener());
  this.isLoading = false;
  this.favorites = favoriteData.favorites;
  this.fetchFavorites();
  console.log("Favorites fetched successful!");

  ...
}

由于 Angular 以反应方式工作了很多,我会选择选项 1 ;)

对于干净的代码,您可以使用 rxjs forkJoin 运算符来达到确切的目的。基本上,您拥有的是两个在组件初始化时订阅的可观察对象。 forkJoin 致力于加入多个可观察流,或者为了更好地理解考虑 Promise.all 将如何等待所有可能的 Promises.

不使用 subscribe 和用户 .toPromise() 并等待的另一个选项。参考

在您的情况下,使用 combineLatest(RxJS v6) 或 combineLatestWith(RxJs v7.4) 会更好。 combineLatest 运算符将在任何源 Observables 至少发射一次后发射。 由于您的服务不相互依赖,因此在您的情况下最好使用 combineLatest。

    this.postsSub = this.postsService.getPosts(this.postsPerPage, this.currentPage);
    this.favoritesSub  = this.favoritesService.getFavorites(this.postsPerPage, this.currentPage);

combineLatest([this.postsSub, this.favoritesSub]).pipe(
            map(([postData, favoriteData]) => {
               this.totalPosts = postData.postCount;
               this.posts = postData.posts;
               console.log("Posts fetched successful!");
               this.isLoading = false;
               this.favorites = favoriteData.favorites;
               this.fetchFavorites();
               console.log("Favorites fetched successful!");
            })).subscribe();