1

I am trying to subscribe two observable and get the values to store in an array object. It is working ok but my problem is it iterates three times which i don't understand. I am doing this in a service to create a new service. Below is the code for example. I am also wondering can i use promise instead of observable in angular?. Or can i convert the observable into promise and resolve after I get the value ? Thank you for your help

 addEmployeeData() {
    const employeeObservable = this.apiService.getEmployeeDataObservable();
    employeeObservable.subscribe({
      next: (res: any) => {
        let employee = res;
        const paramsObservable = this.apiService.getParamsObservable();
        pageParamsObservable.subscribe({
          next: (pageParams: any) => {
Mystic Groot
  • 143
  • 2
  • 13

4 Answers4

2

Yes, you can work with Observables as with Promises:

async asyncAddEmployeeData(): Promise<any> {
  return this.apiService.getEmployeeDataObservable()
    .pipe(
      mergeMap(employeeData => this.apiService.getParamsObservable()
        .pipe(
          tap((paramsData): void => {
            // There is available data
            // from apiService.getEmployeeDataObservable()
            // as employeeData variable
            // and data from apiService.getParamsObservable()
            // as paramsData.
            // You can do in tap function all the same
            // as in next in the subscribe.
          }),
        )
      ),
    )
    .toPromise();
}

And use it like here:

async ngOnInit(): Promise<void> {
  // ngOnInit just for example.
  const someVariable = await this.asyncAddEmployeeData();
}

But regular way of using Observable looks like this:

addEmployeeData(): Observable<any> {
  return this.apiService.getEmployeeDataObservable()
    .pipe(
      mergeMap(employeeData => this.apiService.getParamsObservable()
        .pipe(
          tap(paramsData => {
            // There is available data
            // from apiService.getEmployeeDataObservable()
            // as employeeData variable
            // and data from apiService.getParamsObservable()
            // as paramsData.
          }),
        )
      ),
      take(1), // Just if you need only first value, if not, please, remove this string.
    );
}

And subscription:

ngOnInit(): void {
  // ngOnInit just for example.
  this.subscription = this.addEmployeeData().subscribe();
}

Don't forget unsubscribe to avoid of memory leaks:

ngOnDestroy(): void {
  this.subscription.unsubscribe();
}
  • appreciate your effort in taking time to write all this code. Is it good to use promise and observables at the same time i mean if we use promise with in observables. The promise won't get resolved until the observable is done ? Please correct me if i am wrong. – Mystic Groot Mar 01 '21 at 06:33
  • The best practice for Angular is using Observables only. But using promises with in Observables is permissible too. On backend projects (NestJS) transformation Observables into Promises is common approach, judging by code examples on Youtube, StackOverflow and Udemy. And yes, promise will resolved with the first event in Observable stream after that Observable subscription becomes unsubscribed. – Mikhail Filchushkin Mar 01 '21 at 07:26
2
  1. You can use any of the RxJS higher order mapping operator (like switchMap) to switch from one observable to another that depend on each other.
addEmployeeData() {
  this.apiService.getEmployeeDataObservable().pipe(
    switchMap(_ => {
      let employee = res;  // why is this needed though?
      return this.apiService.getParamsObservable();
    })
  ).subscribe({
    next: (pageParams: any) => { 
      // handle response
    },
    error: (error: any) => {
      // handle error
    }
  );
}
  1. If the observables are not related to each other (eg. if the second request does not depend on the result of first request) you could use functions like RxJS forkJoin or combineLatest to trigger the observables in parallel.

    See my post here for a quick comparison of mapping operators and combination functions.

  2. It's good practice to close any open subscription in the ngOnDestroy so they don't persist and lead to potential memory leaks. There are multiple ways to close the subscription.

    3.1 unsubscribe - Assign the subscription to a member variable and call unsubscribe() on it in the ngOnDestroy hook.

    3.2 take(1) - Close the subscription after the emission of first notification.

    3.3 first() - Similar to take(1) but takes additional predicate and emits error if none of the emissions matched the predicate. See here for take(1) vs first().

    3.4. takeUntil() - Use additional multicast to close multiple open subscriptions with a single emission. Most preferred way for it's elegance. See here for more info.

Note: Observables returned by Angular HttpClient complete after the first emission without any of the above mentioned operations. So in most cases they aren't required.

ruth
  • 29,535
  • 4
  • 30
  • 57
  • Appreciated for you time and explanation this really helps a lot very useful knowledge to share. Sure, I can remove the this line it is not needed `let employee = res;`. I was also wondering please correct me if I'm mistaken. I did try using `combineLatest` as suggested by @Srikar Phani Kumar Marti but it says it is deprecated. Not sure why I am getting that error. I'm using latest rxjs. – Mystic Groot Mar 01 '21 at 09:30
  • `combineLatest` used to be an operator similar to `switchMap`. But the usage shown by @SrikarPhaniKumarMarti shouldn't denote deprecation since it's the now recommended way of using it. Could you please provide a screenshot of the error. And for the sake of clarity, what version of RxJS are you using exactly? – ruth Mar 01 '21 at 09:34
  • Sure, here is the link for the screenshot and Iam on version `"rxjs": "~6.6.0"` and https://ibb.co/j5gqyhX – Mystic Groot Mar 01 '21 at 10:49
  • @MysticGroot: The import statement is denoting the old `combineLatest`. The newer `combineLatest` is static and as such should be imported from `rxjs` and not `operators`. Try: `import { combineLatest } from 'rxjs';` – ruth Mar 01 '21 at 10:52
  • Perfect that worked. Great thank you so much for all the help @Michael D – Mystic Groot Mar 01 '21 at 10:55
1

Since you have 2 Observables and you are doing the actual logic only after you subscribe to the second observable, i am assuming without the second observable data, you are not implementing any logic.

For this you have 2 options to go about.

  1. Use mergeMap RxJS operator and do what Mikhail Filchushkin said above.

(OR)

  1. My method, use combineLatest operator and also use Subject variable to destroy the subscription after its done. This is how you do this:

The advantage of this method over mergeMap is you will have only one subscription and one unsubscription whereas in mergeMap you will have 2.

MergeMap will subscribe to the first variable and if its successfully subscribed then only it will subscribe to the second subscription.

In combineLatest it will subscribe irrespective of the data that is coming.

That is kind of an advantage and a disadvantage depending on how you use it.

And If you want only the first value, please use take(1) operator to stop it from further subscribing

const employeeObservable = this.apiService.getEmployeeDataObservable();
const paramsObservable = this.apiService.getParamsObservable();

destroy$: Subject<any> = new Subject(); // This is used to destroy the subscription later

// Combine the observables and destroy once done in 1 shot.

combineLatest(employeeObservable,paramsObservable)
  .pipe(
      takeUntil(this.destroy$),
      take(1)
   )
  .subscribe(
     ([employeeObservableData, paramsObservableData]) => {
        if (employeeObservableData && paramsObservableData) {
          // do your logic here
        }  
     }
  );

ngOnDestroy() {
   this.destroy$.next();
   this.destroy$.complete();
}

Srikar Phani Kumar M
  • 1,069
  • 1
  • 3
  • 8
  • Firstly appreciate your time and help I learned a lot. I didn't even know we can merge or combine the two calls and work with. I have an another question instead of take(1) can I use first() and what will be the preferred function? – Mystic Groot Mar 01 '21 at 06:22
0

Try to create a subscription and unsubscribe on onDestroy.

// import 
import { Subscription } from 'rxjs';

subscription: Subscription;

ngOnInit() { 
    this.subscription = // employeeObservable.subscribe()
}

ngOnDestroy() { 
    this.subscription.unsubscribe();
}
rdr20
  • 186
  • 7