From 2fc5c57b311f01d4864502f9d8c399c205d67a20 Mon Sep 17 00:00:00 2001 From: vsavkin Date: Thu, 25 Aug 2016 07:56:30 -0700 Subject: [PATCH] feat(router): add support for custom error handlers --- modules/@angular/router/src/router.ts | 23 ++++++++++++++++++- modules/@angular/router/src/router_module.ts | 13 ++++++++++- .../@angular/router/test/integration.spec.ts | 17 ++++++++++++++ tools/public_api_guard/router/index.d.ts | 2 ++ 4 files changed, 53 insertions(+), 2 deletions(-) diff --git a/modules/@angular/router/src/router.ts b/modules/@angular/router/src/router.ts index ef8ea7c28f..4c5f7b4984 100644 --- a/modules/@angular/router/src/router.ts +++ b/modules/@angular/router/src/router.ts @@ -201,6 +201,21 @@ export class RoutesRecognized { export type Event = NavigationStart | NavigationEnd | NavigationCancel | NavigationError | RoutesRecognized; +/** + * Error handler that is invoked when a navigation errors. + * + * If the handler retuns a value, the navigation promise will be resolved with this value. + * If the handler throws an exception, the navigation promise will be rejected with + * the exception. + * + * @stable + */ +export type ErrorHandler = (error: any) => any; + +function defaultErrorHandler(error: any): any { + throw error; +} + /** * The `Router` is responsible for mapping URLs to components. * @@ -216,6 +231,8 @@ export class Router { private navigationId: number = 0; private configLoader: RouterConfigLoader; + errorHandler: ErrorHandler = defaultErrorHandler; + /** * Indicates if at least one navigation happened. * @@ -533,7 +550,11 @@ export class Router { resolvePromise(false); } else { this.routerEvents.next(new NavigationError(id, this.serializeUrl(url), e)); - rejectPromise(e); + try { + resolvePromise(this.errorHandler(e)); + } catch (ee) { + rejectPromise(ee); + } } this.currentRouterState = storedState; this.currentUrlTree = storedUrl; diff --git a/modules/@angular/router/src/router_module.ts b/modules/@angular/router/src/router_module.ts index c95a0daf4b..c2ee5295bd 100644 --- a/modules/@angular/router/src/router_module.ts +++ b/modules/@angular/router/src/router_module.ts @@ -13,7 +13,7 @@ import {Route, Routes} from './config'; import {RouterLink, RouterLinkWithHref} from './directives/router_link'; import {RouterLinkActive} from './directives/router_link_active'; import {RouterOutlet} from './directives/router_outlet'; -import {Router} from './router'; +import {ErrorHandler, Router} from './router'; import {ROUTES} from './router_config_loader'; import {RouterOutletMap} from './router_outlet_map'; import {ActivatedRoute} from './router_state'; @@ -137,11 +137,18 @@ export function provideRoutes(routes: Routes): any { /** + * Extra options used to configure the router. + * + * Set `enableTracing` to log router events to the console. + * Set 'useHash' to true to enable HashLocationStrategy. + * Set `errorHandler` to enable a custom ErrorHandler. + * * @stable */ export interface ExtraOptions { enableTracing?: boolean; useHash?: boolean; + errorHandler?: ErrorHandler; } export function setupRouter( @@ -156,6 +163,10 @@ export function setupRouter( componentType, urlSerializer, outletMap, location, injector, loader, compiler, flatten(config)); + if (opts.errorHandler) { + r.errorHandler = opts.errorHandler; + } + if (opts.enableTracing) { r.events.subscribe(e => { console.group(`Router Event: ${(e.constructor).name}`); diff --git a/modules/@angular/router/test/integration.spec.ts b/modules/@angular/router/test/integration.spec.ts index 978a077e50..0aedb6a146 100644 --- a/modules/@angular/router/test/integration.spec.ts +++ b/modules/@angular/router/test/integration.spec.ts @@ -427,6 +427,23 @@ describe('Integration', () => { ]); }))); + it('should support custom error handlers', fakeAsync(inject([Router], (router: Router) => { + router.errorHandler = (error) => 'resolvedValue'; + const fixture = createRoot(router, RootCmp); + + router.resetConfig([{path: 'user/:name', component: UserCmp}]); + + const recordedEvents: any[] = []; + router.events.forEach(e => recordedEvents.push(e)); + + let e: any; + router.navigateByUrl('/invalid').then(_ => e = _); + advance(fixture); + expect(e).toEqual('resolvedValue'); + + expectEvents(recordedEvents, [[NavigationStart, '/invalid'], [NavigationError, '/invalid']]); + }))); + it('should replace state when path is equal to current path', fakeAsync(inject([Router, Location], (router: Router, location: Location) => { const fixture = createRoot(router, RootCmp); diff --git a/tools/public_api_guard/router/index.d.ts b/tools/public_api_guard/router/index.d.ts index 6751c7b89f..e11cdd90b3 100644 --- a/tools/public_api_guard/router/index.d.ts +++ b/tools/public_api_guard/router/index.d.ts @@ -72,6 +72,7 @@ export declare type Event = NavigationStart | NavigationEnd | NavigationCancel | /** @stable */ export interface ExtraOptions { enableTracing?: boolean; + errorHandler?: ErrorHandler; useHash?: boolean; } @@ -168,6 +169,7 @@ export interface Route { /** @stable */ export declare class Router { config: Routes; + errorHandler: ErrorHandler; events: Observable; /** @experimental */ navigated: boolean; routerState: RouterState;