Skip to content

Commit 5205a9e

Browse files
committed
refactor(angular_1_router): use directives for route targets
BREAKING CHANGE: Previously, route configuration took a controller constructor function as the value of `component` in a route definition: ``` $route.config([ { route: '/', component: MyController } ]) ``` Based on the name of the controller, we used to use a componentMapper service to determine what template to pair with each controller, how to bind the instance to the $scope. To make the 1.x router more semantically alligned with Angular 2, we now route to a directive. Thus a route configuration takes a normalized directive name: ``` $route.config([ { route: '/', component: 'myDirective' } ]) ``` BREAKING CHANGE: In order to avoid name collisions, lifecycle hooks are now prefixed with `$`. Before: ``` MyController.prototype.onActivate = ... ``` After: ``` MyController.prototype.$onActivate = ... ``` Same for `$canActivate` (which now lives on the directive factory function), `$canDeactivate`, `$canReuse`, and `$onDeactivate` hooks.
1 parent 6e0ca7f commit 5205a9e

File tree

12 files changed

+551
-728
lines changed

12 files changed

+551
-728
lines changed

modules/angular1_router/build.js

Lines changed: 4 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -22,70 +22,22 @@ var PRELUDE = '(function(){\n';
2222
var POSTLUDE = '\n}());\n';
2323
var FACADES = fs.readFileSync(__dirname + '/lib/facades.es5', 'utf8');
2424
var DIRECTIVES = fs.readFileSync(__dirname + '/src/ng_outlet.js', 'utf8');
25+
var moduleTemplate = fs.readFileSync(__dirname + '/src/module_template.js', 'utf8');
26+
2527
function main() {
2628
var ES6_SHIM = fs.readFileSync(__dirname + '/../../node_modules/es6-shim/es6-shim.js', 'utf8');
2729
var dir = __dirname + '/../angular2/src/router/';
2830

29-
var out = '';
30-
3131
var sharedCode = '';
3232
files.forEach(function (file) {
3333
var moduleName = 'router/' + file.replace(/\.ts$/, '');
3434

3535
sharedCode += transform(moduleName, fs.readFileSync(dir + file, 'utf8'));
3636
});
3737

38-
out += "angular.module('ngComponentRouter')";
39-
out += angularFactory('$router', ['$q', '$location', '$$controllerIntrospector',
40-
'$browser', '$rootScope', '$injector'], [
41-
FACADES,
42-
"var exports = {Injectable: function () {}};",
43-
"var require = function () {return exports;};",
44-
sharedCode,
45-
"var RouteConfig = exports.RouteConfig;",
46-
"angular.annotations = {RouteConfig: RouteConfig, CanActivate: exports.CanActivate};",
47-
"angular.stringifyInstruction = exports.stringifyInstruction;",
48-
"var RouteRegistry = exports.RouteRegistry;",
49-
"var RootRouter = exports.RootRouter;",
50-
//TODO: move this code into a templated JS file
51-
"var registry = new RouteRegistry();",
52-
"var location = new Location();",
53-
54-
"$$controllerIntrospector(function (name, constructor) {",
55-
"if (constructor.$canActivate) {",
56-
"constructor.annotations = constructor.annotations || [];",
57-
"constructor.annotations.push(new angular.annotations.CanActivate(function (instruction) {",
58-
"return $injector.invoke(constructor.$canActivate, constructor, {",
59-
"$routeParams: instruction.component ? instruction.component.params : instruction.params",
60-
"});",
61-
"}));",
62-
"}",
63-
"if (constructor.$routeConfig) {",
64-
"constructor.annotations = constructor.annotations || [];",
65-
"constructor.annotations.push(new angular.annotations.RouteConfig(constructor.$routeConfig));",
66-
"}",
67-
"if (constructor.annotations) {",
68-
"constructor.annotations.forEach(function(annotation) {",
69-
"if (annotation instanceof RouteConfig) {",
70-
"annotation.configs.forEach(function (config) {",
71-
"registry.config(constructor, config);",
72-
"});",
73-
"}",
74-
"});",
75-
"}",
76-
"});",
77-
78-
"var router = new RootRouter(registry, location, new Object());",
79-
"$rootScope.$watch(function () { return $location.path(); }, function (path) {",
80-
"if (router.lastNavigationAttempt !== path) {",
81-
"router.navigateByUrl(path);",
82-
"}",
83-
"});",
84-
85-
"return router;"
86-
].join('\n'));
38+
var out = moduleTemplate.replace('//{{FACADES}}', FACADES).replace('//{{SHARED_CODE}}', sharedCode);
8739

88-
return PRELUDE + ES6_SHIM + DIRECTIVES + out + POSTLUDE;
40+
return PRELUDE + DIRECTIVES + out + POSTLUDE;
8941
}
9042

