Home > Software design >  Is there a possibility to await a subscription in angular?
Is there a possibility to await a subscription in angular?

Time:12-16

I would like to show a list of posts like this: Post List

To show which post is favorized by a user I need data from two different collections out of my mongodb database. Currently the ngOnInit of my post-list.component.ts file looks like this:

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();
      });
  }
My post list is only shown correctly if the post data arrives first. Due the asynchrony of subscriptions I can't control which data arrives first. What I already tried is to use the completed function of subscribe but it was never executed. Another approach was to outsource the favorites part into a own function and execute it after the posts are fetched. Both approches ended up in a endless loading circle.

Is there any possibility to first await the post data to arrive?

CodePudding user response:

You have several options to achieve your wanted behavior.

Option 1:

You can use the RxJS operator switchMap which becomes executed as soon as the subscription emits and returns a new Observable. See here for more infos about switchMap ;)

I'm using your calls getPostUpdateListener and getFavoriteUpdateListener as example, so it would look something like this:

...

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

...

Option 2:

You can promisify your Observable with firstValueFrom or lastValueFrom and then you can wait for it's execution, e.g. with async/await. See here for more information ;)

This would look like following:

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!");

  ...
}

Since Angular is working a lot in the reactive way I'd go with option 1 ;)

CodePudding user response:

For clean code you can utilize rxjs forkJoin operator for the exact purpose. Basically what you have is two observables getting subscribed on component initialization. forkJoin works on joining multiple observable streams, or for the better understanding think about how Promise.all would await all possible Promises.

Another options to not use subscribe and user .toPromise() and await. for reference check out this post

CodePudding user response:

It will be better in your case to use combineLatest(RxJS v6) or combineLatestWith(RxJs v7.4). combineLatest operator will emit every time any of the source Observables emit after they've emitted at least once. As your services do not depend on each other then it will be better to use combineLatest in your case.

    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();
  • Related