Skip to content

Commit 72bb2d8

Browse files
BREAKING CHANGE: Built-in string parameter type no longer encodes slashes as ~2F nor tildes as ~~
Previously, the `string` parameter type pre-encoded tilde chars (`~`) as two tilde chars (`~~`) and slashes (`/`) as `~2F`. Now, the `string` parameter type does not pre-encode slashes nor tildes. If you rely on the previous encoding, create a custom parameter type that implements the behavior: ```js urlMatcherFactory.type('tildes', { encode: (val: any) => val != null ? val.toString().replace(/(~|\/)/g, m => ({ '~': '~~', '/': '~2F' }[m])) : val; decode: (val: string) => val != null ? val.toString().replace(/(~~|~2F)/g, m => ({ '~~': '~', '~2F': '/' }[m])) : val; pattern: /[^/]*/ }); ``` BREAKING CHANGE: Path/Query parameters no longer default to `string` param type Previously, if a url parameter's type was not specified (in either the path or query), it defaulted to the `string` type. Now, path parameters default to the new `path` type and query parameters default to the new `query` type. **In Angular 1 only**, the new `path` parameter type retains the old behavior of pre-encoding `~` to `~~` and `/` to `~2F` feat(params): Add `path` and `query` param types docs(params): Add docs for built-in param types docs(params): Document `raw` parameters Closes angular-ui/ui-router#2452 Closes #14
1 parent bab3ad7 commit 72bb2d8

File tree

7 files changed

+427
-180
lines changed

7 files changed

+427
-180
lines changed

src/params/interface.ts

