How to emit only last request in subscribe method

Viewed 301

I send a form using complex logic inside concatMap operator. And all works fine, but in my subscribe method I have additional http requests, which fires 2 times instead of 1. I tried to use takeLast operator, but it didn't help. I need to make requests in subscribe method only after all requests completed in concatMap. For example, if I have two updateMember() events, after each request I get invoked function from subscribe method.

makeRequest(value) {
  const team = toFormData(this.form.controls.profile.value);

  this.teamService
  .sendTeam(team)
  .pipe(
    concatMap(() => {
      const tasks$ = [];

      for (const [index, m] of this.form.controls.members.value.entries()) {
        const member = toFormData(m);

        if (m.id) {
          tasks$.push(this.teamService.updateMember(member, m.id).pipe(take(1)));
        } else if (!member.entries().next().done) {
          if (member.has('name')) {
            tasks$.push(this.teamService.createMember(member).pipe(take(1)));
          } else {
            (this.form.controls.members as FormArray).controls[index].markAllAsTouched();
            this.scrollToFirstInvalidControl();
          }
        }
      }

      return tasks$.length ? concat(...tasks$) : EMPTY;
    }),
  )
  .subscribe(() => {

    this.teamService.getMembers().subscribe();

  });
}

What should I use to make only necessary requests?

3 Answers

I think using takeLast was a good idea, but it's important where you place it:

this.teamService
  .sendTeam(tem)
  .pipe(
    concatMap(() => {
      /* ... */

      return tasks.length ? concat(...tasks).pipe(last()) : EMPTY;
    }),

    // You can avoid to manually subscribe in the `subscribe`'s callback
    switchMap(() => this.teamService.getMembers())
  ).subscribe()

concatMap subscribes to the an observable resulted from calling the provided callback function.

In this case, that observable can either be concat(...tasks).pipe(last()) or EMPTY.

If it's concat(...tasks).pipe(last()), it will wait for each task to complete. last also uses takeLast(1) internally.

By the way, concat can be seen this way:

// concat(observables[])

from(observables[])
  .pipe(
    mergeMap(obs$ => obs$, 1)
  )

If it's EMPTY, it will immediately send a complete notification, meaning that teamService.getMembers() from switchMap(() => this.teamService.getMembers()) won't be invoked.

You are not using any value from sendTeam, you're only waiting for the completion so you can subscribe to the response an then process your data.

makeRequest(value) {
  const team = toFormData(this.form.controls.profile.value);
  new Observable(subscription => {
    this.teamService
    .sendTeam(team)
    .subscribe(() => {
      const tasks$ = [];
        for (const [index, m] of this.form.controls.members.value.entries()) {
          const member = toFormData(m);

          if (m.id) {
            tasks$.push(this.teamService.updateMember(member, m.id).pipe(take(1)));
          } else if (!member.entries().next().done) {
            if (member.has('name')) {
              tasks$.push(this.teamService.createMember(member).pipe(take(1)));
            } else {
              (this.form.controls.members as FormArray).controls[index].markAllAsTouched();
              this.scrollToFirstInvalidControl();
            }
          }
        }

        subscription.next(tasks$.length ? concat(...tasks$) : EMPTY);
    });
  }).subscribe(() => {

    this.teamService.getMembers().subscribe();

  });
}

I think, the data structure you are looking for is the ReplySubject. It will act as a Subject and will store the last emitted value.

Related