feat(router): Add defaultQueryParamsHandling to router configuration (#57198)

This commit adds an option to specify the default value for
`queryParamsHandling` in `Router.createUrlTree` when another option is
not specified (or is `null|undefined`).

resolves #12664

PR Close #57198
This commit is contained in:
Andrew Scott
2024-07-30 12:26:22 -07:00
committed by Jessica Janiuk
parent 2a915d1912
commit 6c76c91e15
5 changed files with 62 additions and 6 deletions
+2 -1
View File
@@ -587,7 +587,7 @@ export function provideRouter(routes: Routes, ...features: RouterFeatures[]): En
export function provideRoutes(routes: Routes): Provider[];
// @public
export type QueryParamsHandling = 'merge' | 'preserve' | '';
export type QueryParamsHandling = 'merge' | 'preserve' | 'replace' | '';
// @public
export class RedirectCommand {
@@ -744,6 +744,7 @@ export const ROUTER_INITIALIZER: InjectionToken<(compRef: ComponentRef<any>) =>
// @public
export interface RouterConfigOptions {
canceledNavigationResolution?: 'replace' | 'computed';
defaultQueryParamsHandling?: QueryParamsHandling;
onSameUrlNavigation?: OnSameUrlNavigation;
paramsInheritanceStrategy?: 'emptyOnly' | 'always';
resolveNavigationPromiseOnError?: boolean;
+3 -2
View File
@@ -275,13 +275,14 @@ export type LoadChildren = LoadChildrenCallback;
* One of:
* - `"merge"` : Merge new parameters with current parameters.
* - `"preserve"` : Preserve current parameters.
* - `""` : Replace current parameters with new parameters. This is the default behavior.
* - `"replace"` : Replace current parameters with new parameters. This is the default behavior.
* - `""` : For legacy reasons, the same as `'replace'`.
*
* @see {@link UrlCreationOptions#queryParamsHandling}
* @see {@link RouterLink}
* @publicApi
*/
export type QueryParamsHandling = 'merge' | 'preserve' | '';
export type QueryParamsHandling = 'merge' | 'preserve' | 'replace' | '';
/**
* The type for the function that can be used to handle redirects when the path matches a `Route` config.
+1 -1
View File
@@ -452,7 +452,7 @@ export class Router {
navigationExtras;
const f = preserveFragment ? this.currentUrlTree.fragment : fragment;
let q: Params | null = null;
switch (queryParamsHandling) {
switch (queryParamsHandling ?? this.options.defaultQueryParamsHandling) {
case 'merge':
q = {...this.currentUrlTree.queryParams, ...queryParams};
break;
+15 -1
View File
@@ -8,7 +8,7 @@
import {InjectionToken} from '@angular/core';
import {OnSameUrlNavigation} from './models';
import {OnSameUrlNavigation, QueryParamsHandling} from './models';
/**
* Error handler that is invoked when a navigation error occurs.
@@ -113,6 +113,20 @@ export interface RouterConfigOptions {
*/
urlUpdateStrategy?: 'deferred' | 'eager';
/**
* The default strategy to use for handling query params in `Router.createUrlTree` when one is not provided.
*
* The `createUrlTree` method is used internally by `Router.navigate` and `RouterLink`.
* Note that `QueryParamsHandling` does not apply to `Router.navigateByUrl`.
*
* When neither the default nor the queryParamsHandling option is specified in the call to `createUrlTree`,
* the current parameters will be replaced by new parameters.
*
* @see {@link Router#createUrlTree}
* @see {@link QueryParamsHandling}
*/
defaultQueryParamsHandling?: QueryParamsHandling;
/**
* When `true`, the `Promise` will instead resolve with `false`, as it does with other failed
* navigations (for example, when guards are rejected).
+41 -1
View File
@@ -11,12 +11,13 @@ import {ComponentFixture, fakeAsync, TestBed, tick} from '@angular/core/testing'
import {By} from '@angular/platform-browser';
import {createUrlTreeFromSnapshot} from '../src/create_url_tree';
import {Routes} from '../src/models';
import {QueryParamsHandling, Routes} from '../src/models';
import {Router} from '../src/router';
import {RouterModule} from '../src/router_module';
import {ActivatedRoute, ActivatedRouteSnapshot} from '../src/router_state';
import {Params, PRIMARY_OUTLET} from '../src/shared';
import {DefaultUrlSerializer, UrlTree} from '../src/url_tree';
import {provideRouter, withRouterConfig} from '../src';
describe('createUrlTree', async () => {
const serializer = new DefaultUrlSerializer();
@@ -574,6 +575,45 @@ describe('createUrlTree', async () => {
});
});
describe('defaultQueryParamsHandling', () => {
async function setupRouter(defaultQueryParamsHandling: QueryParamsHandling): Promise<Router> {
TestBed.configureTestingModule({
providers: [
provideRouter(
[{path: '**', component: class {}}],
withRouterConfig({
defaultQueryParamsHandling,
}),
),
],
});
const router = TestBed.inject(Router);
await router.navigateByUrl('/initial?a=1');
return router;
}
it('can use "merge" as the default', async () => {
const router = await setupRouter('merge');
await router.navigate(['new'], {queryParams: {'b': 2}});
expect(router.url).toEqual('/new?a=1&b=2');
});
it('can use "perserve" as the default', async () => {
const router = await setupRouter('preserve');
await router.navigate(['new'], {queryParams: {'b': 2}});
expect(router.url).toEqual('/new?a=1');
});
it('can override the default by providing a new option', async () => {
const router = await setupRouter('preserve');
await router.navigate(['new'], {queryParams: {'b': 2}, queryParamsHandling: 'merge'});
expect(router.url).toEqual('/new?a=1&b=2');
await router.navigate(['replace'], {queryParamsHandling: 'replace'});
expect(router.url).toEqual('/replace');
});
});
async function createRoot(
tree: UrlTree,
commands: any[],