'How to make program wait until observable is executed in Angular

I'm working on an angular project, I have a situation where making a call to backend using an observable to fetch products.

Here is how the code looks like.

getProducts () : Product[] {
this.http.get<[]>(this.m_baseURL+'/products').subscribe(res => {
  console.log(res)
  this.products = res;
});
  return this.products
}

Problem is, the return statement doesn't wait for the above statement to get executed. And in my component, I get an empty array. I checked using console log in both service and component. And it turns out return statement gets executed before observable is done assigning the value.

How do I make it stop until it completes its job, just like async await does. Should I use normal async await instead of observable?

This is my first Angular project, so please pardon me if the question is novice.



Solution 1:[1]

You could use an async / await pattern for this, but I recommend against it. Learn how to program asyncronously using Observables.

The service method to load products should return the Observable:

getProducts () : Observabe<Product[]> {
 return this.http.get<[]>(this.m_baseURL+'/products')
}

The consuming code should subscribe to the Observable and get the data:

myGetProductService.getProducts().subscribe(res => {
  console.log(res)
  this.products = res;
});

Solution 2:[2]

The Angular way of doing things here is to not return a Product[], but to return the Observable itself:

getProducts(): Observable<Product[]> {
  return this.http.get<Product[]>(this.m_baseURL+'/products');
}

You then use the async pipe to subscribe to this Observable, and Angular will automatically display the data.

That's for the case where this is a service method. If you're doing this in a component, the simple answer is: don't. Put this in a service. That's what they're for.

Strongly suggest you try Angular's Tour of Heroes tutorial. They go over this sort of thing.

Solution 3:[3]

Instead of waiting for returns to time some tasks use reactive approach.

SomeService.ts

products$: Product[];

getProducts() : Product[] {
   this.products$ = this.http.get<[]>(this.m_baseURL+'/products');
}

SomeComponents.ts

filteredProducts: Product[];
private readonly unsubscribe$ = new Subject();

constructor(private someService: SomeService){}

ngOnInit() {
   this.someService.getProducts();

   this.someService.products$.pipe(takeUntil(this.unsubscribe$)).subscribe((products) => {
   this.filteredProducts = products.filter(product => product.id > 0); // look over filtering
});
}

ngOnDestroy() {
 this.unsubscribe$.next();
}

SomeComponent.html

<div *ngFor="product of filteredProducs">{{ product }}</div>

Many ways to approach this common problem. Many ways to improve it as well. This is one way. I don't know how your filtering works, but if possible I would prefer to use | async pipe to avoid manual subscribing at all and filter with an additional pipe or filter the observable itself.

Solution 4:[4]

To wait for the first value from an observable you can use firstValueFrom(). This method is dangerous since it can cause your application to hang indefinitely, always include a timeout mechanism.

  async getProducts(): Promise<Product[]> {
    const res = await firstValueFrom(
      this.http.get<[]>(this.m_baseURL + '/products').pipe(timeout(10000))
    );
    console.log(res);
    this.products = res;
    return this.products;
  }

A better design is to instead only make the http request to update this.products, and just use the variable like it always has content, you can do some other things in your subscription if you want side effects to happen on update.

products: Product[] = [];

ngOnInit(){
  this.updateProducts();
}

updateProducts() : void {
  this.http.get<[]>(this.m_baseURL+'/products').subscribe(res => {
    console.log(res)
    this.products = res;
    this.doSomething();
    this.doSomethingElse();
  });
}

Change detection will automatically update your html when an http response is received, but you can always force it with ChangeDetectorRef.detectChanges().

<div *ngFor="product of products">{{ product.name }}</div>

The above html will be blank until your first subscription completes, at which point it should automatically update.


If you're using a service it would look like this:

service

products: Product[] = [];

updateProducts() : void {
  this.http.get<[]>(this.m_baseURL+'/products').subscribe(res => {
    console.log(res)
    this.products = res;
    this.doSomething();
    this.doSomethingElse();
  });
}

component

constructor(private service: MyService){}

ngOnInit(){
  this.updateProducts();
}

get products(){
  return this.service.products;
}

updateProducts(){
  this.service.updateProducts();
}

The two wrapper methods are just so you don't have to write service in your html.


If you want unique side effects to happen in different components, you would convert this.products to a subject, and then you can subscribe and execute a callback whenever products changes. A BehaviorSubject lets you initialize the value to an empty array.

service

products$ = new BehaviorSubject<Product[]>([]);

updateProducts() : void {
  this.http.get<[]>(this.m_baseURL+'/products').subscribe(res => {
    console.log(res)
    this.products$.next(res);
  });
}

If you want to save the value of the subject in the component rather than subscribing with the async pipe, make sure to unsubscribe when the component is destroyed. That's because this subject does not complete like the observables from HttpClient, so subscriptions will remain in memory unless unsubscribed.

component

sub = new Subscription();
products = [];

constructor(private service: MyService){}

ngOnInit(){
  this.sub = this.service.products$.subscribe(res => {
    console.log(res)
    this.products = res;
    this.doSomething();
    this.doSomethingElse();
  });
  this.updateProducts();
}

updateProducts(){
  this.service.updateProducts();
}

ngOnDestroy(){
  this.sub.unsubscribe();
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 JeffryHouser
Solution 2 Will Alexander
Solution 3 Joosep Parts
Solution 4