Skip to content

Commit 31b94c7

Browse files
atscottalxhub
authored andcommitted
feat(router): Add a withNavigationErrorHandler feature to provideRouter (#48551)
`withNavigationErrorHandler` is a close replacement for the `RouterModule.forRoot.errorHandler` / `Router.errorHandler`. It provides a quick, short way for users to define a function to handle `NavigationError` events. PR Close #48551
1 parent d887d69 commit 31b94c7

File tree

5 files changed

+94
-8
lines changed

5 files changed

+94
-8
lines changed

aio/content/guide/deprecations.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -396,13 +396,12 @@ available in `RouterModule.forRoot` or `provideRouter` and `withRouterConfig`.
396396
* `paramsInheritanceStrategy`
397397
* `urlUpdateStrategy`
398398
* `canceledNavigationResolution`
399+
* `errorHandler`
399400

400401
The following options are available in `RouterModule.forRoot` but not
401402
available in `provideRouter`:
402403
* `malformedUriErrorHandler` - This was not found to be used by anyone.
403404
There are currently no plans to make this available in `provideRouter`.
404-
* `errorHandler` - Developers should instead subscribe to `Router.events`
405-
and filter for `NavigationError`.
406405

407406
<a id="router-can-load"></a>
408407

goldens/public-api/router/index.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,9 @@ export type DisabledInitialNavigationFeature = RouterFeature<RouterFeatureKind.D
246246
// @public
247247
export type EnabledBlockingInitialNavigationFeature = RouterFeature<RouterFeatureKind.EnabledBlockingInitialNavigationFeature>;
248248

249+
// @public
250+
export type ErrorHandlerFeature = RouterFeature<RouterFeatureKind.NavigationErrorHandlerFeature>;
251+
249252
// @public
250253
type Event_2 = RouterEvent | NavigationStart | NavigationEnd | NavigationCancel | NavigationError | RoutesRecognized | GuardsCheckStart | GuardsCheckEnd | RouteConfigLoadStart | RouteConfigLoadEnd | ChildActivationStart | ChildActivationEnd | ActivationStart | ActivationEnd | Scroll | ResolveStart | ResolveEnd | NavigationSkipped;
251254
export { Event_2 as Event }
@@ -748,7 +751,7 @@ export interface RouterFeature<FeatureKind extends RouterFeatureKind> {
748751
}
749752

750753
// @public
751-
export type RouterFeatures = PreloadingFeature | DebugTracingFeature | InitialNavigationFeature | InMemoryScrollingFeature | RouterConfigurationFeature;
754+
export type RouterFeatures = PreloadingFeature | DebugTracingFeature | InitialNavigationFeature | InMemoryScrollingFeature | RouterConfigurationFeature | ErrorHandlerFeature;
752755

753756
// @public
754757
export type RouterHashLocationFeature = RouterFeature<RouterFeatureKind.RouterHashLocationFeature>;
@@ -1071,6 +1074,9 @@ export function withDisabledInitialNavigation(): DisabledInitialNavigationFeatur
10711074
// @public
10721075
export function withEnabledBlockingInitialNavigation(): EnabledBlockingInitialNavigationFeature;
10731076

1077+
// @public
1078+
export function withErrorHandler(fn: (error: NavigationError) => void): ErrorHandlerFeature;
1079+
10741080
// @public
10751081
export function withHashLocation(): RouterConfigurationFeature;
10761082

packages/router/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export {ActivationEnd, ActivationStart, ChildActivationEnd, ChildActivationStart
1515
export {CanActivate, CanActivateChild, CanActivateChildFn, CanActivateFn, CanDeactivate, CanDeactivateFn, CanLoad, CanLoadFn, CanMatch, CanMatchFn, Data, DefaultExport, LoadChildren, LoadChildrenCallback, NavigationBehaviorOptions, OnSameUrlNavigation, QueryParamsHandling, Resolve, ResolveData, ResolveFn, Route, Routes, RunGuardsAndResolvers, UrlMatcher, UrlMatchResult} from './models';
1616
export {Navigation, NavigationExtras, UrlCreationOptions} from './navigation_transition';
1717
export {DefaultTitleStrategy, TitleStrategy} from './page_title_strategy';
18-
export {DebugTracingFeature, DisabledInitialNavigationFeature, EnabledBlockingInitialNavigationFeature, InitialNavigationFeature, InMemoryScrollingFeature, PreloadingFeature, provideRouter, provideRoutes, RouterConfigurationFeature, RouterFeature, RouterFeatures, RouterHashLocationFeature, withDebugTracing, withDisabledInitialNavigation, withEnabledBlockingInitialNavigation, withHashLocation, withInMemoryScrolling, withPreloading, withRouterConfig} from './provide_router';
18+
export {DebugTracingFeature, DisabledInitialNavigationFeature, EnabledBlockingInitialNavigationFeature, InitialNavigationFeature, InMemoryScrollingFeature, NavigationErrorHandlerFeature as ErrorHandlerFeature, PreloadingFeature, provideRouter, provideRoutes, RouterConfigurationFeature, RouterFeature, RouterFeatures, RouterHashLocationFeature, withDebugTracing, withDisabledInitialNavigation, withEnabledBlockingInitialNavigation, withHashLocation, withInMemoryScrolling, withNavigationErrorHandler as withErrorHandler, withPreloading, withRouterConfig} from './provide_router';
1919
export {BaseRouteReuseStrategy, DetachedRouteHandle, RouteReuseStrategy} from './route_reuse_strategy';
2020
export {Router} from './router';
2121
export {ExtraOptions, InitialNavigation, InMemoryScrollingOptions, ROUTER_CONFIGURATION, RouterConfigOptions} from './router_config';

packages/router/src/provide_router.ts

Lines changed: 61 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
*/
88

99
import {HashLocationStrategy, LOCATION_INITIALIZED, LocationStrategy, ViewportScroller} from '@angular/common';
10-
import {APP_BOOTSTRAP_LISTENER, APP_INITIALIZER, ApplicationRef, ComponentRef, ENVIRONMENT_INITIALIZER, EnvironmentProviders, inject, InjectFlags, InjectionToken, Injector, makeEnvironmentProviders, NgZone, Provider, Type} from '@angular/core';
10+
import {APP_BOOTSTRAP_LISTENER, APP_INITIALIZER, ApplicationRef, ComponentRef, ENVIRONMENT_INITIALIZER, EnvironmentInjector, EnvironmentProviders, inject, InjectFlags, InjectionToken, Injector, makeEnvironmentProviders, NgZone, Provider, Type} from '@angular/core';
1111
import {of, Subject} from 'rxjs';
1212
import {filter, map, take} from 'rxjs/operators';
1313

@@ -621,6 +621,63 @@ export function withHashLocation(): RouterConfigurationFeature {
621621
return routerFeature(RouterFeatureKind.RouterConfigurationFeature, providers);
622622
}
623623

624+
/**
625+
* A type alias for providers returned by `withNavigationErrorHandler` for use with `provideRouter`.
626+
*
627+
* @see `withNavigationErrorHandler`
628+
* @see `provideRouter`
629+
*
630+
* @publicApi
631+
*/
632+
export type NavigationErrorHandlerFeature =
633+
RouterFeature<RouterFeatureKind.NavigationErrorHandlerFeature>;
634+
635+
/**
636+
* Subscribes to the Router's navigation events and calls the given function when a
637+
* `NavigationError` happens.
638+
*
639+
* This function is run inside application's injection context so you can use the `inject` function.
640+
*
641+
* @usageNotes
642+
*
643+
* Basic example of how you can use the error handler option:
644+
* ```
645+
* const appRoutes: Routes = [];
646+
* bootstrapApplication(AppComponent,
647+
* {
648+
* providers: [
649+
* provideRouter(appRoutes, withNavigationErrorHandler((e: NavigationError) =>
650+
* inject(MyErrorTracker).trackError(e))
651+
* ]
652+
* }
653+
* );
654+
* ```
655+
*
656+
* @see `NavigationError`
657+
* @see `inject`
658+
* @see `EnvironmentInjector#runInContext`
659+
*
660+
* @returns A set of providers for use with `provideRouter`.
661+
*
662+
* @publicApi
663+
*/
664+
export function withNavigationErrorHandler(fn: (error: NavigationError) => void):
665+
NavigationErrorHandlerFeature {
666+
const providers = [{
667+
provide: ENVIRONMENT_INITIALIZER,
668+
multi: true,
669+
useValue: () => {
670+
const injector = inject(EnvironmentInjector);
671+
inject(Router).events.subscribe((e) => {
672+
if (e instanceof NavigationError) {
673+
injector.runInContext(() => fn(e));
674+
}
675+
});
676+
}
677+
}];
678+
return routerFeature(RouterFeatureKind.NavigationErrorHandlerFeature, providers);
679+
}
680+
624681
/**
625682
* A type alias that represents all Router features available for use with `provideRouter`.
626683
* Features can be enabled by adding special functions to the `provideRouter` call.
@@ -632,7 +689,7 @@ export function withHashLocation(): RouterConfigurationFeature {
632689
* @publicApi
633690
*/
634691
export type RouterFeatures = PreloadingFeature|DebugTracingFeature|InitialNavigationFeature|
635-
InMemoryScrollingFeature|RouterConfigurationFeature;
692+
InMemoryScrollingFeature|RouterConfigurationFeature|NavigationErrorHandlerFeature;
636693

637694
/**
638695
* The list of features as an enum to uniquely type each feature.
@@ -644,5 +701,6 @@ export const enum RouterFeatureKind {
644701
DisabledInitialNavigationFeature,
645702
InMemoryScrollingFeature,
646703
RouterConfigurationFeature,
647-
RouterHashLocationFeature
704+
RouterHashLocationFeature,
705+
NavigationErrorHandlerFeature,
648706
}

packages/router/test/integration.spec.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import {concat, EMPTY, Observable, Observer, of, Subscription} from 'rxjs';
1717
import {delay, filter, first, last, map, mapTo, takeWhile, tap} from 'rxjs/operators';
1818

1919
import {CanActivateChildFn, CanActivateFn, CanMatch, CanMatchFn, Data, ResolveFn} from '../src/models';
20-
import {provideRouter, withRouterConfig} from '../src/provide_router';
20+
import {provideRouter, withNavigationErrorHandler, withRouterConfig} from '../src/provide_router';
2121
import {forEach, wrapIntoObservable} from '../src/utils/collection';
2222
import {getLoadedRoutes} from '../src/utils/config';
2323

@@ -1668,6 +1668,29 @@ describe('Integration', () => {
16681668
]);
16691669
})));
16701670

1671+
it('should be able to provide an error handler with DI dependencies', async () => {
1672+
@Injectable({providedIn: 'root'})
1673+
class Handler {
1674+
handlerCalled = false;
1675+
}
1676+
TestBed.configureTestingModule({
1677+
providers: [
1678+
provideRouter(
1679+
[{
1680+
path: 'throw',
1681+
canMatch: [() => {
1682+
throw new Error('');
1683+
}],
1684+
component: BlankCmp
1685+
}],
1686+
withNavigationErrorHandler(() => coreInject(Handler).handlerCalled = true)),
1687+
]
1688+
});
1689+
const router = TestBed.inject(Router);
1690+
await expectAsync(router.navigateByUrl('/throw')).toBeRejected();
1691+
expect(TestBed.inject(Handler).handlerCalled).toBeTrue();
1692+
});
1693+
16711694
// Errors should behave the same for both deferred and eager URL update strategies
16721695
['deferred', 'eager'].forEach((strat: any) => {
16731696
it('should dispatch NavigationError after the url has been reset back', fakeAsync(() => {

0 commit comments

Comments
 (0)