Angular2. How can I check if an observable is completed?
Asked Answered
D

6

34

In my page there is a button that generates a report. That report needs data that is loaded using a http call to a rest endpoint when the page is loaded, but I do not have a guarantee that they are loaded when the user presses the report button.

How can I watch the observable to see if it is completed, and if incomplete, to wait on the action until the http call is completed? Here is some of the code:

loadCompanies(): void {
    this._companyService.getCompanies().subscribe(
        response => {
            this.companiesModel = response;
        },
        err => console.log(err)
    );
}
generateReport() {
   // check if observable that loads companies is completed and do the 
   // action using companiesModel.
} 

One option is a flag set in loading companies with values of 'loading' and 'completed', and make a wait in generateReport() until the flag is completed, but I would prefer a solution using the Observable API if possible.

Disinterest answered 11/7, 2017 at 10:41 Comment(2)
just do a if(companiesModel) that will do the trick i hopePhilippic
Newbie question, what exactly is 'complete' in this case? on page load is there a single call to loadCompanies? so if that has given a response does that mean observable is completed?Hob
H
27

You can do this by using onCompleted callback in subscription. For example, let's say you show loading bar when user press report button;

loadCompanies(): void {
     this._companyService.getCompanies().subscribe(
          response => {
               this.companiesModel = response;
          },
          err => {
               console.log(err);
               //closeLoadingBar();
          },
          () => {
               //do whatever you want
               //closeLoadingBar()
          }
     )
}

generateReport() {
    //showLoadingBar()
    this.loadCompanies();
}

If you get error from your http call, onCompleted method will not be invoked, only onError will be invoked. If it is successful, onCompleted method will be called after your onNext method.

Here is the documentation for subscribe. I hope it helps!

Hurrah answered 11/7, 2017 at 10:55 Comment(1)
Update 2024: This is now deprecated, and should use subscribe arguments instead: of([1,2,3]).subscribe({complete: console.info}); rxjs.dev/deprecations/subscribe-argumentsPlover
S
9

One more solution:

Actually subscribe function takes three parameters:

onNext onError onCompleted

this._companyService.getCompanies().subscribe(
    (response) => { this.companiesModel = response; },
    (err) => { console.log(err) },
    (finally) => { console.log('finally') }
);
Sudra answered 11/7, 2017 at 10:52 Comment(2)
Thanks for reply. I know about this. But my situation is different.I don't need to trigger my action when observable is complete. When my action is done I need to check if the observable is completed, and if not, to wait until is completed.Disinterest
same. Am I forced to set a flag in that callback? That feels harder than it should be. Isn't there an observable.isComplete property?Mady
S
5

Method

finally()

Invokes a specified action after the source observable sequence terminates gracefully or exceptionally.

https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/operators/finally.md

Sudra answered 11/7, 2017 at 10:48 Comment(0)
M
4

In this kind of scenarios it is very useful to use concatMap operator to guarantee to execute the next operation only when the previous has done.

loadCompanies(): void {
    this._companyService.getCompanies()
    .concatMap(companyList => this.getObservableGenerateReport(companyList))
    .subscribe(
        response => {
            this.companiesModel = response;
        },
        err => console.log(err)
    );
}


//Create observable to generate the report
getObservableGenerateReport(response: any): Observable<myReportList> {

    return Observable.create(observer => {

      if (generateReport().isSuccessful) {
        observer.next(myReportList);
        observer.complete();
      } else {
        console.log(err, 'Ups, something was wrong!');
        observer.next({});
        observer.complete();
      }

    });
  }
Mortgagor answered 11/7, 2017 at 15:45 Comment(1)
Thanks. Something like this I was looking for. Let me try if is working as expected, and I'll mark this answer.Disinterest
M
2

If you're doing this to debug, the simplest solution is to use the complete argument of tap:

tap(next: null, error: null, complete: () => void)

With a source source$ it would look like this:

source$.pipe(
  tap(undefined, undefined, console.log)
);
Misstate answered 6/8, 2020 at 12:0 Comment(0)
S
0

The solution I came up with is to use a shared observable, save the request as a hot observable that way when the report button is clicked it will wait for the request or immediately generate if the request is complete.

public companiesModel: Company[];

/** pending request, hot Observable (will emit immediately if complete) */
private companiesRequest: Observable<Company[]>;

constructor(
  private _companyService: CompanyService
) {}

public ngOnInit(): void {
  this.loadCompanies();
}

public generateReport(): void {
  if (this.companiesRequest) {
    // will not make an other network request
    this.companiesRequest.subscribe(
      response => {
        // action using companiesModel.
      },
      err => console.log(err)
    );
  }
}

private loadCompanies(): void {
  this.companiesRequest = this._companyService.getCompanies().pipe(shareReplay());
  this.companiesRequest.subscribe(
    response => {
      this.companiesModel = response;
    },
    err => console.log(err)
  );
}

https://stackblitz.com/edit/check-if-an-observable-is-completed?file=src%2Fapp%2Fapp.component.ts

Update: You could take it one step further and make the UI async https://stackblitz.com/edit/check-if-an-observable-is-completed-async-ui?file=src%2Fapp%2Fapp.component.html

Scarecrow answered 3/7, 2018 at 19:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.