Could I potentially pause and wait for a subscription in Angular?

I'm looking to display a list of posts similar to this: Post List

In order to indicate which post is favorited by a user, I need to retrieve data from two different collections in my MongoDB database. The ngOnInit function in my post-list.component.ts file currently appears as follows:

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 successfully!");
      });
    this.favoritesSub = this.favoritesService
      .getFavoriteUpdateListener()
      .subscribe(
        (favoriteData: { favorites: Favorite[]; postCount: number }) => {
          this.isLoading = false;
          this.favorites = favoriteData.favorites;
          this.fetchFavorites();
          console.log("Favorites fetched successfully!");
        }
      );
    this.userIsAuthenticated = this.authService.getIsAuth();
    this.authStatusSub = this.authService
      .getAuthStatusListener()
      .subscribe((isAuthenticated) => {
        this.userIsAuthenticated = isAuthenticated;
        this.userId = this.authService.getUserId();
      });
  }
My post list only displays correctly if the post data arrives first. Due to the asynchronous nature of subscriptions, I cannot control which data arrives first. I have attempted to use the completed function of subscribe, but it was never executed. Another approach was to separate the favorites part into its own function and run it after the posts are fetched. However, both approaches resulted in an endless loading loop.

Is there a way to await the arrival of the post data first?

Answer №1

If you're looking to achieve your desired outcome, there are a few options available for you.

Approach 1:

One way is to utilize the RxJS operator known as switchMap. This operator gets executed immediately after the subscription emits and returns a new Observable. For more information on how to use switchMap, check out this link ;)

Using the example of getPostUpdateListener and getFavoriteUpdateListener, your code would resemble 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 successfully!");

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

...

Approach 2:

An alternative option is to promisify your Observable using firstValueFrom or lastValueFrom, and then await its execution with async/await. You can find more details about this method here ;)

The implementation would look something like this:

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

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

  ...
}

Given that Angular operates largely in a reactive manner, I would recommend going with approach 1 ;)

Answer №2

To ensure your code is clean and efficient, consider implementing the rxjs forkJoin operator. This operator is designed for the specific purpose of combining multiple observables into one. Essentially, if you have two observables that need to be subscribed to during component initialization, forkJoin can handle this process seamlessly.

Alternatively, you can avoid using subscribe by utilizing .toPromise() and awaiting the results. For more information on this approach, you can refer to this post.

Answer №3

If you want to optimize your code, consider using combineLatest(RxJS v6) or combineLatestWith(RxJs v7.4). The combineLatest operator will trigger an emission every time any of the source Observables emit after they've emitted at least once. Since your services are independent of each other, using combineLatest would be more suitable in this scenario.

    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 successfully!");
               this.isLoading = false;
               this.favorites = favoriteData.favorites;
               this.fetchFavorites();
               console.log("Favorites fetched successfully!");
            })).subscribe();

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

The date in a nodejs application appears to be shifted by one day

Despite similar questions being asked before here, the solutions provided did not resolve my issue. Here is the scenario I am dealing with: https://i.sstatic.net/H9rcO.png Upon clicking save, I collect data from certain fields to generate a date using th ...

Having trouble with sending a POST request using Rest Client, Express, and Node.js?

const express = require('express') const router = express.Router() const flightMethods = require("../model/users") //Setting up routing based on requirements router.post('/bookFlight', (req, res, err, next) => { let flightBooking ...

implement some level of control within the ngFor directive in Angular

For instance, let's say I have an ngfor loop: <ng-container *ngFor="let setting of settings | trackBy: trackById"> <button mat-button [matMenuTriggerFor]="menu">Menu</button> <mat-me ...

The implementation of Symbol.species in the Node.js Buffer class to generate a RapidBuffer seems illogical and confusing

While exploring the source code of ws, a popular WebSocket implementation for Node.js, I stumbled upon this specific piece of code: const FastBuffer = Buffer[Symbol.species]; But what exactly is this FastBuffer used for? Surprisingly, it seems that they a ...

Retrieve the attribute from a TypeScript union data type