9143

modules/angular1_router/lib/facades.es5

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ function isArray(obj) {
3232
return Array.isArray(obj);
3333
}
3434

35+
function getTypeNameForDebugging (fn) {
36+
return fn.name || 'Root';
37+
}
38+
3539
var PromiseWrapper = {
3640
resolve: function (reason) {
3741
return $q.when(reason);
@@ -251,8 +255,8 @@ var StringWrapper = {
251255
return s.replace(from, replace);
252256
},
253257

254-
startsWith: function(s, start) {
255-
return s.startsWith(start);
258+
startsWith: function(s, start) {
259+
return s.substr(0, start.length) === start;
256260
},
257261

258262
replaceAllMapped: function(s, from, cb) {
@@ -272,14 +276,18 @@ var StringWrapper = {
272276

273277
//TODO: implement?
274278
// I think it's too heavy to ask 1.x users to bring in Rx for the router...
275-
function EventEmitter() {
276-
277-
}
279+
function EventEmitter() {}
278280

279281
var BaseException = Error;
280282

281283
var ObservableWrapper = {
282-
callNext: function(){}
284+
callNext: function(ob, val) {
285+
ob.fn(val);
286+
},
287+
288+
subscribe: function(ob, fn) {
289+
ob.fn = fn;
290+
}
283291
};
284292

285293
// TODO: https://github.com/angular/angular.js/blob/master/src/ng/browser.js#L227-L265
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
2+
angular.module('ngComponentRouter').
3+
value('$route', null). // can be overloaded with ngRouteShim
4+
factory('$router', ['$q', '$location', '$$directiveIntrospector', '$browser', '$rootScope', '$injector', '$route', routerFactory]);
5+
6+
function routerFactory($q, $location, $$directiveIntrospector, $browser, $rootScope, $injector) {
7+
8+
// When this file is processed, the line below is replaced with
9+
// the contents of `../lib/facades.es5`.
10+
//{{FACADES}}
11+
12+
var exports = {Injectable: function () {}};
13+
var require = function () {return exports;};
14+
15+
// When this file is processed, the line below is replaced with
16+
// the contents of the compiled TypeScript classes.
17+
//{{SHARED_CODE}}
18+
19+
//TODO: this is a hack to replace the exiting implementation at run-time
20+
exports.getCanActivateHook = function (directiveName) {
21+
var factory = $$directiveIntrospector.getTypeByName(directiveName);
22+
return factory && factory.$canActivate && function (next, prev) {
23+
return $injector.invoke(factory.$canActivate, null, {
24+
$nextInstruction: next,
25+
$prevInstruction: prev
26+
});
27+
};
28+
};
29+
30+
// This hack removes assertions about the type of the "component"
31+
// property in a route config
32+
exports.assertComponentExists = function () {};
33+
34+
angular.stringifyInstruction = exports.stringifyInstruction;
35+
36+
var RouteRegistry = exports.RouteRegistry;
37+
var RootRouter = exports.RootRouter;
38+
39+
var registry = new RouteRegistry();
40+
var location = new Location();
41+
42+
$$directiveIntrospector(function (name, factory) {
43+
if (angular.isArray(factory.$routeConfig)) {
44+
factory.$routeConfig.forEach(function (config) {
45+
registry.config(name, config);
46+
});
47+
}
48+
});
49+
50+
// Because Angular 1 has no notion of a root component, we use an object with unique identity
51+
// to represent this.
52+
var ROOT_COMPONENT_OBJECT = new Object();
53+
54+
var router = new RootRouter(registry, location, ROOT_COMPONENT_OBJECT);
55+
$rootScope.$watch(function () { return $location.path(); }, function (path) {
56+
if (router.lastNavigationAttempt !== path) {
57+
router.navigateByUrl(path);
58+
}
59+
});
60+
61+
router.subscribe(function () {
62+
$rootScope.$broadcast('$routeChangeSuccess', {});
63+
});
64+
65+
return router;
66+
}

0 commit comments

Comments
 (0)