简体   繁体   中英

Error handling with Angular2 async pipe

I am using the Angular2 async pipe to stream values into the DOM. Here's a real simple example:

const stream = Observable.interval(1000)
  .take(5)
  .map(n => { if (n === 3) throw "ERROR"; return n; });

<div *ngFor="for num of stream | async">
  {{num}}
</div>

<div id="error"></div>

What I would like to do is to have the sequence of 1-5 displayed, but on the error item (3), somehow populate the #error div with the error message.

This seems to require two things: first is the ability of the Angular async pipe to do something intelligent with errors, which I see no sign of. Looking at the source code, apparently it throws a JS exception, which doesn't seem too friendly.

Second is the ability to restart or continue the sequence after the error. I have read about catch and onErrorResumeNext and so on, but they all involve another sequence which will be switched to on an error. This greatly complicates the logic of generating the stream, on which I would just like to put a series of numbers (in this simple example). I have the sinking feeling that once an error occurs the game is over and the observable is completed and can only be "restarted" with a different observable. I'm still learning observables; is this in fact the case?

So my question is twofold:

  1. Can Angular2's async pipe do something intelligent with errors?
  2. Do observables have some simple way to continue after an error?

Yes you're right regarding the catch operator and the ability to do something after errors occur...

I would leverage the catch operator to catch the error and do something:

const stream = Observable.interval(1000)
  .take(5)
  .map(n => {
    if (n === 3) {
      throw Observable.throw(n);
    }
    return n;
  })
  .catch(err => {
    this.error = error;
    (...)
  });

and in the template:

<div>{{error}}</div>

To be able to go on the initial observable, you need to create a new one starting at the point where the error occurs:

createObservable(i) {
  return Observable.interval(1000)
    .range(i + 1, 5 - i)
    .take(5 - i)
  });
}

and use it in the catch callback:

  .catch(err => {
    this.error = error;
    return this.createObservable(err);
  });

These two questions could help you:

1) no, The async pipe subscribes and unsubscribes and returns the events it receives. You would need to handle the errors before they receive the async pipe.

2) You can use the catch operator and when it returns an observable then its value(s) is emitted by the .catch(err => Observable.of(-1)) instead of the error.

You could use this to emit a special "error" value and then use something like *ngIf="num === -1 to show the error value in some special way.

You can find more information on this https://blog.thoughtram.io/angular/2017/02/27/three-things-you-didnt-know-about-the-async-pipe.html

@Thierry Templier answer was correct but is now a bit outdated. Here's how to do it with the latest RXJS.

this.myObservable$ = this.myService.myFunc().pipe(
  catchError(() => of([])) // this will emit [] if the request fails - u could handle this [] emit on error in the service itself
)

then HTML as normal:

<div *ngFor="let xxx of (myObservable$ | async)">
</div>

Note $ at end of Observable name is Angular recommended way to denote an Observable.

I was facing a similar issue and came up with another approach. I do not know if it's a good way of doing it, but it works.

template where you want to show the result of your observable:

<div *ngIf="tableData$ | async as tableData; else loader" class="mt-4">
  <!-- do something with tableData -->
</div>


<ng-template #loader>
  <loading [target]="tableData$"></loading>
</ng-template>

The loading component:

export class LoadingComponent implements OnInit {

  private _errorMessageSubject : Subject<string> = new Subject<string>();
  
  private _errorMessage$ : Observable<string> = this._errorMessageSubject.asObservable();
  public get errorMessage$() : Observable<string> { return this._errorMessage$; }

  private _target : Observable<any> | null = null;
  public get target() : Observable<any> | null { return this._target }
  
  // this input does nothing except catch the error and feed the
  // message into the errorMessage subject.
  @Input() public set target(o: Observable<any> | null) { 
    if(o == null) { return; }
    this._target = o.pipe(
      catchError((error, _) => {
        this._errorMessageSubject.next(error);
        return of(null);
      }),
    );
  };
    
  
  constructor() { }

  ngOnInit(): void {
  }

}

loader template:

<div *ngIf="target && target | async;">
</div>

<div *ngIf="errorMessage$ | async as error; else loading">
  <p class="text-danger">{{ error }}</p>
</div>
  
<ng-template #loading> <!-- simply a spinner icon -->
  <div class="d-flex justify-content-center">
    <fa-icon [icon]="['fas', 'spinner']" size="6x" [spin]="true"></fa-icon>
  </div>
</ng-template>

I am not perfectly sure if its a good approach to subscribe to the observable twice, as subscribing is done in the original component that needs the data and in the loader, but otherwise this seems to work properly.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM