diff --git a/modules/@angular/router-deprecated/src/route_config/route_config_impl.ts b/modules/@angular/router-deprecated/src/route_config/route_config_impl.ts index 7ba05b2c28..dfa984ceb0 100644 --- a/modules/@angular/router-deprecated/src/route_config/route_config_impl.ts +++ b/modules/@angular/router-deprecated/src/route_config/route_config_impl.ts @@ -22,14 +22,17 @@ export abstract class AbstractRoute implements RouteDefinition { useAsDefault: boolean; path: string; regex: string; + regex_group_names: string[]; serializer: RegexSerializer; data: {[key: string]: any}; - constructor({name, useAsDefault, path, regex, serializer, data}: RouteDefinition) { + constructor({name, useAsDefault, path, regex, regex_group_names, serializer, + data}: RouteDefinition) { this.name = name; this.useAsDefault = useAsDefault; this.path = path; this.regex = regex; + this.regex_group_names = regex_group_names; this.serializer = serializer; this.data = data; } @@ -62,12 +65,14 @@ export class Route extends AbstractRoute { component: any; aux: string = null; - constructor({name, useAsDefault, path, regex, serializer, data, component}: RouteDefinition) { + constructor({name, useAsDefault, path, regex, regex_group_names, serializer, data, + component}: RouteDefinition) { super({ name: name, useAsDefault: useAsDefault, path: path, regex: regex, + regex_group_names: regex_group_names, serializer: serializer, data: data }); @@ -99,12 +104,14 @@ export class Route extends AbstractRoute { export class AuxRoute extends AbstractRoute { component: any; - constructor({name, useAsDefault, path, regex, serializer, data, component}: RouteDefinition) { + constructor({name, useAsDefault, path, regex, regex_group_names, serializer, data, + component}: RouteDefinition) { super({ name: name, useAsDefault: useAsDefault, path: path, regex: regex, + regex_group_names: regex_group_names, serializer: serializer, data: data }); @@ -141,12 +148,14 @@ export class AsyncRoute extends AbstractRoute { loader: () => Promise; aux: string = null; - constructor({name, useAsDefault, path, regex, serializer, data, loader}: RouteDefinition) { + constructor({name, useAsDefault, path, regex, regex_group_names, serializer, data, + loader}: RouteDefinition) { super({ name: name, useAsDefault: useAsDefault, path: path, regex: regex, + regex_group_names: regex_group_names, serializer: serializer, data: data }); @@ -179,12 +188,14 @@ export class AsyncRoute extends AbstractRoute { export class Redirect extends AbstractRoute { redirectTo: any[]; - constructor({name, useAsDefault, path, regex, serializer, data, redirectTo}: RouteDefinition) { + constructor({name, useAsDefault, path, regex, regex_group_names, serializer, data, + redirectTo}: RouteDefinition) { super({ name: name, useAsDefault: useAsDefault, path: path, regex: regex, + regex_group_names: regex_group_names, serializer: serializer, data: data }); diff --git a/modules/@angular/router-deprecated/src/route_definition.dart b/modules/@angular/router-deprecated/src/route_definition.dart index 2aa3f50894..343b466d2f 100644 --- a/modules/@angular/router-deprecated/src/route_definition.dart +++ b/modules/@angular/router-deprecated/src/route_definition.dart @@ -5,6 +5,7 @@ abstract class RouteDefinition { final String name; final bool useAsDefault; final String regex; + final List regex_group_names; final Function serializer; - const RouteDefinition({this.path, this.name, this.useAsDefault : false, this.regex, this.serializer}); + const RouteDefinition({this.path, this.name, this.useAsDefault : false, this.regex, this.regex_group_names, this.serializer}); } diff --git a/modules/@angular/router-deprecated/src/route_definition.ts b/modules/@angular/router-deprecated/src/route_definition.ts index 002fddd16d..cd8388dea9 100644 --- a/modules/@angular/router-deprecated/src/route_definition.ts +++ b/modules/@angular/router-deprecated/src/route_definition.ts @@ -16,6 +16,7 @@ export interface RouteDefinition { path?: string; aux?: string; regex?: string; + regex_group_names?: string[]; serializer?: RegexSerializer; component?: Type | ComponentDefinition; loader?: () => Promise; diff --git a/modules/@angular/router-deprecated/src/rules/route_paths/regex_route_path.ts b/modules/@angular/router-deprecated/src/rules/route_paths/regex_route_path.ts index 2717d97b7f..cc404476fd 100644 --- a/modules/@angular/router-deprecated/src/rules/route_paths/regex_route_path.ts +++ b/modules/@angular/router-deprecated/src/rules/route_paths/regex_route_path.ts @@ -1,10 +1,21 @@ import {RegExpWrapper, RegExpMatcherWrapper, isBlank} from '../../../src/facade/lang'; +import {BaseException} from '@angular/core'; import {Url} from '../../url_parser'; import {RoutePath, GeneratedUrl, MatchedUrl} from './route_path'; export interface RegexSerializer { (params: {[key: string]: any}): GeneratedUrl; } +function computeNumberOfRegexGroups(regex: string): number { + // cleverly compute regex groups by appending an alternative empty matching + // pattern and match against an empty string, the resulting match still + // receives all the other groups + var test_regex = RegExpWrapper.create(regex + "|"); + var matcher = RegExpWrapper.matcher(test_regex, ''); + var match = RegExpMatcherWrapper.next(matcher); + return match.length; +} + export class RegexRoutePath implements RoutePath { public hash: string; public terminal: boolean = true; @@ -12,9 +23,19 @@ export class RegexRoutePath implements RoutePath { private _regex: RegExp; - constructor(private _reString: string, private _serializer: RegexSerializer) { + constructor(private _reString: string, private _serializer: RegexSerializer, + private _groupNames?: Array) { this.hash = this._reString; this._regex = RegExpWrapper.create(this._reString); + if (this._groupNames != null) { + var groups = computeNumberOfRegexGroups(this._reString); + if (groups != _groupNames.length) { + throw new BaseException( + `Regex group names [${this._groupNames.join(',')}] must contain names for \ +each matching group and a name for the complete match as its first element of regex \ +'${this._reString}'. ${groups} group names are expected.`); + } + } } matchUrl(url: Url): MatchedUrl { @@ -28,7 +49,7 @@ export class RegexRoutePath implements RoutePath { } for (let i = 0; i < match.length; i += 1) { - params[i.toString()] = match[i]; + params[this._groupNames != null ? this._groupNames[i] : i.toString()] = match[i]; } return new MatchedUrl(urlPath, [], params, [], null); diff --git a/modules/@angular/router-deprecated/src/rules/rule_set.ts b/modules/@angular/router-deprecated/src/rules/rule_set.ts index ff0159dc2e..28bee97b71 100644 --- a/modules/@angular/router-deprecated/src/rules/rule_set.ts +++ b/modules/@angular/router-deprecated/src/rules/rule_set.ts @@ -169,7 +169,7 @@ export class RuleSet { private _getRoutePath(config: RouteDefinition): RoutePath { if (isPresent(config.regex)) { if (isFunction(config.serializer)) { - return new RegexRoutePath(config.regex, config.serializer); + return new RegexRoutePath(config.regex, config.serializer, config.regex_group_names); } else { throw new BaseException( `Route provides a regex property, '${config.regex}', but no serializer property`); diff --git a/modules/@angular/router-deprecated/test/rules/route_paths/regex_route_param_spec.ts b/modules/@angular/router-deprecated/test/rules/route_paths/regex_route_param_spec.ts index 433a7979c4..9db5bfcbf8 100644 --- a/modules/@angular/router-deprecated/test/rules/route_paths/regex_route_param_spec.ts +++ b/modules/@angular/router-deprecated/test/rules/route_paths/regex_route_param_spec.ts @@ -45,5 +45,21 @@ export function main() { var url = rec.generateUrl(params); expect(url.urlPath).toEqual('/a/one/b/two'); }); + + it('should raise an error when the number of parameters doesnt match', () => { + expect(() => {new RegexRoutePath('^a-([0-9]+)-b-([0-9]+)$', emptySerializer, + ['complete_match', 'a'])}) + .toThrowError(`Regex group names [complete_match,a] must contain names for each matching \ +group and a name for the complete match as its first element of regex '^a-([0-9]+)-b-([0-9]+)$'. \ +3 group names are expected.`); + }); + + it('should take group naming into account when passing params', () => { + var rec = new RegexRoutePath('^a-([0-9]+)-b-([0-9]+)$', emptySerializer, + ['complete_match', 'a', 'b']); + var url = parser.parse('a-123-b-345'); + var match = rec.matchUrl(url); + expect(match.allParams).toEqual({'complete_match': 'a-123-b-345', 'a': '123', 'b': '345'}); + }); }); } diff --git a/modules/@angular/router-deprecated/test/rules/rule_set_spec.ts b/modules/@angular/router-deprecated/test/rules/rule_set_spec.ts index fd579a50a7..893697f43c 100644 --- a/modules/@angular/router-deprecated/test/rules/rule_set_spec.ts +++ b/modules/@angular/router-deprecated/test/rules/rule_set_spec.ts @@ -83,6 +83,26 @@ export function main() { }); })); + it('should recognize a regex with named_groups', inject([AsyncTestCompleter], (async) => { + function emptySerializer(params): GeneratedUrl { return new GeneratedUrl('', {}); } + + recognizer.config(new Route({ + regex: '^(.+)/(.+)$', + regex_group_names: ['cc', 'a', 'b'], + serializer: emptySerializer, + component: DummyCmpA + })); + recognize(recognizer, '/first/second') + .then((solutions: RouteMatch[]) => { + expect(solutions.length).toBe(1); + expect(getComponentType(solutions[0])).toEqual(DummyCmpA); + expect(getParams(solutions[0])) + .toEqual({'cc': 'first/second', 'a': 'first', 'b': 'second'}); + async.done(); + }); + })); + + it('should throw when given two routes that start with the same static segment', () => { recognizer.config(new Route({path: '/hello', component: DummyCmpA}));