Angular - How to implement switchMap for multiple http requests?
Asked Answered
S

2

7

In my project, I sometimes want to update the available options in a list when the user changes the selected value in another list. To do this, I've used valueChanges with the pipe operator and switchMap like this:

this.form.controls.typeControl.valueChanges
  .pipe(
    switchMap((typeId: number) => {
      return this.typesService.getProjectsByType(typeId);
    }),
  )
  .subscribe((projects) => {
    //...
  });

Now I have the problem that I need to perform two http requests at once to update multiple lists instead of just one. When I try to add a second switchMap, I get error TS2345: Argument of type 'OperatorFunction<number, Project[]>' is not assignable to parameter of type 'OperatorFunction<any, number>'. Here is how I tried to do this:

this.form.controls.typeControl.valueChanges
  .pipe(
    switchMap((typeId: number) => {
      return this.typesService.getProjectsByType(typeId);
    }),
    switchMap((typeId: number) => {
      return this.typesService.getProgramsByType(typeId);
    }),
  )
  .subscribe(([projects, programs]) => {
    //...
  });

How can I add a second http request here so I can process the data received by both request in the subscribe?

Shirleyshirlie answered 1/4, 2022 at 14:27 Comment(4)
can you provide stackblitz example ?Aristides
i'm seeing in code code only 1 switchMap operator, can u post your full attempt?Snag
@Snag I added my attempt where I got the mentioned error to my question.Shirleyshirlie
Actually, your attempt is quite close. What you miss is that these are piped functions, which means that your second switchMap actually doesn't receive typeId but instead gets the observable from the first switchMap (hence that TS error). You basically need to map your first switchMap so it passes on a combination of typeId and the observable of the first switchmap.. Having sad that - multiple switchMaps aren't needed here as the second request doesn't depend on the first one so a forkJoin/combineLatest would be better.Curator
F
13

You could use combineLataest or forkJoin to create an observable that emits when either of it's sources emit:

this.form.controls.typeControl.valueChanges.pipe(
    switchMap(typeId => combineLatest([
      this.typesService.getProjectsByType(typeId),
      this.typesService.getProgramsByType(typeId)
    ]))
  )
  .subscribe(([projects, programs]) => {
    // ...
  });

However, if these two piece of data (projectList and programList) aren't related to each other, you may find it more convenient to define them as separate observables:

private typeId$ = this.form.controls.typeControl.valueChanges;

projectList$ = this.typeId$.pipe(switchMap(id => this.typesService.getProjectsByType(id)));
programList$ = this.typeId$.pipe(switchMap(id => this.typesService.getProgramsByType(id)));

This can give you more granular control and allow you to use the async pipe in your template a little easier:

    <h1> Projects </h1>
    <ul> 
        <li *ngFor="let project of projectList$ | async">
            {{ project.name }}
        </li>
    </ul>

    <h1> Programs </h1>
    <ul> 
        <li *ngFor="let program of programList$ | async">
            {{ program.name }}
        </li>
    </ul>
Fontanez answered 1/4, 2022 at 14:59 Comment(2)
Hi, there is an unsubscribe missing and this is introducing a potential memory leak. Since you are not using the async pipe, this won't automatically unsubscribe onDestroy. Use a take(1) or takeUntil. Or better use the async pipe if possible.Embolden
The second example does in fact show how to use the async pipe.Fontanez
S
3

forkJoin operator could help here, you can do it like this:

this.form.controls.typeControl.valueChanges
  .pipe(
    switchMap((typeId: number) => {
     return forkJoin([
       this.typesService.getProjectsByType(typeId),
       this.typesService.getProgramsByType(typeId)
      ])
    })
  )
  .subscribe(([projects, programs]) => {
    //...
  });
Snag answered 1/4, 2022 at 14:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.