fix(router): load route config from async instructions

Previously, async routes generated from links would not load the configs of
their resolved components, which led to broken links in the children of the
async instruction's component.

This commit fixes the bookkeeping in the Router to correctly load the configs.

Fixes internal b/23791558

Closes #4146
This commit is contained in:
Brian Ford 2015-09-11 14:50:41 -07:00
parent 4c2fb1f6e8
commit 5e49d7e624
4 changed files with 36 additions and 5 deletions

View File

@ -249,9 +249,9 @@ export class RouteRegistry {
} }
lastInstructionIsTerminal = lastInstruction.component.terminal; lastInstructionIsTerminal = lastInstruction.component.terminal;
} }
if (!lastInstructionIsTerminal) { if (isPresent(componentCursor) && !lastInstructionIsTerminal) {
throw new BaseException( throw new BaseException(
`Link "${ListWrapper.toJSON(linkParams)}" does not resolve to a terminal instruction.`); `Link "${ListWrapper.toJSON(linkParams)}" does not resolve to a terminal or async instruction.`);
} }
} }

View File

@ -217,7 +217,8 @@ export class Router {
_settleInstruction(instruction: Instruction): Promise<any> { _settleInstruction(instruction: Instruction): Promise<any> {
var unsettledInstructions: Array<Promise<any>> = []; var unsettledInstructions: Array<Promise<any>> = [];
if (isBlank(instruction.component.componentType)) { if (isBlank(instruction.component.componentType)) {
unsettledInstructions.push(instruction.component.resolveComponentType()); unsettledInstructions.push(instruction.component.resolveComponentType().then(
(type: Type) => { this.registry.configFromComponent(type); }));
} }
if (isPresent(instruction.child)) { if (isPresent(instruction.child)) {
unsettledInstructions.push(this._settleInstruction(instruction.child)); unsettledInstructions.push(this._settleInstruction(instruction.child));

View File

@ -17,6 +17,7 @@ import {
} from 'angular2/test_lib'; } from 'angular2/test_lib';
import {NumberWrapper} from 'angular2/src/core/facade/lang'; import {NumberWrapper} from 'angular2/src/core/facade/lang';
import {PromiseWrapper} from 'angular2/src/core/facade/async';
import {bind, Component, DirectiveResolver, View} from 'angular2/core'; import {bind, Component, DirectiveResolver, View} from 'angular2/core';
@ -29,6 +30,7 @@ import {
Pipeline, Pipeline,
RouterLink, RouterLink,
RouterOutlet, RouterOutlet,
AsyncRoute,
Route, Route,
RouteParams, RouteParams,
RouteConfig, RouteConfig,
@ -96,7 +98,6 @@ export function main() {
})); }));
it('should generate link hrefs with params', inject([AsyncTestCompleter], (async) => { it('should generate link hrefs with params', inject([AsyncTestCompleter], (async) => {
compile('<a href="hello" [router-link]="[\'./user\', {name: name}]">{{name}}</a>') compile('<a href="hello" [router-link]="[\'./user\', {name: name}]">{{name}}</a>')
.then((_) => router.config( .then((_) => router.config(
@ -128,6 +129,31 @@ export function main() {
}); });
})); }));
it('should generate link hrefs when asynchronously loaded',
inject([AsyncTestCompleter], (async) => {
compile()
.then((_) => router.config([
new AsyncRoute({
path: '/child-with-grandchild/...',
loader: parentCmpLoader,
as: 'child-with-grandchild'
})
]))
.then((_) => {
// TODO: refactor when https://github.com/angular/angular/pull/4074 lands
var instruction = router.generate(['/child-with-grandchild']);
return router.navigateInstruction(instruction);
})
.then((_) => {
rootTC.detectChanges();
expect(DOM.getAttribute(
rootTC.componentViewChildren[1].componentViewChildren[0].nativeElement,
'href'))
.toEqual('/child-with-grandchild/grandchild');
async.done();
});
}));
it('should generate relative links preserving the existing parent route', it('should generate relative links preserving the existing parent route',
inject([AsyncTestCompleter], (async) => { inject([AsyncTestCompleter], (async) => {
compile() compile()
@ -319,6 +345,10 @@ class HelloCmp {
class Hello2Cmp { class Hello2Cmp {
} }
function parentCmpLoader() {
return PromiseWrapper.resolve(ParentCmp);
}
@Component({selector: 'parent-cmp'}) @Component({selector: 'parent-cmp'})
@View({ @View({
template: `{ <a [router-link]="['./grandchild']" class="grandchild-link">Grandchild</a> template: `{ <a [router-link]="['./grandchild']" class="grandchild-link">Grandchild</a>

View File

@ -235,7 +235,7 @@ export function main() {
registry.config(RootHostCmp, registry.config(RootHostCmp,
new Route({path: '/first/...', component: DummyParentCmp, as: 'first'})); new Route({path: '/first/...', component: DummyParentCmp, as: 'first'}));
expect(() => { registry.generate(['first'], RootHostCmp); }) expect(() => { registry.generate(['first'], RootHostCmp); })
.toThrowError('Link "["first"]" does not resolve to a terminal instruction.'); .toThrowError('Link "["first"]" does not resolve to a terminal or async instruction.');
}); });
it('should match matrix params on child components and query params on the root component', it('should match matrix params on child components and query params on the root component',