4

I have a component that is currently set up like this:

export class CompareComponent implements OnInit, OnDestroy {
  criteria: Criteria[]
  products: Product[] = [];

  private organisationId: string

  private criteriaSubscription: Subscription
  private routeSubscription: Subscription
  private productSubscription: Subscription

  constructor(
    @Inject(PLATFORM_ID) private platformId: Object,
    private route: ActivatedRoute,

    private attributeMatchService: AttributeMatchService,
    private criteriaService: CriteriaService,
    private optionsService: OptionsService,
    private productService: ProductService,
  ) { }

  ngOnInit() {
    this.organisationId = this.optionsService.get().organisation;
    this.routeSubscription = this.route.paramMap.subscribe(paramMap => {
      let category = paramMap.get('category');
      let gtins = paramMap.get('gtins').split('.');
      this.listCriteria(category);
      this.listProducts(category, gtins);
    });
  }

  ngOnDestroy() {
    if (this.criteriaSubscription) this.criteriaSubscription.unsubscribe();
    if (this.productSubscription) this.productSubscription.unsubscribe();
    if (this.routeSubscription) this.routeSubscription.unsubscribe();
  }

  close() {
    if (isPlatformServer(this.platformId)) return;
    window.history.back();
  }

  private listCriteria(category: string): void {
    this.criteriaSubscription = this.criteriaService.list(category, 'Attributes').subscribe(criteria => this.criteria = criteria);
  }

  private listProducts(category: string, gtins: string[]) {
    gtins.forEach(gtin => {
      this.productSubscription = this.productService.get(category, this.organisationId, parseInt(gtin)).subscribe(products => {
        this.products.push(products[0]);
      });
    });
  }
}

As you can see from the listProducts method, I get a list of products based on the gtins that have been passed as a parameter. What I would like to do is wait for all the subscriptions to finish in listProducts and after they have done, to run some code.

How can I do this?

r3plica
  • 13,017
  • 23
  • 128
  • 290
  • 1
    I think you're looking for something like this to combine the observables and wait for all, that's RxJs [How to 'wait' for two observables in RxJS](https://stackoverflow.com/questions/44004144/how-to-wait-for-two-observables-in-rxjs) You might also want to check out [Observable](http://reactivex.io/rxjs/class/es6/Observable.js~Observable.html) – rmjoia Apr 15 '19 at 12:57

2 Answers2

4

If you want to wait for all observables, use the forkJoin operator and then flatten the result to push it to your products array.

I'd also suggest to use the takeUntil operator as a cleanup helper for your subscriptions. See how it can be used in my example.

forkJoin(
      gtins.forEach(gtin => this.productService.get(category, this.organisationId, parseInt(gtin)))
    ).pipe(
      takeUntil(this.destroyed$)
    ).subscribe(products => {
      this.products.push(...products.flat())
    });

And in your onDestroy hook

  onDestroy() {
    this.destroyed$.next();
    this.destroyed$.complete();
  }

And in your component, define destroyed$ as

destroyed$: Subject<any> = new Subject<any>();

Daniel B
  • 8,770
  • 5
  • 43
  • 76
3

I believe you will need to use forkJoin.

It waits for all the Observables from the array to finish and then emits.

Do the individual observable operations inside tap of each of them.

private listProducts(category: string, gtins: string[]) {
    const products$: Observable<any>[] = [];

    gtins.forEach(gtin => {
        products$.push(this.productService.get(category, 1, parseInt(gtin))
                        .pipe(tap(product => {
                              this.products.push(products[0]);
        }));
    });

    forkJoin(products$).subscribe(() => {
        console.log('All subscriptions done');
    });
}
Roberto Zvjerković
  • 9,657
  • 4
  • 26
  • 47