+110-51
Original file line numberDiff line numberDiff line change
@@ -24,42 +24,49 @@ export interface RawParams {
2424
export type ParamsOrArray = (RawParams|RawParams[]);
2525

2626
/**
27-
* Inside a [[StateDeclaration.params]]:
27+
* Configuration for a single Parameter
2828
*
29-
* A ParamDeclaration object defines how a single State Parameter should work.
30-
*
31-
* @example
32-
* ```
29+
* In a [[StateDeclaration.params]], each `ParamDeclaration`
30+
* defines how a single State Parameter should work.
3331
*
32+
* #### Example:
33+
* ```js
3434
* var mystate = {
3535
* template: '<div ui-view/>',
3636
* controller: function() {}
37-
* url: '/mystate/:param1',
37+
* url: '/mystate/:start?{count:int}',
3838
* params: {
39-
* param1: "index", // <-- Default value for 'param1'
40-
* // (shorthand ParamDeclaration)
39+
* start: { // <-- ParamDeclaration for `start`
40+
* type: 'date',
41+
* value: new Date(), // <-- Default value
42+
* squash: true,
43+
* },
4144
*
4245
* nonUrlParam: { // <-- ParamDeclaration for 'nonUrlParam'
4346
* type: "int",
4447
* array: true,
4548
* value: []
46-
* }
49+
* },
50+
*
51+
* count: 0, // <-- Default value for 'param1'
52+
* // (shorthand ParamDeclaration.value)
4753
* }
4854
* }
4955
* ```
5056
*/
5157
export interface ParamDeclaration {
5258
/**
53-
* A property of [[ParamDeclaration]]:
59+
* The default value for this parameter.
5460
*
55-
* Specifies the default value for this parameter. This implicitly sets this parameter as optional.
61+
* Specifies the default value for this parameter.
62+
* This implicitly sets this parameter as optional.
5663
*
5764
* When UI-Router routes to a state and no value is specified for this parameter in the URL or transition,
58-
* the default value will be used instead. If value is a function, it will be injected and invoked, and the
59-
* return value used.
65+
* the default value will be used instead.
66+
* If value is a function, it will be injected and invoked, and the return value used.
6067
*
61-
* Note: `value: undefined` is treated as though no default value was specified, while `value: null` is treated
62-
* as "the default value is null".
68+
* Note: `value: undefined` is treated as though **no default value was specified**, while `value: null` is treated
69+
* as **"the default value is null"**.
6370
*
6471
* ```
6572
* // define default values for param1 and param2
@@ -74,43 +81,48 @@ export interface ParamDeclaration {
7481
* ```
7582
*
7683
* ### Shorthand Declaration
84+
*
7785
* If you only want to set the default value of the parameter, you may use a shorthand syntax.
7886
* In the params map, instead mapping the param name to a full parameter configuration object, simply set map it
7987
* to the default parameter value, e.g.:
8088
* ```
81-
* // define a parameter's default value
89+
* // Normal (non-shorthand) default value syntax
8290
* params: {
8391
* param1: {
8492
* value: "defaultValue"
8593
* },
8694
* param2: {
87-
* value: "param2Default;
95+
* value: "param2Default"
8896
* }
8997
* }
9098
*
91-
* // shorthand default values
99+
* // Shorthand default value syntax
92100
* params: {
93101
* param1: "defaultValue",
94102
* param2: "param2Default"
95103
* }
96104
* ```
97105
*
98-
* This defines a default value for the parameter. If the parameter value is `undefined`, this value will be used instead
106+
* This defines a default value for the parameter.
107+
* If a parameter value is `undefined`, this default value will be used instead
99108
*/
100109
value?: any;
101110

102111
/**
103-
* A property of [[ParamDeclaration]]:
112+
* The parameter's type
104113
*
105114
* Specifies the [[ParamType]] of the parameter.
115+
* Parameter types can be used to customize the encoding/decoding of parameter values.
116+
*
117+
* Set this property to the name of parameter's type.
118+
* The type may be either one of the built in types, or a custom type that has been registered with the [[UrlMatcherFactory]].
106119
*
107-
* Set this property to the name of parameter's type. The type may be either one of the
108-
* built in types, or a custom type that has been registered with the [[$urlMatcherFactory]]
120+
* See [[ParamTypes]] for the list of built in types.
109121
*/
110122
type: (string|ParamType);
111123

112124
/**
113-
* A property of [[ParamDeclaration]]:
125+
* The parameter's `array` mode
114126
*
115127
* Explicitly specifies the array mode of a URL parameter
116128
*
@@ -124,9 +136,8 @@ export interface ParamDeclaration {
124136
* If you specified a [[type]] for the parameter, the value will be treated as an array
125137
* of the specified [[ParamType]].
126138
*
127-
* @example
128-
* ```
129-
*
139+
* #### Example:
140+
* ```js
130141
* {
131142
* name: 'foo',
132143
* url: '/foo/{arrayParam:int}`,
@@ -144,24 +155,24 @@ export interface ParamDeclaration {
144155
* @default `true` if the parameter name ends in `[]`, such as `url: '/foo/{implicitArrayParam:int[]}'`
145156
*/
146157
array: boolean;
158+
147159
/**
148-
* A property of [[ParamDeclaration]]:
160+
* Squash mode: omit default parameter values in URL
149161
*
150162
* Configures how a default parameter value is represented in the URL when the current parameter value
151163
* is the same as the default value.
152164
*
153165
* There are three squash settings:
154166
*
155167
* - `false`: The parameter's default value is not squashed. It is encoded and included in the URL
156-
* - `true`: The parameter's default value is omitted from the URL. If the parameter is preceeded
157-
* and followed by slashes in the state's url declaration, then one of those slashes are omitted.
168+
* - `true`: The parameter's default value is omitted from the URL.
169+
* If the parameter is preceeded and followed by slashes in the state's url declaration, then one of those slashes are omitted.
158170
* This can allow for cleaner looking URLs.
159171
* - `"&lt;arbitrary string&gt;"`: The parameter's default value is replaced with an arbitrary
160172
* placeholder of your choice.
161173
*
162-
* @example
163-
* ```
164-
*
174+
* #### Example:
175+
* ```js
165176
* {
166177
* name: 'mystate',
167178
* url: '/mystate/:myparam',
@@ -178,9 +189,8 @@ export interface ParamDeclaration {
178189
* $state.go('mystate', { myparam: 'someOtherValue' });
179190
* ```
180191
*
181-
* @example
182-
* ```
183-
*
192+
* #### Example:
193+
* ```js
184194
* {
185195
* name: 'mystate2',
186196
* url: '/mystate2/:myparam2',
@@ -200,6 +210,7 @@ export interface ParamDeclaration {
200210
* If squash is not set, it uses the configured default squash policy. (See [[defaultSquashPolicy]]())
201211
*/
202212
squash: (boolean|string);
213+
203214
/**
204215
* @internalapi
205216
*
@@ -218,31 +229,69 @@ export interface ParamDeclaration {
218229
* ```
219230
*/
220231
replace: Replace[];
232+
221233
/**
222234
* @hidden
223235
* @internalapi
224236
*
225237
* This is not part of the declaration; it is a calculated value depending on if a default value was specified or not.
226238
*/
227239
isOptional: boolean;
240+
228241
/**
229242
* Dynamic flag
230243
*
231244
* When `dynamic` is `true`, changes to the parameter value will not cause the state to be entered/exited.
245+
* The resolves will not be re-fetched, nor will views be reloaded.
232246
*
233-
* The resolves will not be re-fetched, nor will views be recreated.
247+
* Normally, if a parameter value changes, the state which declared that the parameter will be reloaded (entered/exited).
248+
* When a parameter is `dynamic`, a transition still occurs, but it does not cause the state to exit/enter.
249+
*
250+
* This can be useful to build UI where the component updates itself when the param values change.
251+
* A common scenario where this is useful is searching/paging/sorting.
234252
*/
235253
dynamic: boolean;
254+
255+
/**
256+
* Disables url-encoding of parameter values
257+
*
258+
* When `true`, parameter values are not url-encoded.
259+
* This is commonly used to allow "slug" urls, with a parameter value including non-semantic slashes.
260+
*
261+
* #### Example:
262+
* ```js
263+
* url: '/product/:slug',
264+
* params: {
265+
* slug: { type: 'string', raw: true }
266+
* }
267+
* ```
268+
*
269+
* This allows a URL parameter of `{ slug: 'camping/tents/awesome_tent' }`
270+
* to serialize to `/product/camping/tents/awesome_tent`
271+
* instead of `/product/camping%2Ftents%2Fawesome_tent`.
272+
*
273+
* ### Decoding warning
274+
*
275+
* The decoding behavior of raw parameters is not defined.
276+
* For example, given a url template such as `/:raw1/:raw2`
277+
* the url `/foo/bar/baz/qux/`, there is no way to determine which slashes belong to which params.
278+
*
279+
* It's generally safe to use a raw parameter at the end of a path, like '/product/:slug'.
280+
* However, beware of the characters you allow in your raw parameter values.
281+
* Avoid unencoded characters that could disrupt normal URL parsing, such as `?` and `#`.
282+
*/
283+
raw: boolean;
236284
}
237285

238286
export interface Replace {
239287
from: string;
240288
to: string;
241289
}
242290

243-
244291
/**
245-
* Definition for a custom [[ParamType]]
292+
* Describes a custom [[ParamType]]
293+
*
294+
* See: [[UrlMatcherFactory.type]]
246295
*
247296
* A developer can create a custom parameter type definition to customize the encoding and decoding of parameter values.
248297
* The definition should implement all the methods of this interface.
@@ -254,11 +303,10 @@ export interface Replace {
254303
* - date
255304
* - array of <integer/date/string>
256305
* - custom object
257-
* - some custom string representation
306+
* - some internal string representation
258307
*
259308
* Typed parameter definitions control how parameter values are encoded (to the URL) and decoded (from the URL).
260-
* UI-Router always provides the decoded parameter values to the user from methods such as [[Transition.params]].
261-
*
309+
* UI-Router always provides the decoded parameter values to the user (from methods such as [[Transition.params]])).
262310
*
263311
* For example, if a state has a url of `/foo/{fooId:int}` (the `fooId` parameter is of the `int` ParamType)
264312
* and if the browser is at `/foo/123`, then the 123 is parsed as an integer:
@@ -358,13 +406,17 @@ export interface ParamTypeDefinition {
358406
* If your custom type encodes the parameter to a specific type, check for that type here.
359407
* For example, if your custom type decodes the URL parameter value as an array of ints, return true if the
360408
* input is an array of ints:
361-
* `(val) => Array.isArray(val) && array.reduce((acc, x) => acc && parseInt(val, 10) === val, true)`.
409+
*
410+
* ```
411+
* is: (val) => Array.isArray(val) && array.reduce((acc, x) => acc && parseInt(val, 10) === val, true)
412+
* ```
413+
*
362414
* If your type decodes the URL parameter value to a custom string, check that the string matches
363415
* the pattern (don't use an arrow fn if you need `this`): `function (val) { return !!this.pattern.exec(val) }`
364416
*
365417
* Note: This method is _not used to check if the URL matches_.
366-
* It's used to check if a _decoded value is this type_.
367-
* Use [[pattern]] to check the URL.
418+
* It's used to check if a _decoded value *is* this type_.
419+
* Use [[pattern]] to check the encoded value in the URL.
368420
*
369421
* @param val The value to check.
370422
* @param key If the type check is happening in the context of a specific [[UrlMatcher]] object,
@@ -377,11 +429,14 @@ export interface ParamTypeDefinition {
377429
/**
378430
* Encodes a custom/native type value to a string that can be embedded in a URL.
379431
*
380-
* Note that the return value does *not* need to be URL-safe (i.e. passed through `encodeURIComponent()`), it
381-
* only needs to be a representation of `val` that has been encoded as a string.
432+
* Note that the return value does *not* need to be URL-safe (i.e. passed through `encodeURIComponent()`).
433+
* It only needs to be a representation of `val` that has been encoded as a string.
382434
*
383-
* For example, if your type decodes to an array of ints, then encode the array of ints as a string here:
384-
* `(intarray) => intarray.join("-")`
435+
* For example, if your custom type decodes to an array of ints, then encode the array of ints to a string here:
436+
*
437+
* ```js
438+
* encode: (intarray) => intarray.join("-")
439+
* ```
385440
*
386441
* Note: in general, [[encode]] and [[decode]] should be symmetrical. That is, `encode(decode(str)) === str`
387442
*
@@ -395,7 +450,9 @@ export interface ParamTypeDefinition {
395450
* Decodes a parameter value string (from URL string or transition param) to a custom/native value.
396451
*
397452
* For example, if your type decodes to an array of ints, then decode the string as an array of ints here:
398-
* `(str) => str.split("-").map(str => parseInt(str, 10))`
453+
* ```js
454+
* decode: (str) => str.split("-").map(str => parseInt(str, 10))
455+
* ```
399456
*
400457
* Note: in general, [[encode]] and [[decode]] should be symmetrical. That is, `encode(decode(str)) === str`
401458
*
@@ -409,7 +466,9 @@ export interface ParamTypeDefinition {
409466
* Determines whether two decoded values are equivalent.
410467
*
411468
* For example, if your type decodes to an array of ints, then check if the arrays are equal:
412-
* `(a, b) => a.length === b.length && a.reduce((acc, x, idx) => acc && x === b[idx], true)`
469+
* ```js
470+
* equals: (a, b) => a.length === b.length && a.reduce((acc, x, idx) => acc && x === b[idx], true)
471+
* ```
413472
*
414473
* @param a A value to compare against.
415474
* @param b A value to compare against.
@@ -420,7 +479,7 @@ export interface ParamTypeDefinition {
420479
/**
421480
* A regular expression that matches the encoded parameter type
422481
*
423-
* This regular expression is used to match the parameter type in the URL.
482+
* This regular expression is used to match an encoded parameter value **in the URL**.
424483
*
425484
* For example, if your type encodes as a dash-separated numbers, match that here:
426485
* `new RegExp("[0-9]+(?:-[0-9]+)*")`.

src/params/param.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,12 @@ function getType(cfg: ParamDeclaration, urlType: ParamType, location: DefType, i
2828
if (cfg.type && urlType && urlType.name !== 'string') throw new Error(`Param '${id}' has two type configurations.`);
2929
if (cfg.type && urlType && urlType.name === 'string' && paramTypes.type(cfg.type as string)) return paramTypes.type(cfg.type as string);
3030
if (urlType) return urlType;
31-
if (!cfg.type) return (location === DefType.CONFIG ? paramTypes.type("any") : paramTypes.type("string"));
31+
if (!cfg.type) {
32+
let type = location === DefType.CONFIG ? "any" :
33+
location === DefType.PATH ? "path" :
34+
location === DefType.SEARCH ? "query" : "string";
35+
return paramTypes.type(type);
36+
}
3237
return cfg.type instanceof ParamType ? cfg.type : paramTypes.type(cfg.type as string);
3338
}
3439

0 commit comments

Comments
 (0)