Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to handle error in a Resolver

Tags:

angular

rxjs

I'm trying to use resolvers in order to make a better UX. Everything works great on the happy path. What I can't seem to figure out is how to handle exceptions. My resolver calls a service, which hits a webapi project. An example:

FooResolver:

resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<Foo> {       return this.fooService.getById(route.params['id']).catch(err => {     ****not sure what to do/return in the case of a server error****     return Observable.throw(err);   }); }  

FooService:

  public getById(id: string): Observable<Foo> {     return this.http.get(`${ this.apiUrl }/${ id }`)         .map(this.extractData)         .catch(this.handleError); } 

The handleError function:

   protected handleError (error: Response | any) {     // Todo: Log the error        // Errors will be handled uniquely by the component that triggered them     return Observable.throw(error); } 

Inside the FooComponent, I do this (this is never hit in the event of an error returned from the service/resolver):

FooComponent:

ngOnInit(): void {     this.foo= this.route.snapshot.data['foo'];     if (this.foo) {        this.createForm(this.foo);     } } 

I've tried throwing the error (as shown) - I get this exception in the console:

Uncaught (in promise): Response with status: 500 Internal Server Error for URL:

and returning new Observable<Foo>(), which gives:

Cannot read property 'subscribe' of undefined

I have a few resolvers, all of which can experience exceptions on the server, But I don't know what to do in the event of these exceptions.

like image 356
Matt M Avatar asked May 10 '17 17:05

Matt M


People also ask

How do you handle errors in services?

You can specify an error code and error data for the error. Consider specifying the error data to catch specific errors. For example, you could filter on the error code for the types of errors that are caught and map the error code to a variable after the errors are caught.

How do I resolve angular errors?

The Shortcomings of Angular's ErrorHandler One traditional way of handling errors in Angular is to provide an ErrorHandler class. This class can be extended to create your own global error handler. This is also a useful way to handle all errors that occur, but is mostly useful for tracking error logs.

How do you test angular resolvers?

To test the resolver we need to render RouterOutlet . const fixture = MockRender(RouterOutlet); Additionally, we also need to properly customize mocked services if the resolver is using them to fetch data. const dataService = TestBed.


2 Answers

Here is an example of one of my resolvers with error handling, using the technique that Gunter suggests:

import { Injectable } from '@angular/core'; import { Resolve, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from '@angular/router';  import { Observable } from 'rxjs/Observable'; import 'rxjs/add/operator/catch'; import 'rxjs/add/operator/map'; import 'rxjs/add/observable/of';  import { IProduct } from './product'; import { ProductService } from './product.service';  @Injectable() export class ProductResolver implements Resolve<IProduct> {      constructor(private productService: ProductService,                 private router: Router) { }      resolve(route: ActivatedRouteSnapshot,             state: RouterStateSnapshot): Observable<IProduct> {         let id = route.params['id'];         if (isNaN(+id)) {             console.log(`Product id was not a number: ${id}`);             this.router.navigate(['/products']);             return Observable.of(null);         }         return this.productService.getProduct(+id)             .map(product => {                 if (product) {                     return product;                 }                 console.log(`Product was not found: ${id}`);                 this.router.navigate(['/products']);                 return null;             })             .catch(error => {                 console.log(`Retrieval error: ${error}`);                 this.router.navigate(['/products']);                 return Observable.of(null);             });     } } 

You can find the complete example here: https://github.com/DeborahK/Angular-Routing in the APM-final folder.

UPDATE Feb 2019

Here is a better answer for error handling in a resolver:

  1. Wrap your interface in another interface with an optional error property:
/* Defines the product entity */ export interface Product {   id: number;   productName: string;   productCode: string;   category: string;   tags?: string[];   releaseDate: string;   price: number;   description: string;   starRating: number;   imageUrl: string; }  export interface ProductResolved {   product: Product;   error?: any; } 
  1. Resolve to that interface:
import { Injectable } from '@angular/core'; import { Resolve, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';  import { Observable, of } from 'rxjs'; import { map, catchError } from 'rxjs/operators';  import { ProductResolved } from './product'; import { ProductService } from './product.service';  @Injectable({   providedIn: 'root', }) export class ProductResolver implements Resolve<ProductResolved> {   constructor(private productService: ProductService) {}    resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<ProductResolved> {     const id = route.paramMap.get('id');     if (isNaN(+id)) {       const message = `Product id was not a number: ${id}`;       console.error(message);       return of({ product: null, error: message });     }      return this.productService.getProduct(+id).pipe(       map((product) => ({ product: product })),       catchError((error) => {         const message = `Retrieval error: ${error}`;         console.error(message);         return of({ product: null, error: message });       }),     );   } } 
  1. In the component, pull off the piece of the interface you need:
ngOnInit(): void {   const resolvedData: ProductResolved = this.route.snapshot.data['resolvedData'];   this.errorMessage = resolvedData.error;   this.product = resolvedData.product; } 
like image 177
DeborahK Avatar answered Sep 21 '22 06:09

DeborahK


You need to return an observable that completes with false

handleError() {   return Observable.of([false]); } 
like image 37
Günter Zöchbauer Avatar answered Sep 20 '22 06:09

Günter Zöchbauer