Skip to content

Commit 6b26102

Browse files
vsavkinvikerman
authored andcommitted
feat(router): extend support for lazy loading children (#10705)
1 parent bec5c5f commit 6b26102

File tree

10 files changed

+84
-25
lines changed

10 files changed

+84
-25
lines changed

modules/@angular/router/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99

1010
export {ExtraOptions, provideRouterConfig, provideRoutes} from './src/common_router_providers';
11-
export {Data, ResolveData, Route, RouterConfig, Routes} from './src/config';
11+
export {Data, LoadChildren, LoadChildrenCallback, ResolveData, Route, RouterConfig, Routes} from './src/config';
1212
export {RouterLink, RouterLinkWithHref} from './src/directives/router_link';
1313
export {RouterLinkActive} from './src/directives/router_link_active';
1414
export {RouterOutlet} from './src/directives/router_outlet';

modules/@angular/router/src/common_router_providers.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
*/
88

99
import {Location, LocationStrategy, PathLocationStrategy} from '@angular/common';
10-
import {ANALYZE_FOR_ENTRY_COMPONENTS, APP_BOOTSTRAP_LISTENER, APP_INITIALIZER, ApplicationRef, ComponentResolver, Injector, NgModuleFactoryLoader, OpaqueToken, SystemJsNgModuleLoader} from '@angular/core';
10+
import {ANALYZE_FOR_ENTRY_COMPONENTS, APP_BOOTSTRAP_LISTENER, APP_INITIALIZER, ApplicationRef, Compiler, ComponentResolver, Injector, NgModuleFactoryLoader, OpaqueToken, SystemJsNgModuleLoader} from '@angular/core';
1111

1212
import {Route, Routes} from './config';
1313
import {Router} from './router';
@@ -30,13 +30,13 @@ export interface ExtraOptions {
3030
export function setupRouter(
3131
ref: ApplicationRef, resolver: ComponentResolver, urlSerializer: UrlSerializer,
3232
outletMap: RouterOutletMap, location: Location, injector: Injector,
33-
loader: NgModuleFactoryLoader, config: Route[][], opts: ExtraOptions = {}) {
33+
loader: NgModuleFactoryLoader, compiler: Compiler, config: Route[][], opts: ExtraOptions = {}) {
3434
if (ref.componentTypes.length == 0) {
3535
throw new Error('Bootstrap at least one component before injecting Router.');
3636
}
3737
const componentType = ref.componentTypes[0];
3838
const r = new Router(
39-
componentType, resolver, urlSerializer, outletMap, location, injector, loader,
39+
componentType, resolver, urlSerializer, outletMap, location, injector, loader, compiler,
4040
flatten(config));
4141

4242
if (opts.enableTracing) {
@@ -92,7 +92,7 @@ export function provideRouter(routes: Routes, config: ExtraOptions = {}): any[]
9292
useFactory: setupRouter,
9393
deps: [
9494
ApplicationRef, ComponentResolver, UrlSerializer, RouterOutletMap, Location, Injector,
95-
NgModuleFactoryLoader, ROUTES, ROUTER_CONFIGURATION
95+
NgModuleFactoryLoader, Compiler, ROUTES, ROUTER_CONFIGURATION
9696
]
9797
},
9898

modules/@angular/router/src/config.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
*/
88

99
import {Type} from '@angular/core';
10+
import {Observable} from 'rxjs/Observable';
1011

1112

1213
/**
@@ -474,6 +475,16 @@ export type ResolveData = {
474475
[name: string]: any
475476
};
476477

478+
/**
479+
* @experimental
480+
*/
481+
export type LoadChildrenCallback = () => Type<any>| Promise<Type<any>>| Observable<Type<any>>;
482+
483+
/**
484+
* @experimental
485+
*/
486+
export type LoadChildren = string | LoadChildrenCallback;
487+
477488
/**
478489
* See {@link Routes} for more details.
479490
* @stable
@@ -496,7 +507,7 @@ export interface Route {
496507
data?: Data;
497508
resolve?: ResolveData;
498509
children?: Route[];
499-
loadChildren?: string;
510+
loadChildren?: LoadChildren;
500511
}
501512

502513
export function validateConfig(config: Routes): void {

modules/@angular/router/src/router.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import 'rxjs/add/operator/reduce';
1313
import 'rxjs/add/operator/every';
1414

1515
import {Location} from '@angular/common';
16-
import {ComponentFactoryResolver, ComponentResolver, Injector, NgModuleFactoryLoader, ReflectiveInjector, Type} from '@angular/core';
16+
import {Compiler, ComponentFactoryResolver, ComponentResolver, Injector, NgModuleFactoryLoader, ReflectiveInjector, Type} from '@angular/core';
1717
import {Observable} from 'rxjs/Observable';
1818
import {Subject} from 'rxjs/Subject';
1919
import {Subscription} from 'rxjs/Subscription';
@@ -148,11 +148,11 @@ export class Router {
148148
private rootComponentType: Type<any>, private resolver: ComponentResolver,
149149
private urlSerializer: UrlSerializer, private outletMap: RouterOutletMap,
150150
private location: Location, private injector: Injector, loader: NgModuleFactoryLoader,
151-
public config: Routes) {
151+
compiler: Compiler, public config: Routes) {
152152
this.resetConfig(config);
153153
this.routerEvents = new Subject<Event>();
154154
this.currentUrlTree = createEmptyUrlTree();
155-
this.configLoader = new RouterConfigLoader(loader);
155+
this.configLoader = new RouterConfigLoader(loader, compiler);
156156
this.currentRouterState = createEmptyState(this.currentUrlTree, this.rootComponentType);
157157
}
158158

modules/@angular/router/src/router_config_loader.ts

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,14 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
import {ComponentFactoryResolver, Injector, NgModuleFactoryLoader, OpaqueToken} from '@angular/core';
9+
import {Compiler, ComponentFactoryResolver, Injector, NgModuleFactory, NgModuleFactoryLoader, OpaqueToken} from '@angular/core';
1010
import {Observable} from 'rxjs/Observable';
1111
import {fromPromise} from 'rxjs/observable/fromPromise';
12+
import {of } from 'rxjs/observable/of';
13+
14+
import {LoadChildren, Route} from './config';
15+
import {flatten, wrapIntoObservable} from './utils/collection';
1216

13-
import {Route} from './config';
14-
import {flatten} from './utils/collection';
1517

1618

1719
/**
@@ -27,13 +29,24 @@ export class LoadedRouterConfig {
2729
}
2830

2931
export class RouterConfigLoader {
30-
constructor(private loader: NgModuleFactoryLoader) {}
32+
constructor(private loader: NgModuleFactoryLoader, private compiler: Compiler) {}
3133

32-
load(parentInjector: Injector, path: string): Observable<LoadedRouterConfig> {
33-
return fromPromise(this.loader.load(path).then(r => {
34+
load(parentInjector: Injector, loadChildren: LoadChildren): Observable<LoadedRouterConfig> {
35+
return this.loadModuleFactory(loadChildren).map(r => {
3436
const ref = r.create(parentInjector);
3537
return new LoadedRouterConfig(
3638
flatten(ref.injector.get(ROUTES)), ref.injector, ref.componentFactoryResolver);
37-
}));
39+
});
3840
}
39-
}
41+
42+
private loadModuleFactory(loadChildren: LoadChildren): Observable<NgModuleFactory<any>> {
43+
if (typeof loadChildren === 'string') {
44+
return fromPromise(this.loader.load(loadChildren));
45+
} else {
46+
const offlineMode = this.compiler instanceof Compiler;
47+
return wrapIntoObservable(loadChildren())
48+
.mergeMap(
49+
t => offlineMode ? of (<any>t) : fromPromise(this.compiler.compileModuleAsync(t)));
50+
}
51+
}
52+
}

modules/@angular/router/src/router_module.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
*/
88

99
import {APP_BASE_HREF, HashLocationStrategy, Location, LocationStrategy, PathLocationStrategy, PlatformLocation} from '@angular/common';
10-
import {ApplicationRef, ComponentResolver, Inject, Injector, ModuleWithProviders, NgModule, NgModuleFactoryLoader, OpaqueToken, Optional, SystemJsNgModuleLoader} from '@angular/core';
10+
import {ApplicationRef, Compiler, ComponentResolver, Inject, Injector, ModuleWithProviders, NgModule, NgModuleFactoryLoader, OpaqueToken, Optional, SystemJsNgModuleLoader} from '@angular/core';
1111

1212
import {ExtraOptions, ROUTER_CONFIGURATION, provideRouterConfig, provideRouterInitializer, provideRoutes, rootRoute, setupRouter} from './common_router_providers';
1313
import {Routes} from './config';
@@ -42,7 +42,7 @@ export const ROUTER_PROVIDERS: any[] = [
4242
useFactory: setupRouter,
4343
deps: [
4444
ApplicationRef, ComponentResolver, UrlSerializer, RouterOutletMap, Location, Injector,
45-
NgModuleFactoryLoader, ROUTES, ROUTER_CONFIGURATION
45+
NgModuleFactoryLoader, Compiler, ROUTES, ROUTER_CONFIGURATION
4646
]
4747
},
4848
RouterOutletMap, {provide: ActivatedRoute, useFactory: rootRoute, deps: [Router]},

modules/@angular/router/src/utils/collection.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ export function andObservables(observables: Observable<Observable<any>>): Observ
122122
return observables.mergeAll().every(result => result === true);
123123
}
124124

125-
export function wrapIntoObservable<T>(value: T | Observable<T>): Observable<T> {
125+
export function wrapIntoObservable<T>(value: T | Promise<T>| Observable<T>): Observable<T> {
126126
if (value instanceof Observable) {
127127
return value;
128128
} else if (value instanceof Promise) {

modules/@angular/router/test/integration.spec.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1608,6 +1608,33 @@ describe('Integration', () => {
16081608
expect(fixture.debugElement.nativeElement).toHaveText('lazy-loaded');
16091609
})));
16101610

1611+
it('works when given a callback',
1612+
fakeAsync(inject(
1613+
[Router, TestComponentBuilder, Location, NgModuleFactoryLoader],
1614+
(router: Router, tcb: TestComponentBuilder, location: Location) => {
1615+
@Component({selector: 'lazy', template: 'lazy-loaded'})
1616+
class LazyLoadedComponent {
1617+
}
1618+
1619+
@NgModule({
1620+
declarations: [LazyLoadedComponent],
1621+
imports: [RouterModule.forChild([{path: 'loaded', component: LazyLoadedComponent}])],
1622+
entryComponents: [LazyLoadedComponent]
1623+
})
1624+
class LoadedModule {
1625+
}
1626+
1627+
const fixture = createRoot(tcb, router, RootCmp);
1628+
1629+
router.resetConfig([{path: 'lazy', loadChildren: () => LoadedModule}]);
1630+
1631+
router.navigateByUrl('/lazy/loaded');
1632+
advance(fixture);
1633+
1634+
expect(location.path()).toEqual('/lazy/loaded');
1635+
expect(fixture.debugElement.nativeElement).toHaveText('lazy-loaded');
1636+
})));
1637+
16111638
it('error emit an error when cannot load a config',
16121639
fakeAsync(inject(
16131640
[Router, TestComponentBuilder, Location, NgModuleFactoryLoader],

modules/@angular/router/testing/router_testing_module.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,11 @@ export class SpyNgModuleFactoryLoader implements NgModuleFactoryLoader {
4040

4141
function setupTestingRouter(
4242
resolver: ComponentResolver, urlSerializer: UrlSerializer, outletMap: RouterOutletMap,
43-
location: Location, loader: NgModuleFactoryLoader, injector: Injector, routes: Route[][]) {
43+
location: Location, loader: NgModuleFactoryLoader, compiler: Compiler, injector: Injector,
44+
routes: Route[][]) {
4445
return new Router(
45-
null, resolver, urlSerializer, outletMap, location, injector, loader, flatten(routes));
46+
null, resolver, urlSerializer, outletMap, location, injector, loader, compiler,
47+
flatten(routes));
4648
}
4749

4850
/**
@@ -75,7 +77,7 @@ function setupTestingRouter(
7577
useFactory: setupTestingRouter,
7678
deps: [
7779
ComponentResolver, UrlSerializer, RouterOutletMap, Location, NgModuleFactoryLoader,
78-
Injector, ROUTES
80+
Compiler, Injector, ROUTES
7981
]
8082
},
8183
]

tools/public_api_guard/router/index.d.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,12 @@ export interface ExtraOptions {
7575
useHash?: boolean;
7676
}
7777

78+
/** @experimental */
79+
export declare type LoadChildren = string | LoadChildrenCallback;
80+
81+
/** @experimental */
82+
export declare type LoadChildrenCallback = () => Type<any> | Promise<Type<any>> | Observable<Type<any>>;
83+
7884
/** @stable */
7985
export declare class NavigationCancel {
8086
id: number;
@@ -156,7 +162,7 @@ export interface Route {
156162
children?: Route[];
157163
component?: Type<any> | string;
158164
data?: Data;
159-
loadChildren?: string;
165+
loadChildren?: LoadChildren;
160166
outlet?: string;
161167
path?: string;
162168
pathMatch?: string;
@@ -172,7 +178,7 @@ export declare class Router {
172178
/** @experimental */ navigated: boolean;
173179
routerState: RouterState;
174180
url: string;
175-
constructor(rootComponentType: Type<any>, resolver: ComponentResolver, urlSerializer: UrlSerializer, outletMap: RouterOutletMap, location: Location, injector: Injector, loader: NgModuleFactoryLoader, config: Routes);
181+
constructor(rootComponentType: Type<any>, resolver: ComponentResolver, urlSerializer: UrlSerializer, outletMap: RouterOutletMap, location: Location, injector: Injector, loader: NgModuleFactoryLoader, compiler: Compiler, config: Routes);
176182
createUrlTree(commands: any[], {relativeTo, queryParams, fragment, preserveQueryParams, preserveFragment}?: NavigationExtras): UrlTree;
177183
dispose(): void;
178184
initialNavigation(): void;

0 commit comments

Comments
 (0)