feat(router): add support for lazily loaded modules

This commit is contained in:
vsavkin
2016-07-06 11:02:16 -07:00
parent 6fcf962fb5
commit 8ebb8e44c8
13 changed files with 431 additions and 140 deletions

View File

@ -4,14 +4,14 @@ import {Location, LocationStrategy} from '@angular/common';
import {SpyLocation} from '@angular/common/testing';
import {MockLocationStrategy} from '@angular/common/testing/mock_location_strategy';
import {ComponentFixture, TestComponentBuilder} from '@angular/compiler/testing';
import {Component, Injector} from '@angular/core';
import {AppModule, AppModuleFactory, AppModuleFactoryLoader, Compiler, Component, Injectable, Injector, Type} from '@angular/core';
import {ComponentResolver} from '@angular/core';
import {beforeEach, beforeEachProviders, ddescribe, describe, fakeAsync, iit, inject, it, tick, xdescribe, xit} from '@angular/core/testing';
import {expect} from '@angular/platform-browser/testing/matchers';
import {Observable} from 'rxjs/Observable';
import {of } from 'rxjs/observable/of';
import {ActivatedRoute, ActivatedRouteSnapshot, CanActivate, CanDeactivate, DefaultUrlSerializer, Event, NavigationCancel, NavigationEnd, NavigationError, NavigationStart, Params, ROUTER_DIRECTIVES, Resolve, Router, RouterConfig, RouterOutletMap, RouterStateSnapshot, RoutesRecognized, UrlSerializer} from '../index';
import {ActivatedRoute, ActivatedRouteSnapshot, CanActivate, CanDeactivate, DefaultUrlSerializer, Event, NavigationCancel, NavigationEnd, NavigationError, NavigationStart, Params, ROUTER_DIRECTIVES, Resolve, Router, RouterConfig, RouterOutletMap, RouterStateSnapshot, RoutesRecognized, UrlSerializer, provideRoutes} from '../index';
describe('Integration', () => {
@ -26,13 +26,18 @@ describe('Integration', () => {
{provide: LocationStrategy, useClass: MockLocationStrategy},
{
provide: Router,
useFactory: (resolver: ComponentResolver, urlSerializer: UrlSerializer,
outletMap: RouterOutletMap, location: Location, injector: Injector) => {
return new Router(
RootCmp, resolver, urlSerializer, outletMap, location, injector, config);
},
deps: [ComponentResolver, UrlSerializer, RouterOutletMap, Location, Injector]
useFactory:
(resolver: ComponentResolver, urlSerializer: UrlSerializer, outletMap: RouterOutletMap,
location: Location, loader: AppModuleFactoryLoader, injector: Injector) => {
return new Router(
RootCmp, resolver, urlSerializer, outletMap, location, injector, loader, config);
},
deps: [
ComponentResolver, UrlSerializer, RouterOutletMap, Location, AppModuleFactoryLoader,
Injector
]
},
{provide: AppModuleFactoryLoader, useClass: SpyAppModuleFactoryLoader},
{provide: ActivatedRoute, useFactory: (r: Router) => r.routerState.root, deps: [Router]},
];
});
@ -713,6 +718,8 @@ describe('Integration', () => {
describe('should not activate a route when CanActivate returns false', () => {
beforeEachProviders(() => [{provide: 'alwaysFalse', useValue: (a: any, b: any) => false}]);
// handle errors
it('works',
fakeAsync(inject(
[Router, TestComponentBuilder, Location],
@ -1084,8 +1091,90 @@ describe('Integration', () => {
})));
});
describe('lazy loading', () => {
it('works', fakeAsync(inject(
[Router, TestComponentBuilder, Location, AppModuleFactoryLoader],
(router: Router, tcb: TestComponentBuilder, location: Location,
loader: AppModuleFactoryLoader) => {
@Component({
selector: 'lazy',
template: 'lazy-loaded-parent {<router-outlet></router-outlet>}',
directives: ROUTER_DIRECTIVES
})
class ParentLazyLoadedComponent {
}
@Component({selector: 'lazy', template: 'lazy-loaded-child'})
class ChildLazyLoadedComponent {
}
@AppModule({
providers: [provideRoutes([{
path: 'loaded',
component: ParentLazyLoadedComponent,
children: [{path: 'child', component: ChildLazyLoadedComponent}]
}])],
precompile: [ParentLazyLoadedComponent, ChildLazyLoadedComponent]
})
class LoadedModule {
}
(<any>loader).expectedPath = 'expected';
(<any>loader).expected = LoadedModule;
const fixture = createRoot(tcb, router, RootCmp);
router.resetConfig([{path: 'lazy', mountChildren: 'expected'}]);
router.navigateByUrl('/lazy/loaded/child');
advance(fixture);
expect(location.path()).toEqual('/lazy/loaded/child');
expect(fixture.debugElement.nativeElement)
.toHaveText('lazy-loaded-parent {lazy-loaded-child}');
})));
it('error emit an error when cannot load a config',
fakeAsync(inject(
[Router, TestComponentBuilder, Location, AppModuleFactoryLoader],
(router: Router, tcb: TestComponentBuilder, location: Location,
loader: AppModuleFactoryLoader) => {
(<any>loader).expectedPath = 'expected';
const fixture = createRoot(tcb, router, RootCmp);
router.resetConfig([{path: 'lazy', mountChildren: 'invalid'}]);
const recordedEvents: any = [];
router.events.forEach(e => recordedEvents.push(e));
router.navigateByUrl('/lazy/loaded').catch(s => {})
advance(fixture);
expect(location.path()).toEqual('/');
expectEvents(
recordedEvents,
[[NavigationStart, '/lazy/loaded'], [NavigationError, '/lazy/loaded']]);
})));
});
});
@Injectable()
class SpyAppModuleFactoryLoader implements AppModuleFactoryLoader {
public expected: any;
public expectedPath: string;
constructor(private compiler: Compiler) {}
load(path: string): Promise<AppModuleFactory<any>> {
if (path === this.expectedPath) {
return this.compiler.compileAppModuleAsync(this.expected);
} else {
return <any>Promise.reject(new Error('boom'));
}
}
}
function expectEvents(events: Event[], pairs: any[]) {
for (let i = 0; i < events.length; ++i) {
expect((<any>events[i].constructor).name).toBe(pairs[i][0].name);