Mystic Groot
Mystic Groot

Reputation: 143

How to subscribe observable only once

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) => {

Upvotes: 1

Views: 7459

Answers (4)

Barremian
Barremian

Reputation: 31135

  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.

Upvotes: 2

Srikar Phani Kumar M
Srikar Phani Kumar M

Reputation: 1384

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();
}

Upvotes: 1

Mikhail Filchushkin
Mikhail Filchushkin

Reputation: 330

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();
}

Upvotes: 2

rdr20
rdr20

Reputation: 196

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();
}

Upvotes: 0

Related Questions