diff --git a/goldens/public-api/router/errors.api.md b/goldens/public-api/router/errors.api.md index 81f7d42e67c4..0ed6714f7a14 100644 --- a/goldens/public-api/router/errors.api.md +++ b/goldens/public-api/router/errors.api.md @@ -9,6 +9,8 @@ export const enum RuntimeErrorCode { // (undocumented) EMPTY_PATH_WITH_PARAMS = 4009, // (undocumented) + ERROR_PARSING_URL = 4018, + // (undocumented) FOR_ROOT_CALLED_TWICE = 4007, // (undocumented) INFINITE_REDIRECT = 4016, diff --git a/packages/router/src/errors.ts b/packages/router/src/errors.ts index 37d08f5bf655..734d56e1c807 100644 --- a/packages/router/src/errors.ts +++ b/packages/router/src/errors.ts @@ -28,4 +28,5 @@ export const enum RuntimeErrorCode { INVALID_ROOT_URL_SEGMENT = 4015, INFINITE_REDIRECT = 4016, INVALID_ROUTER_LINK_INPUTS = 4017, + ERROR_PARSING_URL = 4018, } diff --git a/packages/router/src/router.ts b/packages/router/src/router.ts index 30379c22b663..896f962adacc 100644 --- a/packages/router/src/router.ts +++ b/packages/router/src/router.ts @@ -18,6 +18,7 @@ import { Type, untracked, ɵINTERNAL_APPLICATION_ERROR_HANDLER, + ɵformatRuntimeError as formatRuntimeError, } from '@angular/core'; import {Observable, Subject, Subscription, SubscriptionLike} from 'rxjs'; @@ -581,7 +582,13 @@ export class Router { parseUrl(url: string): UrlTree { try { return this.urlSerializer.parse(url); - } catch { + } catch (e) { + this.console.warn( + formatRuntimeError( + RuntimeErrorCode.ERROR_PARSING_URL, + ngDevMode && `Error parsing URL ${url}. Falling back to '/' instead. \n` + e, + ), + ); return this.urlSerializer.parse('/'); } } diff --git a/packages/router/test/router.spec.ts b/packages/router/test/router.spec.ts index c9dfe01f6361..fd221fc04cd8 100644 --- a/packages/router/test/router.spec.ts +++ b/packages/router/test/router.spec.ts @@ -7,7 +7,7 @@ */ import {Location} from '@angular/common'; -import {EnvironmentInjector, inject} from '@angular/core'; +import {EnvironmentInjector, inject, ɵConsole as Console} from '@angular/core'; import {TestBed} from '@angular/core/testing'; import {RouterModule} from '../index'; import {of} from 'rxjs'; @@ -20,7 +20,7 @@ import {resolveData as resolveDataOperator} from '../src/operators/resolve_data' import {Router} from '../src/router'; import {ChildrenOutletContexts} from '../src/router_outlet_context'; import {createEmptyStateSnapshot, RouterStateSnapshot} from '../src/router_state'; -import {DefaultUrlSerializer, UrlTree} from '../src/url_tree'; +import {DefaultUrlSerializer, UrlSerializer, UrlTree} from '../src/url_tree'; import {getAllRouteGuards} from '../src/utils/preactivation'; import {TreeNode} from '../src/utils/tree'; @@ -108,6 +108,30 @@ describe('Router', () => { }); }); + describe('parseUrl', () => { + beforeEach(() => { + TestBed.configureTestingModule({imports: [RouterModule.forRoot([])]}); + }); + + it('should log a warning and fall back to "/" when parsing fails', () => { + const router: Router = TestBed.inject(Router); + const urlSerializer: UrlSerializer = TestBed.inject(UrlSerializer); + const console: Console = TestBed.inject(Console); + spyOn(urlSerializer, 'parse').and.callFake((url: string) => { + if (url === 'invalid-url') { + throw new Error('test error'); + } + // The fallback call should not be mocked + return new DefaultUrlSerializer().parse(url); + }); + const spy = spyOn(console, 'warn'); + + const result = router.parseUrl('invalid-url'); + expect(spy.calls.argsFor(0)).toMatch(/Error parsing URL/); + expect(result).toEqual(new DefaultUrlSerializer().parse('/')); + }); + }); + describe('PreActivation', () => { const serializer = new DefaultUrlSerializer(); let empty: RouterStateSnapshot;