UNPKG

23 kBJavaScriptView Raw
1"use strict";
2var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3 var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4 if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5 else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6 return c > 3 && r && Object.defineProperty(target, key, r), r;
7};
8var __metadata = (this && this.__metadata) || function (k, v) {
9 if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10};
11var __param = (this && this.__param) || function (paramIndex, decorator) {
12 return function (target, key) { decorator(target, key, paramIndex); }
13};
14var async_1 = require('../src/facade/async');
15var collection_1 = require('../src/facade/collection');
16var lang_1 = require('../src/facade/lang');
17var exceptions_1 = require('../src/facade/exceptions');
18var core_1 = require('@angular/core');
19var route_config_impl_1 = require('./route_config/route_config_impl');
20var rules_1 = require('./rules/rules');
21var rule_set_1 = require('./rules/rule_set');
22var instruction_1 = require('./instruction');
23var route_config_normalizer_1 = require('./route_config/route_config_normalizer');
24var url_parser_1 = require('./url_parser');
25var core_private_1 = require('../core_private');
26var _resolveToNull = async_1.PromiseWrapper.resolve(null);
27// A LinkItemArray is an array, which describes a set of routes
28// The items in the array are found in groups:
29// - the first item is the name of the route
30// - the next items are:
31// - an object containing parameters
32// - or an array describing an aux route
33// export type LinkRouteItem = string | Object;
34// export type LinkItem = LinkRouteItem | Array<LinkRouteItem>;
35// export type LinkItemArray = Array<LinkItem>;
36/**
37 * Token used to bind the component with the top-level {@link RouteConfig}s for the
38 * application.
39 *
40 * ### Example ([live demo](http://plnkr.co/edit/iRUP8B5OUbxCWQ3AcIDm))
41 *
42 * ```
43 * import {Component} from '@angular/core';
44 * import {
45 * ROUTER_DIRECTIVES,
46 * ROUTER_PROVIDERS,
47 * RouteConfig
48 * } from '@angular/router-deprecated';
49 *
50 * @Component({directives: [ROUTER_DIRECTIVES]})
51 * @RouteConfig([
52 * {...},
53 * ])
54 * class AppCmp {
55 * // ...
56 * }
57 *
58 * bootstrap(AppCmp, [ROUTER_PROVIDERS]);
59 * ```
60 */
61exports.ROUTER_PRIMARY_COMPONENT =
62/*@ts2dart_const*/ new core_1.OpaqueToken('RouterPrimaryComponent');
63/**
64 * The RouteRegistry holds route configurations for each component in an Angular app.
65 * It is responsible for creating Instructions from URLs, and generating URLs based on route and
66 * parameters.
67 */
68var RouteRegistry = (function () {
69 function RouteRegistry(_rootComponent) {
70 this._rootComponent = _rootComponent;
71 this._rules = new collection_1.Map();
72 }
73 /**
74 * Given a component and a configuration object, add the route to this registry
75 */
76 RouteRegistry.prototype.config = function (parentComponent, config) {
77 config = route_config_normalizer_1.normalizeRouteConfig(config, this);
78 // this is here because Dart type guard reasons
79 if (config instanceof route_config_impl_1.Route) {
80 route_config_normalizer_1.assertComponentExists(config.component, config.path);
81 }
82 else if (config instanceof route_config_impl_1.AuxRoute) {
83 route_config_normalizer_1.assertComponentExists(config.component, config.path);
84 }
85 var rules = this._rules.get(parentComponent);
86 if (lang_1.isBlank(rules)) {
87 rules = new rule_set_1.RuleSet();
88 this._rules.set(parentComponent, rules);
89 }
90 var terminal = rules.config(config);
91 if (config instanceof route_config_impl_1.Route) {
92 if (terminal) {
93 assertTerminalComponent(config.component, config.path);
94 }
95 else {
96 this.configFromComponent(config.component);
97 }
98 }
99 };
100 /**
101 * Reads the annotations of a component and configures the registry based on them
102 */
103 RouteRegistry.prototype.configFromComponent = function (component) {
104 var _this = this;
105 if (!lang_1.isType(component)) {
106 return;
107 }
108 // Don't read the annotations from a type more than once –
109 // this prevents an infinite loop if a component routes recursively.
110 if (this._rules.has(component)) {
111 return;
112 }
113 var annotations = core_private_1.reflector.annotations(component);
114 if (lang_1.isPresent(annotations)) {
115 for (var i = 0; i < annotations.length; i++) {
116 var annotation = annotations[i];
117 if (annotation instanceof route_config_impl_1.RouteConfig) {
118 var routeCfgs = annotation.configs;
119 routeCfgs.forEach(function (config) { return _this.config(component, config); });
120 }
121 }
122 }
123 };
124 /**
125 * Given a URL and a parent component, return the most specific instruction for navigating
126 * the application into the state specified by the url
127 */
128 RouteRegistry.prototype.recognize = function (url, ancestorInstructions) {
129 var parsedUrl = url_parser_1.parser.parse(url);
130 return this._recognize(parsedUrl, []);
131 };
132 /**
133 * Recognizes all parent-child routes, but creates unresolved auxiliary routes
134 */
135 RouteRegistry.prototype._recognize = function (parsedUrl, ancestorInstructions, _aux) {
136 var _this = this;
137 if (_aux === void 0) { _aux = false; }
138 var parentInstruction = collection_1.ListWrapper.last(ancestorInstructions);
139 var parentComponent = lang_1.isPresent(parentInstruction) ? parentInstruction.component.componentType :
140 this._rootComponent;
141 var rules = this._rules.get(parentComponent);
142 if (lang_1.isBlank(rules)) {
143 return _resolveToNull;
144 }
145 // Matches some beginning part of the given URL
146 var possibleMatches = _aux ? rules.recognizeAuxiliary(parsedUrl) : rules.recognize(parsedUrl);
147 var matchPromises = possibleMatches.map(function (candidate) { return candidate.then(function (candidate) {
148 if (candidate instanceof rules_1.PathMatch) {
149 var auxParentInstructions = ancestorInstructions.length > 0 ? [collection_1.ListWrapper.last(ancestorInstructions)] : [];
150 var auxInstructions = _this._auxRoutesToUnresolved(candidate.remainingAux, auxParentInstructions);
151 var instruction = new instruction_1.ResolvedInstruction(candidate.instruction, null, auxInstructions);
152 if (lang_1.isBlank(candidate.instruction) || candidate.instruction.terminal) {
153 return instruction;
154 }
155 var newAncestorInstructions = ancestorInstructions.concat([instruction]);
156 return _this._recognize(candidate.remaining, newAncestorInstructions)
157 .then(function (childInstruction) {
158 if (lang_1.isBlank(childInstruction)) {
159 return null;
160 }
161 // redirect instructions are already absolute
162 if (childInstruction instanceof instruction_1.RedirectInstruction) {
163 return childInstruction;
164 }
165 instruction.child = childInstruction;
166 return instruction;
167 });
168 }
169 if (candidate instanceof rules_1.RedirectMatch) {
170 var instruction = _this.generate(candidate.redirectTo, ancestorInstructions.concat([null]));
171 return new instruction_1.RedirectInstruction(instruction.component, instruction.child, instruction.auxInstruction, candidate.specificity);
172 }
173 }); });
174 if ((lang_1.isBlank(parsedUrl) || parsedUrl.path == '') && possibleMatches.length == 0) {
175 return async_1.PromiseWrapper.resolve(this.generateDefault(parentComponent));
176 }
177 return async_1.PromiseWrapper.all(matchPromises).then(mostSpecific);
178 };
179 RouteRegistry.prototype._auxRoutesToUnresolved = function (auxRoutes, parentInstructions) {
180 var _this = this;
181 var unresolvedAuxInstructions = {};
182 auxRoutes.forEach(function (auxUrl) {
183 unresolvedAuxInstructions[auxUrl.path] = new instruction_1.UnresolvedInstruction(function () { return _this._recognize(auxUrl, parentInstructions, true); });
184 });
185 return unresolvedAuxInstructions;
186 };
187 /**
188 * Given a normalized list with component names and params like: `['user', {id: 3 }]`
189 * generates a url with a leading slash relative to the provided `parentComponent`.
190 *
191 * If the optional param `_aux` is `true`, then we generate starting at an auxiliary
192 * route boundary.
193 */
194 RouteRegistry.prototype.generate = function (linkParams, ancestorInstructions, _aux) {
195 if (_aux === void 0) { _aux = false; }
196 var params = splitAndFlattenLinkParams(linkParams);
197 var prevInstruction;
198 // The first segment should be either '.' (generate from parent) or '' (generate from root).
199 // When we normalize above, we strip all the slashes, './' becomes '.' and '/' becomes ''.
200 if (collection_1.ListWrapper.first(params) == '') {
201 params.shift();
202 prevInstruction = collection_1.ListWrapper.first(ancestorInstructions);
203 ancestorInstructions = [];
204 }
205 else {
206 prevInstruction = ancestorInstructions.length > 0 ? ancestorInstructions.pop() : null;
207 if (collection_1.ListWrapper.first(params) == '.') {
208 params.shift();
209 }
210 else if (collection_1.ListWrapper.first(params) == '..') {
211 while (collection_1.ListWrapper.first(params) == '..') {
212 if (ancestorInstructions.length <= 0) {
213 throw new exceptions_1.BaseException("Link \"" + collection_1.ListWrapper.toJSON(linkParams) + "\" has too many \"../\" segments.");
214 }
215 prevInstruction = ancestorInstructions.pop();
216 params = collection_1.ListWrapper.slice(params, 1);
217 }
218 }
219 else {
220 // we must only peak at the link param, and not consume it
221 var routeName = collection_1.ListWrapper.first(params);
222 var parentComponentType = this._rootComponent;
223 var grandparentComponentType = null;
224 if (ancestorInstructions.length > 1) {
225 var parentComponentInstruction = ancestorInstructions[ancestorInstructions.length - 1];
226 var grandComponentInstruction = ancestorInstructions[ancestorInstructions.length - 2];
227 parentComponentType = parentComponentInstruction.component.componentType;
228 grandparentComponentType = grandComponentInstruction.component.componentType;
229 }
230 else if (ancestorInstructions.length == 1) {
231 parentComponentType = ancestorInstructions[0].component.componentType;
232 grandparentComponentType = this._rootComponent;
233 }
234 // For a link with no leading `./`, `/`, or `../`, we look for a sibling and child.
235 // If both exist, we throw. Otherwise, we prefer whichever exists.
236 var childRouteExists = this.hasRoute(routeName, parentComponentType);
237 var parentRouteExists = lang_1.isPresent(grandparentComponentType) &&
238 this.hasRoute(routeName, grandparentComponentType);
239 if (parentRouteExists && childRouteExists) {
240 var msg = "Link \"" + collection_1.ListWrapper.toJSON(linkParams) + "\" is ambiguous, use \"./\" or \"../\" to disambiguate.";
241 throw new exceptions_1.BaseException(msg);
242 }
243 if (parentRouteExists) {
244 prevInstruction = ancestorInstructions.pop();
245 }
246 }
247 }
248 if (params[params.length - 1] == '') {
249 params.pop();
250 }
251 if (params.length > 0 && params[0] == '') {
252 params.shift();
253 }
254 if (params.length < 1) {
255 var msg = "Link \"" + collection_1.ListWrapper.toJSON(linkParams) + "\" must include a route name.";
256 throw new exceptions_1.BaseException(msg);
257 }
258 var generatedInstruction = this._generate(params, ancestorInstructions, prevInstruction, _aux, linkParams);
259 // we don't clone the first (root) element
260 for (var i = ancestorInstructions.length - 1; i >= 0; i--) {
261 var ancestorInstruction = ancestorInstructions[i];
262 if (lang_1.isBlank(ancestorInstruction)) {
263 break;
264 }
265 generatedInstruction = ancestorInstruction.replaceChild(generatedInstruction);
266 }
267 return generatedInstruction;
268 };
269 /*
270 * Internal helper that does not make any assertions about the beginning of the link DSL.
271 * `ancestorInstructions` are parents that will be cloned.
272 * `prevInstruction` is the existing instruction that would be replaced, but which might have
273 * aux routes that need to be cloned.
274 */
275 RouteRegistry.prototype._generate = function (linkParams, ancestorInstructions, prevInstruction, _aux, _originalLink) {
276 var _this = this;
277 if (_aux === void 0) { _aux = false; }
278 var parentComponentType = this._rootComponent;
279 var componentInstruction = null;
280 var auxInstructions = {};
281 var parentInstruction = collection_1.ListWrapper.last(ancestorInstructions);
282 if (lang_1.isPresent(parentInstruction) && lang_1.isPresent(parentInstruction.component)) {
283 parentComponentType = parentInstruction.component.componentType;
284 }
285 if (linkParams.length == 0) {
286 var defaultInstruction = this.generateDefault(parentComponentType);
287 if (lang_1.isBlank(defaultInstruction)) {
288 throw new exceptions_1.BaseException("Link \"" + collection_1.ListWrapper.toJSON(_originalLink) + "\" does not resolve to a terminal instruction.");
289 }
290 return defaultInstruction;
291 }
292 // for non-aux routes, we want to reuse the predecessor's existing primary and aux routes
293 // and only override routes for which the given link DSL provides
294 if (lang_1.isPresent(prevInstruction) && !_aux) {
295 auxInstructions = collection_1.StringMapWrapper.merge(prevInstruction.auxInstruction, auxInstructions);
296 componentInstruction = prevInstruction.component;
297 }
298 var rules = this._rules.get(parentComponentType);
299 if (lang_1.isBlank(rules)) {
300 throw new exceptions_1.BaseException("Component \"" + lang_1.getTypeNameForDebugging(parentComponentType) + "\" has no route config.");
301 }
302 var linkParamIndex = 0;
303 var routeParams = {};
304 // first, recognize the primary route if one is provided
305 if (linkParamIndex < linkParams.length && lang_1.isString(linkParams[linkParamIndex])) {
306 var routeName = linkParams[linkParamIndex];
307 if (routeName == '' || routeName == '.' || routeName == '..') {
308 throw new exceptions_1.BaseException("\"" + routeName + "/\" is only allowed at the beginning of a link DSL.");
309 }
310 linkParamIndex += 1;
311 if (linkParamIndex < linkParams.length) {
312 var linkParam = linkParams[linkParamIndex];
313 if (lang_1.isStringMap(linkParam) && !lang_1.isArray(linkParam)) {
314 routeParams = linkParam;
315 linkParamIndex += 1;
316 }
317 }
318 var routeRecognizer = (_aux ? rules.auxRulesByName : rules.rulesByName).get(routeName);
319 if (lang_1.isBlank(routeRecognizer)) {
320 throw new exceptions_1.BaseException("Component \"" + lang_1.getTypeNameForDebugging(parentComponentType) + "\" has no route named \"" + routeName + "\".");
321 }
322 // Create an "unresolved instruction" for async routes
323 // we'll figure out the rest of the route when we resolve the instruction and
324 // perform a navigation
325 if (lang_1.isBlank(routeRecognizer.handler.componentType)) {
326 var generatedUrl = routeRecognizer.generateComponentPathValues(routeParams);
327 return new instruction_1.UnresolvedInstruction(function () {
328 return routeRecognizer.handler.resolveComponentType().then(function (_) {
329 return _this._generate(linkParams, ancestorInstructions, prevInstruction, _aux, _originalLink);
330 });
331 }, generatedUrl.urlPath, url_parser_1.convertUrlParamsToArray(generatedUrl.urlParams));
332 }
333 componentInstruction = _aux ? rules.generateAuxiliary(routeName, routeParams) :
334 rules.generate(routeName, routeParams);
335 }
336 // Next, recognize auxiliary instructions.
337 // If we have an ancestor instruction, we preserve whatever aux routes are active from it.
338 while (linkParamIndex < linkParams.length && lang_1.isArray(linkParams[linkParamIndex])) {
339 var auxParentInstruction = [parentInstruction];
340 var auxInstruction = this._generate(linkParams[linkParamIndex], auxParentInstruction, null, true, _originalLink);
341 // TODO: this will not work for aux routes with parameters or multiple segments
342 auxInstructions[auxInstruction.component.urlPath] = auxInstruction;
343 linkParamIndex += 1;
344 }
345 var instruction = new instruction_1.ResolvedInstruction(componentInstruction, null, auxInstructions);
346 // If the component is sync, we can generate resolved child route instructions
347 // If not, we'll resolve the instructions at navigation time
348 if (lang_1.isPresent(componentInstruction) && lang_1.isPresent(componentInstruction.componentType)) {
349 var childInstruction = null;
350 if (componentInstruction.terminal) {
351 if (linkParamIndex >= linkParams.length) {
352 }
353 }
354 else {
355 var childAncestorComponents = ancestorInstructions.concat([instruction]);
356 var remainingLinkParams = linkParams.slice(linkParamIndex);
357 childInstruction = this._generate(remainingLinkParams, childAncestorComponents, null, false, _originalLink);
358 }
359 instruction.child = childInstruction;
360 }
361 return instruction;
362 };
363 RouteRegistry.prototype.hasRoute = function (name, parentComponent) {
364 var rules = this._rules.get(parentComponent);
365 if (lang_1.isBlank(rules)) {
366 return false;
367 }
368 return rules.hasRoute(name);
369 };
370 RouteRegistry.prototype.generateDefault = function (componentCursor) {
371 var _this = this;
372 if (lang_1.isBlank(componentCursor)) {
373 return null;
374 }
375 var rules = this._rules.get(componentCursor);
376 if (lang_1.isBlank(rules) || lang_1.isBlank(rules.defaultRule)) {
377 return null;
378 }
379 var defaultChild = null;
380 if (lang_1.isPresent(rules.defaultRule.handler.componentType)) {
381 var componentInstruction = rules.defaultRule.generate({});
382 if (!rules.defaultRule.terminal) {
383 defaultChild = this.generateDefault(rules.defaultRule.handler.componentType);
384 }
385 return new instruction_1.DefaultInstruction(componentInstruction, defaultChild);
386 }
387 return new instruction_1.UnresolvedInstruction(function () {
388 return rules.defaultRule.handler.resolveComponentType().then(function (_) { return _this.generateDefault(componentCursor); });
389 });
390 };
391 RouteRegistry = __decorate([
392 core_1.Injectable(),
393 __param(0, core_1.Inject(exports.ROUTER_PRIMARY_COMPONENT)),
394 __metadata('design:paramtypes', [lang_1.Type])
395 ], RouteRegistry);
396 return RouteRegistry;
397}());
398exports.RouteRegistry = RouteRegistry;
399/*
400 * Given: ['/a/b', {c: 2}]
401 * Returns: ['', 'a', 'b', {c: 2}]
402 */
403function splitAndFlattenLinkParams(linkParams) {
404 var accumulation = [];
405 linkParams.forEach(function (item) {
406 if (lang_1.isString(item)) {
407 var strItem = item;
408 accumulation = accumulation.concat(strItem.split('/'));
409 }
410 else {
411 accumulation.push(item);
412 }
413 });
414 return accumulation;
415}
416/*
417 * Given a list of instructions, returns the most specific instruction
418 */
419function mostSpecific(instructions) {
420 instructions = instructions.filter(function (instruction) { return lang_1.isPresent(instruction); });
421 if (instructions.length == 0) {
422 return null;
423 }
424 if (instructions.length == 1) {
425 return instructions[0];
426 }
427 var first = instructions[0];
428 var rest = instructions.slice(1);
429 return rest.reduce(function (instruction, contender) {
430 if (compareSpecificityStrings(contender.specificity, instruction.specificity) == -1) {
431 return contender;
432 }
433 return instruction;
434 }, first);
435}
436/*
437 * Expects strings to be in the form of "[0-2]+"
438 * Returns -1 if string A should be sorted above string B, 1 if it should be sorted after,
439 * or 0 if they are the same.
440 */
441function compareSpecificityStrings(a, b) {
442 var l = lang_1.Math.min(a.length, b.length);
443 for (var i = 0; i < l; i += 1) {
444 var ai = lang_1.StringWrapper.charCodeAt(a, i);
445 var bi = lang_1.StringWrapper.charCodeAt(b, i);
446 var difference = bi - ai;
447 if (difference != 0) {
448 return difference;
449 }
450 }
451 return a.length - b.length;
452}
453function assertTerminalComponent(component /** TODO #9100 */, path /** TODO #9100 */) {
454 if (!lang_1.isType(component)) {
455 return;
456 }
457 var annotations = core_private_1.reflector.annotations(component);
458 if (lang_1.isPresent(annotations)) {
459 for (var i = 0; i < annotations.length; i++) {
460 var annotation = annotations[i];
461 if (annotation instanceof route_config_impl_1.RouteConfig) {
462 throw new exceptions_1.BaseException("Child routes are not allowed for \"" + path + "\". Use \"...\" on the parent's route path.");
463 }
464 }
465 }
466}
467//# sourceMappingURL=route_registry.js.map
\No newline at end of file