X Tutup
Skip to content

Commit aed34e1

Browse files
committed
feat(angular_1_router): add ngRouteShim module
This module attempts to provide a shim for `$routeProvider` to aid in migrating from ngRoute to Component Router. Closes #4266
1 parent 5205a9e commit aed34e1

File tree

3 files changed

+532
-0
lines changed

3 files changed

+532
-0
lines changed

modules/angular1_router/karma-router.conf.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ module.exports = function (config) {
1414
'../../node_modules/angular-mocks/angular-mocks.js',
1515

1616
'../../dist/angular_1_router.js',
17+
'src/ng_route_shim.js',
1718

1819
'test/*.es5.js',
1920
'test/**/*_spec.js'
Lines changed: 349 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,349 @@
1+
/** @license Copyright 2014-2015 Google, Inc. http://github.com/angular/angular/LICENSE */
2+
(function () {
3+
4+
'use strict';
5+
6+
// keep a reference to compileProvider so we can register new component-directives
7+
// on-the-fly based on $routeProvider configuration
8+
// TODO: remove this– right now you can only bootstrap one Angular app with this hack
9+
var $compileProvider, $q, $injector;
10+
11+
/**
12+
* This module loads services that mimic ngRoute's configuration, and includes
13+
* an anchor link directive that intercepts clicks to routing.
14+
*
15+
* This module is intended to be used as a stop-gap solution for projects upgrading from ngRoute.
16+
* It intentionally does not implement all features of ngRoute.
17+
*/
18+
angular.module('ngRouteShim', [])
19+
.provider('$route', $RouteProvider)
20+
.config(['$compileProvider', function (compileProvider) {
21+
$compileProvider = compileProvider;
22+
}])
23+
.factory('$routeParams', $routeParamsFactory)
24+
.directive('a', anchorLinkDirective)
25+
26+
// Connects the legacy $routeProvider config shim to Component Router's config.
27+
.run(['$route', '$router', function ($route, $router) {
28+
$route.$$subscribe(function (routeDefinition) {
29+
if (!angular.isArray(routeDefinition)) {
30+
routeDefinition = [routeDefinition];
31+
}
32+
$router.config(routeDefinition);
33+
});
34+
}]);
35+
36+
37+
/**
38+
* A shimmed out provider that provides the same API as ngRoute's $routeProvider, but uses these calls
39+
* to configure Component Router.
40+
*/
41+
function $RouteProvider() {
42+
43+
var routes = [];
44+
var subscriptionFn = null;
45+
46+
var routeMap = {};
47+
48+
// Stats for which routes are skipped
49+
var skipCount = 0;
50+
var successCount = 0;
51+
var allCount = 0;
52+
53+
function consoleMetrics() {
54+
return '(' + skipCount + ' skipped / ' + successCount + ' success / ' + allCount + ' total)';
55+
}
56+
57+
58+
/**
59+
* @ngdoc method
60+
* @name $routeProvider#when
61+
*
62+
* @param {string} path Route path (matched against `$location.path`). If `$location.path`
63+
* contains redundant trailing slash or is missing one, the route will still match and the
64+
* `$location.path` will be updated to add or drop the trailing slash to exactly match the
65+
* route definition.
66+
*
67+
* @param {Object} route Mapping information to be assigned to `$route.current` on route
68+
* match.
69+
*/
70+
this.when = function(path, route) {
71+
//copy original route object to preserve params inherited from proto chain
72+
var routeCopy = angular.copy(route);
73+
74+
allCount++;
75+
76+
if (angular.isDefined(routeCopy.reloadOnSearch)) {
77+
console.warn('Route for "' + path + '" uses "reloadOnSearch" which is not implemented.');
78+
}
79+
if (angular.isDefined(routeCopy.caseInsensitiveMatch)) {
80+
console.warn('Route for "' + path + '" uses "caseInsensitiveMatch" which is not implemented.');
81+
}
82+
83+
// use new wildcard format
84+
path = reformatWildcardParams(path);
85+
86+
if (path[path.length - 1] == '*') {
87+
skipCount++;
88+
console.warn('Route for "' + path + '" ignored because it ends with *. Skipping.', consoleMetrics());
89+
return this;
90+
}
91+
92+
if (path.indexOf('?') > -1) {
93+
skipCount++;
94+
console.warn('Route for "' + path + '" ignored because it has optional parameters. Skipping.', consoleMetrics());
95+
return this;
96+
}
97+
98+
if (typeof route.redirectTo == 'function') {
99+
skipCount++;
100+
console.warn('Route for "' + path + '" ignored because lazy redirecting to a function is not yet implemented. Skipping.', consoleMetrics());
101+
return this;
102+
}
103+
104+
105+
var routeDefinition = {
106+
path: path,
107+
data: routeCopy
108+
};
109+
110+
routeMap[path] = routeCopy;
111+
112+
if (route.redirectTo) {
113+
routeDefinition.redirectTo = route.redirectTo;
114+
} else {
115+
if (routeCopy.controller && !routeCopy.controllerAs) {
116+
console.warn('Route for "' + path + '" should use "controllerAs".');
117+
}
118+
119+
var directiveName = routeObjToRouteName(routeCopy, path);
120+
121+
if (!directiveName) {
122+
throw new Error('Could not determine a name for route "' + path + '".');
123+
}
124+
125+
routeDefinition.component = directiveName;
126+
routeDefinition.as = upperCase(directiveName);
127+
128+
var directiveController = routeCopy.controller;
129+
130+
var directiveDefinition = {
131+
scope: false,
132+
controller: directiveController,
133+
controllerAs: routeCopy.controllerAs,
134+
templateUrl: routeCopy.templateUrl,
135+
template: routeCopy.template
136+
};
137+
138+
var directiveFactory = function () {
139+
return directiveDefinition;
140+
};
141+
142+
// if we have route resolve options, prepare a wrapper controller
143+
if (directiveController && routeCopy.resolve) {
144+
var originalController = directiveController;
145+
var resolvedLocals = {};
146+
147+
directiveDefinition.controller = ['$injector', '$scope', function ($injector, $scope) {
148+
var locals = angular.extend({
149+
$scope: $scope
150+
}, resolvedLocals);
151+
152+
var ctrl = $injector.instantiate(originalController, locals);
153+
154+
if (routeCopy.controllerAs) {
155+
locals.$scope[routeCopy.controllerAs] = ctrl;
156+
}
157+
158+
return ctrl;
159+
}];
160+
161+
// we take care of controllerAs in the directive controller wrapper
162+
delete directiveDefinition.controllerAs;
163+
164+
// we resolve the locals in a canActivate block
165+
directiveFactory.$canActivate = function() {
166+
var locals = angular.extend({}, routeCopy.resolve);
167+
168+
angular.forEach(locals, function(value, key) {
169+
locals[key] = angular.isString(value) ?
170+
$injector.get(value) : $injector.invoke(value, null, null, key);
171+
});
172+
173+
return $q.all(locals).then(function (locals) {
174+
resolvedLocals = locals;
175+
}).then(function () {
176+
return true;
177+
});
178+
};
179+
}
180+
181+
// register the dynamically created directive
182+
$compileProvider.directive(directiveName, directiveFactory);
183+
}
184+
if (subscriptionFn) {
185+
subscriptionFn(routeDefinition);
186+
} else {
187+
routes.push(routeDefinition);
188+
}
189+
successCount++;
190+
191+
return this;
192+
};
193+
194+
this.otherwise = function(params) {
195+
if (typeof params === 'string') {
196+
params = {redirectTo: params};
197+
}
198+
this.when('/*rest', params);
199+
return this;
200+
};
201+
202+
203+
this.$get = ['$q', '$injector', function (q, injector) {
204+
$q = q;
205+
$injector = injector;
206+
207+
var $route = {
208+
routes: routeMap,
209+
210+
/**
211+
* @ngdoc method
212+
* @name $route#reload
213+
*
214+
* @description
215+
* Causes `$route` service to reload the current route even if
216+
* {@link ng.$location $location} hasn't changed.
217+
*/
218+
reload: function() {
219+
throw new Error('Not implemented: $route.reload');
220+
},
221+
222+
/**
223+
* @ngdoc method
224+
* @name $route#updateParams
225+
*/
226+
updateParams: function(newParams) {
227+
throw new Error('Not implemented: $route.updateParams');
228+
},
229+
230+
/**
231+
* Runs the given `fn` whenever new configs are added.
232+
* Only one subscription is allowed.
233+
* Passed `fn` is called synchronously.
234+
*/
235+
$$subscribe: function(fn) {
236+
if (subscriptionFn) {
237+
throw new Error('only one subscription allowed');
238+
}
239+
subscriptionFn = fn;
240+
subscriptionFn(routes);
241+
routes = [];
242+
},
243+
244+
/**
245+
* Runs a string with stats about many route configs were adapted, and how many were
246+
* dropped because they are incompatible.
247+
*/
248+
$$getStats: consoleMetrics
249+
};
250+
251+
return $route;
252+
}];
253+
254+
}
255+
256+
function $routeParamsFactory($router, $rootScope) {
257+
// the identity of this object cannot change
258+
var paramsObj = {};
259+
260+
$rootScope.$on('$routeChangeSuccess', function () {
261+
var newParams = $router._currentInstruction && $router._currentInstruction.component.params;
262+
263+
angular.forEach(paramsObj, function (val, name) {
264+
delete paramsObj[name];
265+
});
266+
angular.forEach(newParams, function (val, name) {
267+
paramsObj[name] = val;
268+
});
269+
});
270+
271+
return paramsObj;
272+
}
273+
274+
/**
275+
* Allows normal anchor links to kick off routing.
276+
*/
277+
function anchorLinkDirective($router) {
278+
return {
279+
restrict: 'E',
280+
link: function (scope, element) {
281+
// If the linked element is not an anchor tag anymore, do nothing
282+
if (element[0].nodeName.toLowerCase() !== 'a') {
283+
return;
284+
}
285+
286+
// SVGAElement does not use the href attribute, but rather the 'xlinkHref' attribute.
287+
var hrefAttrName = Object.prototype.toString.call(element.prop('href')) === '[object SVGAnimatedString]' ?
288+
'xlink:href' : 'href';
289+
290+
element.on('click', function (event) {
291+
if (event.which !== 1) {
292+
return;
293+
}
294+
295+
var href = element.attr(hrefAttrName);
296+
if (href && $router.recognize(href)) {
297+
$router.navigateByUrl(href);
298+
event.preventDefault();
299+
}
300+
});
301+
}
302+
};
303+
}
304+
305+
/**
306+
* Given a route object, attempts to find a unique directive name.
307+
*
308+
* @param route – route config object passed to $routeProvider.when
309+
* @param path – route configuration path
310+
* @returns {string|name} – a normalized (camelCase) directive name
311+
*/
312+
function routeObjToRouteName(route, path) {
313+
var name = route.controllerAs;
314+
315+
var controller = route.controller;
316+
if (!name && controller) {
317+
if (angular.isArray(controller)) {
318+
controller = controller[controller.length - 1];
319+
}
320+
name = controller.name;
321+
}
322+
323+
if (!name) {
324+
var segments = path.split('/');
325+
name = segments[segments.length - 1];
326+
}
327+
328+
if (name) {
329+
name = name + 'AutoCmp';
330+
}
331+
332+
return name;
333+
}
334+
335+
function upperCase(str) {
336+
return str.charAt(0).toUpperCase() + str.substr(1);
337+
}
338+
339+
/*
340+
* Changes "/:foo*" to "/*foo"
341+
*/
342+
var WILDCARD_PARAM_RE = new RegExp('\\/:([a-z0-9]+)\\*', 'gi');
343+
function reformatWildcardParams(path) {
344+
return path.replace(WILDCARD_PARAM_RE, function (m, m1) {
345+
return '/*' + m1;
346+
});
347+
}
348+
349+
}());

0 commit comments

Comments
 (0)
X Tutup