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
This commit is contained in:
Andrew Scott
2022-12-20 08:35:23 -08:00
committed by Alex Rickabaugh
parent d887d69f24
commit 31b94c762f
5 changed files with 94 additions and 8 deletions
+1 -2
View File
@@ -396,13 +396,12 @@ available in `RouterModule.forRoot` or `provideRouter` and `withRouterConfig`.
* `paramsInheritanceStrategy`
* `urlUpdateStrategy`
* `canceledNavigationResolution`
* `errorHandler`
The following options are available in `RouterModule.forRoot` but not
available in `provideRouter`:
* `malformedUriErrorHandler` - This was not found to be used by anyone.
There are currently no plans to make this available in `provideRouter`.
* `errorHandler` - Developers should instead subscribe to `Router.events`
and filter for `NavigationError`.
<a id="router-can-load"></a>
+7 -1
View File
@@ -246,6 +246,9 @@ export type DisabledInitialNavigationFeature = RouterFeature<RouterFeatureKind.D
// @public
export type EnabledBlockingInitialNavigationFeature = RouterFeature<RouterFeatureKind.EnabledBlockingInitialNavigationFeature>;
// @public
export type ErrorHandlerFeature = RouterFeature<RouterFeatureKind.NavigationErrorHandlerFeature>;
// @public
type Event_2 = RouterEvent | NavigationStart | NavigationEnd | NavigationCancel | NavigationError | RoutesRecognized | GuardsCheckStart | GuardsCheckEnd | RouteConfigLoadStart | RouteConfigLoadEnd | ChildActivationStart | ChildActivationEnd | ActivationStart | ActivationEnd | Scroll | ResolveStart | ResolveEnd | NavigationSkipped;
export { Event_2 as Event }
@@ -748,7 +751,7 @@ export interface RouterFeature<FeatureKind extends RouterFeatureKind> {
}
// @public
export type RouterFeatures = PreloadingFeature | DebugTracingFeature | InitialNavigationFeature | InMemoryScrollingFeature | RouterConfigurationFeature;
export type RouterFeatures = PreloadingFeature | DebugTracingFeature | InitialNavigationFeature | InMemoryScrollingFeature | RouterConfigurationFeature | ErrorHandlerFeature;
// @public
export type RouterHashLocationFeature = RouterFeature<RouterFeatureKind.RouterHashLocationFeature>;
@@ -1071,6 +1074,9 @@ export function withDisabledInitialNavigation(): DisabledInitialNavigationFeatur
// @public
export function withEnabledBlockingInitialNavigation(): EnabledBlockingInitialNavigationFeature;
// @public
export function withErrorHandler(fn: (error: NavigationError) => void): ErrorHandlerFeature;
// @public
export function withHashLocation(): RouterConfigurationFeature;
+1 -1
View File
@@ -15,7 +15,7 @@ export {ActivationEnd, ActivationStart, ChildActivationEnd, ChildActivationStart
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';
export {Navigation, NavigationExtras, UrlCreationOptions} from './navigation_transition';
export {DefaultTitleStrategy, TitleStrategy} from './page_title_strategy';
export {DebugTracingFeature, DisabledInitialNavigationFeature, EnabledBlockingInitialNavigationFeature, InitialNavigationFeature, InMemoryScrollingFeature, PreloadingFeature, provideRouter, provideRoutes, RouterConfigurationFeature, RouterFeature, RouterFeatures, RouterHashLocationFeature, withDebugTracing, withDisabledInitialNavigation, withEnabledBlockingInitialNavigation, withHashLocation, withInMemoryScrolling, withPreloading, withRouterConfig} from './provide_router';
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';
export {BaseRouteReuseStrategy, DetachedRouteHandle, RouteReuseStrategy} from './route_reuse_strategy';
export {Router} from './router';
export {ExtraOptions, InitialNavigation, InMemoryScrollingOptions, ROUTER_CONFIGURATION, RouterConfigOptions} from './router_config';
+61 -3
View File
@@ -7,7 +7,7 @@
*/
import {HashLocationStrategy, LOCATION_INITIALIZED, LocationStrategy, ViewportScroller} from '@angular/common';
import {APP_BOOTSTRAP_LISTENER, APP_INITIALIZER, ApplicationRef, ComponentRef, ENVIRONMENT_INITIALIZER, EnvironmentProviders, inject, InjectFlags, InjectionToken, Injector, makeEnvironmentProviders, NgZone, Provider, Type} from '@angular/core';
import {APP_BOOTSTRAP_LISTENER, APP_INITIALIZER, ApplicationRef, ComponentRef, ENVIRONMENT_INITIALIZER, EnvironmentInjector, EnvironmentProviders, inject, InjectFlags, InjectionToken, Injector, makeEnvironmentProviders, NgZone, Provider, Type} from '@angular/core';
import {of, Subject} from 'rxjs';
import {filter, map, take} from 'rxjs/operators';
@@ -621,6 +621,63 @@ export function withHashLocation(): RouterConfigurationFeature {
return routerFeature(RouterFeatureKind.RouterConfigurationFeature, providers);
}
/**
* A type alias for providers returned by `withNavigationErrorHandler` for use with `provideRouter`.
*
* @see `withNavigationErrorHandler`
* @see `provideRouter`
*
* @publicApi
*/
export type NavigationErrorHandlerFeature =
RouterFeature<RouterFeatureKind.NavigationErrorHandlerFeature>;
/**
* Subscribes to the Router's navigation events and calls the given function when a
* `NavigationError` happens.
*
* This function is run inside application's injection context so you can use the `inject` function.
*
* @usageNotes
*
* Basic example of how you can use the error handler option:
* ```
* const appRoutes: Routes = [];
* bootstrapApplication(AppComponent,
* {
* providers: [
* provideRouter(appRoutes, withNavigationErrorHandler((e: NavigationError) =>
* inject(MyErrorTracker).trackError(e))
* ]
* }
* );
* ```
*
* @see `NavigationError`
* @see `inject`
* @see `EnvironmentInjector#runInContext`
*
* @returns A set of providers for use with `provideRouter`.
*
* @publicApi
*/
export function withNavigationErrorHandler(fn: (error: NavigationError) => void):
NavigationErrorHandlerFeature {
const providers = [{
provide: ENVIRONMENT_INITIALIZER,
multi: true,
useValue: () => {
const injector = inject(EnvironmentInjector);
inject(Router).events.subscribe((e) => {
if (e instanceof NavigationError) {
injector.runInContext(() => fn(e));
}
});
}
}];
return routerFeature(RouterFeatureKind.NavigationErrorHandlerFeature, providers);
}
/**
* A type alias that represents all Router features available for use with `provideRouter`.
* Features can be enabled by adding special functions to the `provideRouter` call.
@@ -632,7 +689,7 @@ export function withHashLocation(): RouterConfigurationFeature {
* @publicApi
*/
export type RouterFeatures = PreloadingFeature|DebugTracingFeature|InitialNavigationFeature|
InMemoryScrollingFeature|RouterConfigurationFeature;
InMemoryScrollingFeature|RouterConfigurationFeature|NavigationErrorHandlerFeature;
/**
* The list of features as an enum to uniquely type each feature.
@@ -644,5 +701,6 @@ export const enum RouterFeatureKind {
DisabledInitialNavigationFeature,
InMemoryScrollingFeature,
RouterConfigurationFeature,
RouterHashLocationFeature
RouterHashLocationFeature,
NavigationErrorHandlerFeature,
}
+24 -1
View File
@@ -17,7 +17,7 @@ import {concat, EMPTY, Observable, Observer, of, Subscription} from 'rxjs';
import {delay, filter, first, last, map, mapTo, takeWhile, tap} from 'rxjs/operators';
import {CanActivateChildFn, CanActivateFn, CanMatch, CanMatchFn, Data, ResolveFn} from '../src/models';
import {provideRouter, withRouterConfig} from '../src/provide_router';
import {provideRouter, withNavigationErrorHandler, withRouterConfig} from '../src/provide_router';
import {forEach, wrapIntoObservable} from '../src/utils/collection';
import {getLoadedRoutes} from '../src/utils/config';
@@ -1668,6 +1668,29 @@ describe('Integration', () => {
]);
})));
it('should be able to provide an error handler with DI dependencies', async () => {
@Injectable({providedIn: 'root'})
class Handler {
handlerCalled = false;
}
TestBed.configureTestingModule({
providers: [
provideRouter(
[{
path: 'throw',
canMatch: [() => {
throw new Error('');
}],
component: BlankCmp
}],
withNavigationErrorHandler(() => coreInject(Handler).handlerCalled = true)),
]
});
const router = TestBed.inject(Router);
await expectAsync(router.navigateByUrl('/throw')).toBeRejected();
expect(TestBed.inject(Handler).handlerCalled).toBeTrue();
});
// Errors should behave the same for both deferred and eager URL update strategies
['deferred', 'eager'].forEach((strat: any) => {
it('should dispatch NavigationError after the url has been reset back', fakeAsync(() => {