Victor Berchet a755b715ed feat(router): introduce ParamMap to access parameters
The Router use the type `Params` for all of:
- position parameters,
- matrix parameters,
- query parameters.

`Params` is defined as follow `type Params = {[key: string]: any}`

Because parameters can either have single or multiple values, the type should
actually be `type Params = {[key: string]: string | string[]}`.

The client code often assumes that parameters have single values, as in the
following exemple:

```
class MyComponent {
sessionId: Observable<string>;

constructor(private route: ActivatedRoute) {}

ngOnInit() {
    this.sessionId = this.route
      .queryParams
      .map(params => params['session_id'] || 'None');
}
}

```

The problem here is that `params['session_id']` could be `string` or `string[]`
but the error is not caught at build time because of the `any` type.

Fixing the type as describe above would break the build because `sessionId`
would becomes an `Observable<string | string[]>`.

However the client code knows if it expects a single or multiple values. By
using the new `ParamMap` interface the user code can decide when it needs a
single value (calling `ParamMap.get(): string`) or multiple values (calling
`ParamMap.getAll(): string[]`).

The above exemple should be rewritten as:

```
class MyComponent {
sessionId: Observable<string>;

constructor(private route: ActivatedRoute) {}

ngOnInit() {
    this.sessionId = this.route
      .queryParamMap
      .map(paramMap => paramMap.get('session_id') || 'None');
}
}

```

Added APIs:
- `interface ParamMap`,
- `ActivatedRoute.paramMap: ParamMap`,
- `ActivatedRoute.queryParamMap: ParamMap`,
- `ActivatedRouteSnapshot.paramMap: ParamMap`,
- `ActivatedRouteSnapshot.queryParamMap: ParamMap`,
- `UrlSegment.parameterMap: ParamMap`
2017-03-20 09:19:32 -07:00

132 lines
3.3 KiB
TypeScript

/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {Route, UrlMatchResult} from './config';
import {UrlSegment, UrlSegmentGroup} from './url_tree';
/**
* @whatItDoes Name of the primary outlet.
*
* @stable
*/
export const PRIMARY_OUTLET = 'primary';
/**
* A collection of parameters.
*
* @stable
*/
export type Params = {
[key: string]: any
};
/**
* Matrix and Query parameters.
*
* `ParamMap` makes it easier to work with parameters as they could have either a single value or
* multiple value. Because this should be know by the user calling `get` or `getAll` returns the
* correct type (either `string` or `string[]`).
*
* The API is inspired by the URLSearchParams interface.
* see https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams
*
* @stable
*/
export interface ParamMap {
has(name: string): boolean;
/**
* Return a single value for the given parameter name:
* - the value when the parameter has a single value,
* - the first value if the parameter has multiple values,
* - `null` when there is no such parameter.
*/
get(name: string): string|null;
/**
* Return an array of values for the given parameter name.
*
* If there is no such parameter, an empty array is returned.
*/
getAll(name: string): string[];
}
class ParamsAsMap implements ParamMap {
private params: Params;
constructor(params: Params) { this.params = params || {}; }
has(name: string): boolean { return this.params.hasOwnProperty(name); }
get(name: string): string|null {
if (this.has(name)) {
const v = this.params[name];
return Array.isArray(v) ? v[0] : v;
}
return null;
}
getAll(name: string): string[] {
if (this.has(name)) {
const v = this.params[name];
return Array.isArray(v) ? v : [v];
}
return [];
}
}
export function convertToParamMap(params: Params): ParamMap {
return new ParamsAsMap(params);
}
const NAVIGATION_CANCELING_ERROR = 'ngNavigationCancelingError';
export function navigationCancelingError(message: string) {
const error = Error('NavigationCancelingError: ' + message);
(error as any)[NAVIGATION_CANCELING_ERROR] = true;
return error;
}
export function isNavigationCancelingError(error: Error) {
return (error as any)[NAVIGATION_CANCELING_ERROR];
}
export function defaultUrlMatcher(
segments: UrlSegment[], segmentGroup: UrlSegmentGroup, route: Route): UrlMatchResult {
const path = route.path;
const parts = path.split('/');
const posParams: {[key: string]: UrlSegment} = {};
const consumed: UrlSegment[] = [];
let currentIndex = 0;
for (let i = 0; i < parts.length; ++i) {
if (currentIndex >= segments.length) return null;
const current = segments[currentIndex];
const p = parts[i];
const isPosParam = p.startsWith(':');
if (!isPosParam && p !== current.path) return null;
if (isPosParam) {
posParams[p.substring(1)] = current;
}
consumed.push(current);
currentIndex++;
}
if (route.pathMatch === 'full' &&
(segmentGroup.hasChildren() || currentIndex < segments.length)) {
return null;
} else {
return {consumed, posParams};
}
}