refactor(router): improve recognition and generation pipeline

This is a big change. @matsko also deserves much of the credit for the implementation.

Previously, `ComponentInstruction`s held all the state for async components.
Now, we introduce several subclasses for `Instruction` to describe each type of navigation.

BREAKING CHANGE:

Redirects now use the Link DSL syntax. Before:

```
@RouteConfig([
	{ path: '/foo', redirectTo: '/bar' },
	{ path: '/bar', component: BarCmp }
])
```

After:

```
@RouteConfig([
	{ path: '/foo', redirectTo: ['Bar'] },
	{ path: '/bar', component: BarCmp, name: 'Bar' }
])
```

BREAKING CHANGE:

This also introduces `useAsDefault` in the RouteConfig, which makes cases like lazy-loading
and encapsulating large routes with sub-routes easier.

Previously, you could use `redirectTo` like this to expand a URL like `/tab` to `/tab/posts`:

@RouteConfig([
	{ path: '/tab', redirectTo: '/tab/users' }
	{ path: '/tab', component: TabsCmp, name: 'Tab' }
])
AppCmp { ... }

Now the recommended way to handle this is case is to use `useAsDefault` like so:

```
@RouteConfig([
	{ path: '/tab', component: TabsCmp, name: 'Tab' }
])
AppCmp { ... }

@RouteConfig([
	{ path: '/posts', component: PostsCmp, useAsDefault: true, name: 'Posts' },
	{ path: '/users', component: UsersCmp, name: 'Users' }
])
TabsCmp { ... }
```

In the above example, you can write just `['/Tab']` and the route `Users` is automatically selected as a child route.

Closes #4728
Closes #4228
Closes #4170
Closes #4490
Closes #4694
Closes #5200

Closes #5475
This commit is contained in:
Brian Ford
2015-11-23 18:07:37 -08:00
parent a3253210b7
commit 6ddfff5cd5
43 changed files with 3113 additions and 1197 deletions

View File