Here is the structure that I am working with: export interface VendorState extends PaginationViewModel { vendors: CategoryVendorCommand[] | CategoryVendorCommand; } This is my model: export interface CategoryVendorCommand { id: string; name: str ...

Understanding the Typescript Type for a JSON Schema Object

When working with JSON-schema objects in typescript, is there a specific type that should be associated with them? I currently have a method within my class that validates whether its members adhere to the dynamic json schema schema. This is how I am doing ...

In Typescript, it is not possible to assign the type 'any' to a string, but I am attempting to assign a value that is

I'm new to TypeScript and currently learning about how types function in this language. Additionally, I'm utilizing MaterialUI for this particular project. The issue I'm encountering involves attempting to assign an any value to a variable ...

What is the process of 'initializing' an object in TypeScript?

Is it possible that retrieving a json from a mongodb database and casting it does not trigger the typescript constructor? What could be causing this issue? I have a Team class export class Team { transformations: { [transformationId: string]: Transfor ...

Bringing Angular ECharts into a Stackblitz 15.1 setup: A How-To Guide

Recently, Stackblitz made a change to use a standalone configuration for Angular Projects. However, when trying to initialize the module for Angular ECharts (ngx-echarts), an error occurred: Error in src/main.ts (18:5) Type 'ModuleWithProviders<Ngx ...

Is there a way to configure tsconfig so that it can properly recognize ".graphql" files and aliases when they are imported into components?

Currently, I have encountered an issue where including "graphql" files in my components is leading to TypeScript errors due to unrecognized import pathing. Despite the error, the functionality works as expected. import QUERY_GET_CATS from "@gql/GetCats.gra ...

Explaining the union type using a list of data types

Is there a way to create a union type that strictly limits values to 'a', 'b', 'c' when using a list like const list: string[] = ['a', 'b', 'c']? I know one method is: const list = ['a' ...

Having trouble compiling Angular CLI version 8.3.21 with the command ng serve

Upon trying to compile my first Angular app, I encountered an error when running ng serve: ERROR in ./src/styles.css (./node_modules/@angular-devkit/build-angular/src/angular-cli-files/plugins/raw-css-loader.js!./node_modules/postcss-loader/src??embedded! ...

Eliminate nested object properties using an attribute in JavaScript

I am working with a nested object structured like this const data = [ { id: '1', description: 'desc 1', data : [ { id: '5', description: 'desc', number :1 }, { id: '4', description: 'descip& ...

Struggling to compile Angular 8 when using ng2-adsense

I have integrated the ng2-adsense Google Adsense library into my Angular applications to display ads. To set it up, I followed the guidelines provided at this link: https://github.com/scttcper/ng2-adsense/ Include the adsbygoogle.js script in the head s ...

How can you toggle the selection of a clicked element on and off?

I am struggling with the selection color of my headings which include Administration, Market, DTA. https://i.stack.imgur.com/luqeP.png The issue is that the selection color stays on Administration, even when a user clicks on another heading like Market. ...

Securing Angular 2+: Safeguarding Server-Side Lazy Loaded Modules

In my Angular 2+ application, users input personal data that is then analyzed in a different section of the app restricted to authorized personnel. The challenge lies in preventing unauthorized individuals from gaining insight into the analysis process. We ...

Attempt to create a truncated text that spans two lines, with the truncation occurring at the beginning of the text

How can I truncate text on two lines with truncation at the beginning of the text? I want it to appear like this: ... to long for this div I haven't been able to find a solution. Does anyone have any suggestions? Thanks in advance! ...

Having trouble getting tailwind dark mode to work on next.js?

I have set up a custom boilerplate using next.js(10.0.5) with preact(10.5.12), typescript(4.1.3), and tailwind(2.0.2). I am attempting to incorporate a dark mode feature from Tailwind. I followed the instructions from next-themes in order to add the dark ...

Encountering an error when attempting to store a value in an array of custom types: "Unable to assign properties to undefined (setting 'id')"

My model looks like this: export class SelectedApplicationFeatures { id: number; } In my TypeScript file, I imported the model as shown below: import { SelectedApplicationFeatures } from "src/app/models/selectedApplicationFeatures.model"; selec ...

Having trouble setting the default value of a select element with 'selected' in Angular's bootstrap?

Click here I've been facing some difficulties in making the 'selected' tag work to pre-select my default select value. It seems like it might be related to the unique pipe I'm using and how Angular handles it. I have experimented with ...