diff --git a/modules/@angular/router/src/router.ts b/modules/@angular/router/src/router.ts index 903f1b1d08..9b075af18e 100644 --- a/modules/@angular/router/src/router.ts +++ b/modules/@angular/router/src/router.ts @@ -524,6 +524,9 @@ export class Router { */ navigate(commands: any[], extras: NavigationExtras = {skipLocationChange: false}): Promise { + if (typeof extras.queryParams === 'object' && extras.queryParams !== null) { + extras.queryParams = this.removeEmptyProps(extras.queryParams); + } return this.navigateByUrl(this.createUrlTree(commands, extras), extras); } @@ -549,6 +552,16 @@ export class Router { } } + private removeEmptyProps(params: Params): Params { + return Object.keys(params).reduce((result: Params, key: string) => { + const value: any = params[key]; + if (value !== null && value !== undefined) { + result[key] = value; + } + return result; + }, {}); + } + private processNavigations(): void { concatMap .call( diff --git a/modules/@angular/router/test/integration.spec.ts b/modules/@angular/router/test/integration.spec.ts index e8e1cfc488..57a2cb2d58 100644 --- a/modules/@angular/router/test/integration.spec.ts +++ b/modules/@angular/router/test/integration.spec.ts @@ -487,6 +487,18 @@ describe('Integration', () => { expect(fixture.nativeElement).toHaveText('query: 2 fragment: fragment2'); }))); + it('should ignore null and undefined query params', + fakeAsync(inject([Router], (router: Router) => { + const fixture = createRoot(router, RootCmp); + + router.resetConfig([{path: 'query', component: EmptyQueryParamsCmp}]); + + router.navigate(['query'], {queryParams: {name: 1, age: null, page: undefined}}); + advance(fixture); + const cmp = fixture.debugElement.children[1].componentInstance; + expect(cmp.recordedParams).toEqual([{name: '1'}]); + }))); + it('should push params only when they change', fakeAsync(inject([Router], (router: Router) => { const fixture = createRoot(router, RootCmp); @@ -2436,6 +2448,15 @@ class QueryParamsAndFragmentCmp { } } +@Component({selector: 'empty-query-cmp', template: ``}) +class EmptyQueryParamsCmp { + recordedParams: Params[] = []; + + constructor(route: ActivatedRoute) { + route.queryParams.forEach(_ => this.recordedParams.push(_)); + } +} + @Component({selector: 'route-cmp', template: `route`}) class RouteCmp { constructor(public route: ActivatedRoute) {} @@ -2529,7 +2550,8 @@ function createRoot(router: Router, type: any): ComponentFixture { RouteCmp, RootCmp, RelativeLinkInIfCmp, - RootCmpWithTwoOutlets + RootCmpWithTwoOutlets, + EmptyQueryParamsCmp ], @@ -2554,7 +2576,8 @@ function createRoot(router: Router, type: any): ComponentFixture { RouteCmp, RootCmp, RelativeLinkInIfCmp, - RootCmpWithTwoOutlets + RootCmpWithTwoOutlets, + EmptyQueryParamsCmp ], @@ -2580,7 +2603,8 @@ function createRoot(router: Router, type: any): ComponentFixture { RouteCmp, RootCmp, RelativeLinkInIfCmp, - RootCmpWithTwoOutlets + RootCmpWithTwoOutlets, + EmptyQueryParamsCmp ] }) class TestModule {