@ -6,12 +6,13 @@ var ts = require('typescript');
var files = [
'lifecycle_annotations_impl.ts',
'url_parser.ts',
'path_recognizer.ts',
'route_recognizer.ts',
'route_config_impl.ts',
'async_route_handler.ts',
'sync_route_handler.ts',
'route_recognizer.ts',
'component_recognizer.ts',
'instruction.ts',
'path_recognizer.ts',
'route_config_nomalizer.ts',
'route_lifecycle_reflector.ts',
'route_registry.ts',
@ -39,7 +40,10 @@ function main() {
* sourcemap, and exported variable identifier name for the content.
*/
var IMPORT_RE = new RegExp("import \\{?([\\w\\n_, ]+)\\}? from '(.+)';?", 'g');
var INJECT_RE = new RegExp("@Inject\\(ROUTER_PRIMARY_COMPONENT\\)", 'g');
var IMJECTABLE_RE = new RegExp("@Injectable\\(\\)", 'g');
function transform(contents) {
contents = contents.replace(INJECT_RE, '').replace(IMJECTABLE_RE, '');
contents = contents.replace(IMPORT_RE, function (match, imports, includePath) {
//TODO: remove special-case
if (isFacadeModule(includePath) || includePath === './router_outlet') {

View File

@ -173,6 +173,10 @@ var StringMapWrapper = {
var List = Array;
var ListWrapper = {
clear: function (l) {
l.length = 0;
},
create: function () {
return [];
},

View File

@ -9,7 +9,11 @@ function routerFactory($q, $location, $$directiveIntrospector, $browser, $rootSc
// the contents of `../lib/facades.es5`.
//{{FACADES}}
var exports = {Injectable: function () {}};
var exports = {
Injectable: function () {},
OpaqueToken: function () {},
Inject: function () {}
};
var require = function () {return exports;};
// When this file is processed, the line below is replaced with
@ -31,12 +35,19 @@ function routerFactory($q, $location, $$directiveIntrospector, $browser, $rootSc
// property in a route config
exports.assertComponentExists = function () {};
angular.stringifyInstruction = exports.stringifyInstruction;
angular.stringifyInstruction = function (instruction) {
return instruction.toRootUrl();
};
var RouteRegistry = exports.RouteRegistry;
var RootRouter = exports.RootRouter;
var registry = new RouteRegistry();
// Because Angular 1 has no notion of a root component, we use an object with unique identity
// to represent this.
var ROOT_COMPONENT_OBJECT = new Object();
var registry = new RouteRegistry(ROOT_COMPONENT_OBJECT);
var location = new Location();
$$directiveIntrospector(function (name, factory) {
@ -47,10 +58,6 @@ function routerFactory($q, $location, $$directiveIntrospector, $browser, $rootSc
}
});
// Because Angular 1 has no notion of a root component, we use an object with unique identity
// to represent this.
var ROOT_COMPONENT_OBJECT = new Object();
var router = new RootRouter(registry, location, ROOT_COMPONENT_OBJECT);
$rootScope.$watch(function () { return $location.path(); }, function (path) {
if (router.lastNavigationAttempt !== path) {

View File

@ -110,7 +110,7 @@
routeMap[path] = routeCopy;
if (route.redirectTo) {
routeDefinition.redirectTo = route.redirectTo;
routeDefinition.redirectTo = [routeMap[route.redirectTo].name];
} else {
if (routeCopy.controller && !routeCopy.controllerAs) {
console.warn('Route for "' + path + '" should use "controllerAs".');
@ -123,7 +123,7 @@
}
routeDefinition.component = directiveName;
routeDefinition.as = upperCase(directiveName);
routeDefinition.name = route.name || upperCase(directiveName);
var directiveController = routeCopy.controller;

View File

@ -113,8 +113,7 @@ describe('navigation', function () {
});
// TODO: fix this
xit('should work with recursive nested outlets', function () {
it('should work with recursive nested outlets', function () {
registerComponent('recurCmp', {
template: '<div>recur { <div ng-outlet></div> }</div>',
$routeConfig: [
@ -152,8 +151,8 @@ describe('navigation', function () {
compile('<div ng-outlet></div>');
$router.config([
{ path: '/', redirectTo: '/user' },
{ path: '/user', component: 'userCmp' }
{ path: '/', redirectTo: ['/User'] },
{ path: '/user', component: 'userCmp', name: 'User' }
]);
$router.navigateByUrl('/');
@ -167,16 +166,15 @@ describe('navigation', function () {
registerComponent('childRouter', {
template: '<div>inner { <div ng-outlet></div> }</div>',
$routeConfig: [
{ path: '/old-child', redirectTo: '/new-child' },
{ path: '/new-child', component: 'oneCmp'},
{ path: '/old-child-two', redirectTo: '/new-child-two' },
{ path: '/new-child-two', component: 'twoCmp'}
{ path: '/new-child', component: 'oneCmp', name: 'NewChild'},
{ path: '/new-child-two', component: 'twoCmp', name: 'NewChildTwo'}
]
});
$router.config([
{ path: '/old-parent', redirectTo: '/new-parent' },
{ path: '/new-parent/...', component: 'childRouter' }
{ path: '/old-parent/old-child', redirectTo: ['/NewParent', 'NewChild'] },
{ path: '/old-parent/old-child-two', redirectTo: ['/NewParent', 'NewChildTwo'] },
{ path: '/new-parent/...', component: 'childRouter', name: 'NewParent' }
]);
compile('<div ng-outlet></div>');

View File

@ -139,11 +139,12 @@ describe('ngRoute shim', function () {
it('should adapt routes with redirects', inject(function ($location) {
$routeProvider
.when('/home', {
template: 'welcome home!',
name: 'Home'
})
.when('/', {
redirectTo: '/home'
})
.when('/home', {
template: 'welcome home!'
});
$rootScope.$digest();