Pablo
Pablo

Reputation: 380

Execute observable after another set of Observables have finished

So, I'm new to RxJS and was playing around with Angular 5, wondering how to accomplish the following:

Let's asume we have a form. When the page loads, we need 3 select to be populated with data from the server, so we have 3 Observables to accomplish this.

Now, we also have an observable for when the route params change (in that case, we have to get the requested record and populate the form):

// These 3 guys will get the required data for each select

this.countryService.getAll().subscribe(countries => {
  this.countries = countries;
});
this.categoryService.getAll().subscribe(categories => {
  this.categories = categories;
});
this.sectorService.getAll().subscribe(sectors => {
  this.sectors = sectors;
});


// And this is for the change in url

this.route.paramMap.subscribe(params => {
  this.formDisabled = true;
  const id = params.get('id');

  // We get the resource based on the id param
  this.service.get(id).subscribe(contact => {
    this.contact = contact;

    this.form.reset(this.contact);

    this.formDisabled = false;
  }
});

Now, I need the callback of this.service.get(id).subscribe() to execute only after the 3 selects have been populated, i.e. when their respective callbacks have completed, otherwise we may end up trying to do things with the form when it's not completely built. I would like it to remain requesting the resource in parallel to the other 3 requests, but execute the callback (reset the form with it) only after the other 3 are completely done.

Upvotes: 4

Views: 6955

Answers (1)

CozyAzure
CozyAzure

Reputation: 8478

It often helps if you try to layout the steps on what you want to achieve, instead of how you want to achieve. This trains you to think of in a more "reactive" manner.

  1. Get the 3 dropdowns. This can be done in parallel

    • Assign the dropdown values
  2. Retrieve the route params

    • Disable the form
  3. Call service to retrieve by id which is from step 2
  4. Do the rest:

    • Assign the values of contact.

    • Reset the form

    • Renable the form

Once you layout the steps, coding them as observables will be quite trivial:

//Step 1: Call the dropdownsService in parallel
Observable.forkJoin([
    this.countryService.getAll(),
    this.categoryService.getAll(),
    this.sectorService.getAll()
])
    .switchMap(([countries, categories, sectors]) => {
        //Assign the dropdown values
        this.countries = countries;
        this.categories = categories;
        this.sectors = sectors;
        //Step 2: Retrieve the route params
        return this.route.paramMap;
    })
    .switchMap(({id}) => {
        //disable the form
        this.formDisabled = true;
        //step 3: Call the service to get contact info
        return this.service.get(id)
    })
    .subscribe(contact => {
        //Do the rest
        this.contact = contact;
        this.form.reset(this.contact);
        this.formDisabled = false;
    });

PS: I use object and array destructuring for more succinct and readable code.

Edit:

If you want to call your this.service.get parallel to the dropdown service, put them in the same Observable.forkJoin:

Observable.forkJoin([
    this.countryService.getAll(),
    this.categoryService.getAll(),
    this.sectorService.getAll(),
    this.route.paramMap.switchMap(({id}) => {
        this.formDisabled = true;
        return this.service.get(id);
    })
])
    .subscribe(([countries, categories, sectors, contact]) => {
        //Assign the dropdown values
        this.countries = countries;
        this.categories = categories;
        this.sectors = sectors;

        //Do the rest
        this.contact = contact;
        this.form.reset(this.contact);
        this.formDisabled = false;
    })

Edit 2:

If you want to listen to the changes to any of the observable grouped together, regardless who emits first, use combineLatest():

Observable.combineLatest(
        Observable.forkJoin([
            this.countryService.getAll(),
            this.categoryService.getAll(),
            this.sectorService.getAll()
        ]),
        this.route.paramMap.switchMap(({id}) => {
            this.formDisabled = true;
            return this.service.get(id);
        })
    )
        .subscribe(([countries, categories, sectors, contact]) => {
            //Assign the dropdown values
            this.countries = countries;
            this.categories = categories;
            this.sectors = sectors;

            //Do the rest
            this.contact = contact;
            this.form.reset(this.contact);
            this.formDisabled = false;
        })

Upvotes: 6

Related Questions