diff --git a/modules/@angular/router/src/config.ts b/modules/@angular/router/src/config.ts
index dc68e60ced..4dd00be40b 100644
--- a/modules/@angular/router/src/config.ts
+++ b/modules/@angular/router/src/config.ts
@@ -320,6 +320,13 @@ export type LoadChildrenCallback = () =>
*/
export type LoadChildren = string | LoadChildrenCallback;
+/**
+ * @whatItDoes The type of `queryParamsHandling`.
+ * See {@link RouterLink} for more details.
+ * @stable
+ */
+export type QueryParamsHandling = 'merge' | 'preserve' | '';
+
/**
* See {@link Routes} for more details.
* @stable
diff --git a/modules/@angular/router/src/directives/router_link.ts b/modules/@angular/router/src/directives/router_link.ts
index fe8469de70..5a84060bd5 100644
--- a/modules/@angular/router/src/directives/router_link.ts
+++ b/modules/@angular/router/src/directives/router_link.ts
@@ -7,9 +7,10 @@
*/
import {LocationStrategy} from '@angular/common';
-import {Attribute, Directive, ElementRef, HostBinding, HostListener, Input, OnChanges, OnDestroy, Renderer} from '@angular/core';
+import {Attribute, Directive, ElementRef, HostBinding, HostListener, Input, OnChanges, OnDestroy, Renderer, isDevMode} from '@angular/core';
import {Subscription} from 'rxjs/Subscription';
+import {QueryParamsHandling} from '../config';
import {NavigationEnd, Router} from '../router';
import {ActivatedRoute} from '../router_state';
import {UrlTree} from '../url_tree';
@@ -59,12 +60,26 @@ import {UrlTree} from '../url_tree';
*
* You can also tell the directive to preserve the current query params and fragment:
*
+ * deprecated, use `queryParamsHandling` instead
+ *
* ```
*
* link to user component
*
* ```
*
+ * You can tell the directive to how to handle queryParams, available options are:
+ * - 'merge' merge the queryParams into the current queryParams
+ * - 'preserve' prserve the current queryParams
+ * - default / '' use the queryParams only
+ * same options for {@link NavigationExtras.queryParamsHandling}
+ *
+ * ```
+ *
+ * link to user component
+ *
+ * ```
+ *
* The router link directive always treats the provided input as a delta to the current url.
*
* For instance, if the current url is `/user/(box//aux:team)`.
@@ -82,11 +97,12 @@ import {UrlTree} from '../url_tree';
export class RouterLink {
@Input() queryParams: {[k: string]: any};
@Input() fragment: string;
- @Input() preserveQueryParams: boolean;
+ @Input() queryParamsHandling: QueryParamsHandling;
@Input() preserveFragment: boolean;
@Input() skipLocationChange: boolean;
@Input() replaceUrl: boolean;
private commands: any[] = [];
+ private preserve: boolean;
constructor(
private router: Router, private route: ActivatedRoute,
@@ -105,6 +121,14 @@ export class RouterLink {
}
}
+ @Input()
+ set preserveQueryParams(value: boolean) {
+ if (isDevMode() && console && console.warn) {
+ console.warn('preserveQueryParams is deprecated!, use queryParamsHandling instead.');
+ }
+ this.preserve = value;
+ }
+
@HostListener('click')
onClick(): boolean {
const extras = {
@@ -120,7 +144,8 @@ export class RouterLink {
relativeTo: this.route,
queryParams: this.queryParams,
fragment: this.fragment,
- preserveQueryParams: attrBoolValue(this.preserveQueryParams),
+ preserveQueryParams: attrBoolValue(this.preserve),
+ queryParamsHandling: this.queryParamsHandling,
preserveFragment: attrBoolValue(this.preserveFragment),
});
}
@@ -140,12 +165,13 @@ export class RouterLinkWithHref implements OnChanges, OnDestroy {
@HostBinding('attr.target') @Input() target: string;
@Input() queryParams: {[k: string]: any};
@Input() fragment: string;
- @Input() preserveQueryParams: boolean;
+ @Input() queryParamsHandling: QueryParamsHandling;
@Input() preserveFragment: boolean;
@Input() skipLocationChange: boolean;
@Input() replaceUrl: boolean;
private commands: any[] = [];
private subscription: Subscription;
+ private preserve: boolean;
// the url displayed on the anchor element.
@HostBinding() href: string;
@@ -169,6 +195,14 @@ export class RouterLinkWithHref implements OnChanges, OnDestroy {
}
}
+ @Input()
+ set preserveQueryParams(value: boolean) {
+ if (isDevMode() && console && console.warn) {
+ console.warn('preserveQueryParams is deprecated, use queryParamsHandling instead.');
+ }
+ this.preserve = value;
+ }
+
ngOnChanges(changes: {}): any { this.updateTargetUrlAndHref(); }
ngOnDestroy(): any { this.subscription.unsubscribe(); }
@@ -199,7 +233,8 @@ export class RouterLinkWithHref implements OnChanges, OnDestroy {
relativeTo: this.route,
queryParams: this.queryParams,
fragment: this.fragment,
- preserveQueryParams: attrBoolValue(this.preserveQueryParams),
+ preserveQueryParams: attrBoolValue(this.preserve),
+ queryParamsHandling: this.queryParamsHandling,
preserveFragment: attrBoolValue(this.preserveFragment),
});
}
diff --git a/modules/@angular/router/src/router.ts b/modules/@angular/router/src/router.ts
index a7a8faef67..7c9cc789f6 100644
--- a/modules/@angular/router/src/router.ts
+++ b/modules/@angular/router/src/router.ts
@@ -7,7 +7,7 @@
*/
import {Location} from '@angular/common';
-import {Compiler, ComponentFactoryResolver, Injector, NgModuleFactoryLoader, ReflectiveInjector, Type} from '@angular/core';
+import {Compiler, ComponentFactoryResolver, Injector, NgModuleFactoryLoader, ReflectiveInjector, Type, isDevMode} from '@angular/core';
import {BehaviorSubject} from 'rxjs/BehaviorSubject';
import {Observable} from 'rxjs/Observable';
import {Subject} from 'rxjs/Subject';
@@ -22,7 +22,7 @@ import {mergeMap} from 'rxjs/operator/mergeMap';
import {reduce} from 'rxjs/operator/reduce';
import {applyRedirects} from './apply_redirects';
-import {ResolveData, Routes, validateConfig} from './config';
+import {QueryParamsHandling, ResolveData, Routes, validateConfig} from './config';
import {createRouterState} from './create_router_state';
import {createUrlTree} from './create_url_tree';
import {RouterOutlet} from './directives/router_outlet';
@@ -102,12 +102,26 @@ export interface NavigationExtras {
/**
* Preserves the query parameters for the next navigation.
*
+ * deprecated, use `queryParamsHandling` instead
+ *
* ```
* // Preserve query params from /results?page=1 to /view?page=1
* this.router.navigate(['/view'], { preserveQueryParams: true });
* ```
+ *
+ * @deprecated
*/
preserveQueryParams?: boolean;
+
+ /**
+ * config strategy to handle the query parameters for the next navigation.
+ *
+ * ```
+ * // from /results?page=1 to /view?page=1&page=2
+ * this.router.navigate(['/view'], { queryParams: { page: 2 }, queryParamsHandling: "merge" });
+ * ```
+ */
+ queryParamsHandling?: QueryParamsHandling;
/**
* Preserves the fragment for the next navigation
*
@@ -465,11 +479,28 @@ export class Router {
* ```
*/
createUrlTree(
- commands: any[], {relativeTo, queryParams, fragment, preserveQueryParams,
+ commands: any[], {relativeTo, queryParams, fragment, preserveQueryParams, queryParamsHandling,
preserveFragment}: NavigationExtras = {}): UrlTree {
+ if (isDevMode() && preserveQueryParams && console && console.warn) {
+ console.warn('preserveQueryParams is deprecated, use queryParamsHandling instead.');
+ }
const a = relativeTo || this.routerState.root;
- const q = preserveQueryParams ? this.currentUrlTree.queryParams : queryParams;
const f = preserveFragment ? this.currentUrlTree.fragment : fragment;
+ let q: Params = null;
+ if (queryParamsHandling) {
+ switch (queryParamsHandling) {
+ case 'merge':
+ q = merge(this.currentUrlTree.queryParams, queryParams);
+ break;
+ case 'preserve':
+ q = this.currentUrlTree.queryParams;
+ break;
+ default:
+ q = queryParams;
+ }
+ } else {
+ q = preserveQueryParams ? this.currentUrlTree.queryParams : queryParams;
+ }
return createUrlTree(a, this.currentUrlTree, commands, q, f);
}
diff --git a/modules/@angular/router/test/integration.spec.ts b/modules/@angular/router/test/integration.spec.ts
index c7ab7cdfbc..70331c2896 100644
--- a/modules/@angular/router/test/integration.spec.ts
+++ b/modules/@angular/router/test/integration.spec.ts
@@ -1100,6 +1100,50 @@ describe('Integration', () => {
expect(native.getAttribute('href')).toEqual('/home?q=456#1');
}));
+ it('should correctly use the preserve strategy', fakeAsync(() => {
+
+ @Component({
+ selector: 'someRoot',
+ template:
+ `Link`
+ })
+ class RootCmpWithLink {
+ }
+ TestBed.configureTestingModule({declarations: [RootCmpWithLink]});
+ const router: Router = TestBed.get(Router);
+ const fixture = createRoot(router, RootCmpWithLink);
+
+ router.resetConfig([{path: 'home', component: SimpleCmp}]);
+
+ const native = fixture.nativeElement.querySelector('a');
+
+ router.navigateByUrl('/home?a=123');
+ advance(fixture);
+ expect(native.getAttribute('href')).toEqual('/home?a=123');
+ }));
+
+ it('should correctly use the merge strategy', fakeAsync(() => {
+
+ @Component({
+ selector: 'someRoot',
+ template:
+ `Link`
+ })
+ class RootCmpWithLink {
+ }
+ TestBed.configureTestingModule({declarations: [RootCmpWithLink]});
+ const router: Router = TestBed.get(Router);
+ const fixture = createRoot(router, RootCmpWithLink);
+
+ router.resetConfig([{path: 'home', component: SimpleCmp}]);
+
+ const native = fixture.nativeElement.querySelector('a');
+
+ router.navigateByUrl('/home?a=123');
+ advance(fixture);
+ expect(native.getAttribute('href')).toEqual('/home?a=123&q=456');
+ }));
+
it('should support using links on non-a tags', fakeAsync(inject([Router], (router: Router) => {
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 680d663e1d..d1532d5484 100644
--- a/tools/public_api_guard/router/index.d.ts
+++ b/tools/public_api_guard/router/index.d.ts
@@ -127,8 +127,9 @@ export declare class NavigationError {
export interface NavigationExtras {
fragment?: string;
preserveFragment?: boolean;
- preserveQueryParams?: boolean;
+ /** @deprecated */ preserveQueryParams?: boolean;
queryParams?: Params;
+ queryParamsHandling?: QueryParamsHandling;
relativeTo?: ActivatedRoute;
replaceUrl?: boolean;
skipLocationChange?: boolean;
@@ -209,7 +210,7 @@ export declare class Router {
url: string;
urlHandlingStrategy: UrlHandlingStrategy;
constructor(rootComponentType: Type, urlSerializer: UrlSerializer, outletMap: RouterOutletMap, location: Location, injector: Injector, loader: NgModuleFactoryLoader, compiler: Compiler, config: Routes);
- createUrlTree(commands: any[], {relativeTo, queryParams, fragment, preserveQueryParams, preserveFragment}?: NavigationExtras): UrlTree;
+ createUrlTree(commands: any[], {relativeTo, queryParams, fragment, preserveQueryParams, queryParamsHandling, preserveFragment}?: NavigationExtras): UrlTree;
dispose(): void;
initialNavigation(): void;
isActive(url: string | UrlTree, exact: boolean): boolean;
@@ -245,6 +246,7 @@ export declare class RouterLink {
queryParams: {
[k: string]: any;
};
+ queryParamsHandling: QueryParamsHandling;
replaceUrl: boolean;
routerLink: any[] | string;
skipLocationChange: boolean;
@@ -277,6 +279,7 @@ export declare class RouterLinkWithHref implements OnChanges, OnDestroy {
queryParams: {
[k: string]: any;
};
+ queryParamsHandling: QueryParamsHandling;
replaceUrl: boolean;
routerLink: any[] | string;
skipLocationChange: boolean;