fix: element injector vs module injector (#15044)
fixes #12869 fixes #12889 fixes #13885 fixes #13870 Before this change there was a single injector tree. Now we have 2 injector trees, one for the modules and one for the components. This fixes lazy loading modules. See the design docs for details: https://docs.google.com/document/d/1OEUIwc-s69l1o97K0wBd_-Lth5BBxir1KuCRWklTlI4 BREAKING CHANGES `ComponentFactory.create()` takes an extra optional `NgModuleRef` parameter. No change should be required in user code as the correct module will be used when none is provided DEPRECATIONS The following methods were used internally and are no more required: - `RouterOutlet.locationFactoryResolver` - `RouterOutlet.locationInjector`
This commit is contained in:
parent
f093501501
commit
13686bb518
|
@ -7,7 +7,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {NgIf} from '@angular/common';
|
import {NgIf} from '@angular/common';
|
||||||
import {ComponentFactory, ComponentRef, Injector, RendererFactory2, RootRenderer, Sanitizer, TemplateRef, ViewContainerRef} from '@angular/core';
|
import {ComponentFactory, ComponentFactoryResolver, ComponentRef, Injector, NgModuleRef, RendererFactory2, RootRenderer, Sanitizer, TemplateRef, ViewContainerRef} from '@angular/core';
|
||||||
import {ArgumentType, BindingType, NodeFlags, ViewDefinition, ViewFlags, anchorDef, createComponentFactory, directiveDef, elementDef, initServicesIfNeeded, textDef, viewDef} from '@angular/core/src/view/index';
|
import {ArgumentType, BindingType, NodeFlags, ViewDefinition, ViewFlags, anchorDef, createComponentFactory, directiveDef, elementDef, initServicesIfNeeded, textDef, viewDef} from '@angular/core/src/view/index';
|
||||||
import {DomRendererFactory2} from '@angular/platform-browser/src/dom/dom_renderer';
|
import {DomRendererFactory2} from '@angular/platform-browser/src/dom/dom_renderer';
|
||||||
import {DomSanitizerImpl, SafeStyle} from '@angular/platform-browser/src/security/dom_sanitization_service';
|
import {DomSanitizerImpl, SafeStyle} from '@angular/platform-browser/src/security/dom_sanitization_service';
|
||||||
|
@ -84,7 +84,7 @@ function TreeComponent_0(): ViewDefinition {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export class AppModule implements Injector {
|
export class AppModule implements Injector, NgModuleRef<any> {
|
||||||
private sanitizer: DomSanitizerImpl;
|
private sanitizer: DomSanitizerImpl;
|
||||||
private componentFactory: ComponentFactory<TreeComponent>;
|
private componentFactory: ComponentFactory<TreeComponent>;
|
||||||
private renderer2: RendererFactory2;
|
private renderer2: RendererFactory2;
|
||||||
|
@ -108,12 +108,22 @@ export class AppModule implements Injector {
|
||||||
return this.sanitizer;
|
return this.sanitizer;
|
||||||
case RootRenderer:
|
case RootRenderer:
|
||||||
return null;
|
return null;
|
||||||
|
case NgModuleRef:
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
return Injector.NULL.get(token, notFoundValue);
|
return Injector.NULL.get(token, notFoundValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
bootstrap() {
|
bootstrap() {
|
||||||
this.componentRef = this.componentFactory.create(this, [], this.componentFactory.selector);
|
this.componentRef =
|
||||||
|
this.componentFactory.create(Injector.NULL, [], this.componentFactory.selector, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
tick() { this.componentRef.changeDetectorRef.detectChanges(); }
|
tick() { this.componentRef.changeDetectorRef.detectChanges(); }
|
||||||
|
|
||||||
|
get injector() { return this; }
|
||||||
|
get componentFactoryResolver(): ComponentFactoryResolver { return null; }
|
||||||
|
get instance() { return this; }
|
||||||
|
destroy() {}
|
||||||
|
onDestroy(callback: () => void) {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,8 +8,6 @@
|
||||||
|
|
||||||
import {ComponentFactoryResolver, ComponentRef, Directive, Injector, Input, NgModuleFactory, NgModuleRef, OnChanges, OnDestroy, Provider, SimpleChanges, Type, ViewContainerRef} from '@angular/core';
|
import {ComponentFactoryResolver, ComponentRef, Directive, Injector, Input, NgModuleFactory, NgModuleRef, OnChanges, OnDestroy, Provider, SimpleChanges, Type, ViewContainerRef} from '@angular/core';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Instantiates a single {@link Component} type and inserts its Host View into current View.
|
* Instantiates a single {@link Component} type and inserts its Host View into current View.
|
||||||
* `NgComponentOutlet` provides a declarative approach for dynamic component creation.
|
* `NgComponentOutlet` provides a declarative approach for dynamic component creation.
|
||||||
|
@ -81,34 +79,35 @@ export class NgComponentOutlet implements OnChanges, OnDestroy {
|
||||||
constructor(private _viewContainerRef: ViewContainerRef) {}
|
constructor(private _viewContainerRef: ViewContainerRef) {}
|
||||||
|
|
||||||
ngOnChanges(changes: SimpleChanges) {
|
ngOnChanges(changes: SimpleChanges) {
|
||||||
if (this._componentRef) {
|
|
||||||
this._viewContainerRef.remove(this._viewContainerRef.indexOf(this._componentRef.hostView));
|
|
||||||
}
|
|
||||||
this._viewContainerRef.clear();
|
this._viewContainerRef.clear();
|
||||||
this._componentRef = null;
|
this._componentRef = null;
|
||||||
|
|
||||||
if (this.ngComponentOutlet) {
|
if (this.ngComponentOutlet) {
|
||||||
let injector = this.ngComponentOutletInjector || this._viewContainerRef.parentInjector;
|
const elInjector = this.ngComponentOutletInjector || this._viewContainerRef.parentInjector;
|
||||||
|
|
||||||
if ((changes as any).ngComponentOutletNgModuleFactory) {
|
if (changes['ngComponentOutletNgModuleFactory']) {
|
||||||
if (this._moduleRef) this._moduleRef.destroy();
|
if (this._moduleRef) this._moduleRef.destroy();
|
||||||
|
|
||||||
if (this.ngComponentOutletNgModuleFactory) {
|
if (this.ngComponentOutletNgModuleFactory) {
|
||||||
this._moduleRef = this.ngComponentOutletNgModuleFactory.create(injector);
|
const parentModule = elInjector.get(NgModuleRef);
|
||||||
|
this._moduleRef = this.ngComponentOutletNgModuleFactory.create(parentModule.injector);
|
||||||
} else {
|
} else {
|
||||||
this._moduleRef = null;
|
this._moduleRef = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this._moduleRef) {
|
|
||||||
injector = this._moduleRef.injector;
|
|
||||||
}
|
|
||||||
|
|
||||||
let componentFactory =
|
const componentFactoryResolver = this._moduleRef ? this._moduleRef.componentFactoryResolver :
|
||||||
injector.get(ComponentFactoryResolver).resolveComponentFactory(this.ngComponentOutlet);
|
elInjector.get(ComponentFactoryResolver);
|
||||||
|
|
||||||
|
const componentFactory =
|
||||||
|
componentFactoryResolver.resolveComponentFactory(this.ngComponentOutlet);
|
||||||
|
|
||||||
this._componentRef = this._viewContainerRef.createComponent(
|
this._componentRef = this._viewContainerRef.createComponent(
|
||||||
componentFactory, this._viewContainerRef.length, injector, this.ngComponentOutletContent);
|
componentFactory, this._viewContainerRef.length, elInjector,
|
||||||
|
this.ngComponentOutletContent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy() {
|
ngOnDestroy() {
|
||||||
if (this._moduleRef) this._moduleRef.destroy();
|
if (this._moduleRef) this._moduleRef.destroy();
|
||||||
}
|
}
|
||||||
|
|
|
@ -177,13 +177,13 @@ export function main() {
|
||||||
it('should not re-create moduleRef when it didn\'t actually change', async(() => {
|
it('should not re-create moduleRef when it didn\'t actually change', async(() => {
|
||||||
const compiler = TestBed.get(Compiler) as Compiler;
|
const compiler = TestBed.get(Compiler) as Compiler;
|
||||||
const fixture = TestBed.createComponent(TestComponent);
|
const fixture = TestBed.createComponent(TestComponent);
|
||||||
|
|
||||||
fixture.componentInstance.module = compiler.compileModuleSync(TestModule2);
|
fixture.componentInstance.module = compiler.compileModuleSync(TestModule2);
|
||||||
fixture.componentInstance.currentComponent = Module2InjectedComponent;
|
fixture.componentInstance.currentComponent = Module2InjectedComponent;
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
expect(fixture.nativeElement).toHaveText('baz');
|
expect(fixture.nativeElement).toHaveText('baz');
|
||||||
|
|
||||||
const moduleRef = fixture.componentInstance.ngComponentOutlet['_moduleRef'];
|
const moduleRef = fixture.componentInstance.ngComponentOutlet['_moduleRef'];
|
||||||
|
|
||||||
fixture.componentInstance.currentComponent = Module2InjectedComponent2;
|
fixture.componentInstance.currentComponent = Module2InjectedComponent2;
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
@ -247,11 +247,11 @@ class TestComponent {
|
||||||
export class TestModule {
|
export class TestModule {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({selector: 'mdoule-2-injected-component', template: 'baz'})
|
@Component({selector: 'module-2-injected-component', template: 'baz'})
|
||||||
class Module2InjectedComponent {
|
class Module2InjectedComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({selector: 'mdoule-2-injected-component-2', template: 'baz2'})
|
@Component({selector: 'module-2-injected-component-2', template: 'baz2'})
|
||||||
class Module2InjectedComponent2 {
|
class Module2InjectedComponent2 {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -264,7 +264,7 @@ class Module2InjectedComponent2 {
|
||||||
export class TestModule2 {
|
export class TestModule2 {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({selector: 'mdoule-3-injected-component', template: 'bat'})
|
@Component({selector: 'module-3-injected-component', template: 'bat'})
|
||||||
class Module3InjectedComponent {
|
class Module3InjectedComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {ANALYZE_FOR_ENTRY_COMPONENTS, ChangeDetectionStrategy, ChangeDetectorRef, ComponentFactory, ComponentFactoryResolver, ComponentRef, ElementRef, Injector, LOCALE_ID, NgModuleFactory, QueryList, RenderComponentType, Renderer, SecurityContext, SimpleChange, TRANSLATIONS_FORMAT, TemplateRef, ViewContainerRef, ViewEncapsulation, ɵChangeDetectorStatus, ɵCodegenComponentFactoryResolver, ɵEMPTY_ARRAY, ɵEMPTY_MAP, ɵNgModuleInjector, ɵValueUnwrapper, ɵand, ɵccf, ɵcrt, ɵdevModeEqual, ɵdid, ɵeld, ɵinlineInterpolate, ɵinterpolate, ɵncd, ɵnov, ɵpad, ɵpid, ɵpod, ɵppd, ɵprd, ɵqud, ɵreflector, ɵregisterModuleFactory, ɵted, ɵunv, ɵvid} from '@angular/core';
|
import {ANALYZE_FOR_ENTRY_COMPONENTS, ChangeDetectionStrategy, ChangeDetectorRef, ComponentFactory, ComponentFactoryResolver, ComponentRef, ElementRef, Injector, LOCALE_ID, NgModuleFactory, NgModuleRef, QueryList, RenderComponentType, Renderer, SecurityContext, SimpleChange, TRANSLATIONS_FORMAT, TemplateRef, ViewContainerRef, ViewEncapsulation, ɵChangeDetectorStatus, ɵCodegenComponentFactoryResolver, ɵEMPTY_ARRAY, ɵEMPTY_MAP, ɵNgModuleInjector, ɵValueUnwrapper, ɵand, ɵccf, ɵcrt, ɵdevModeEqual, ɵdid, ɵeld, ɵinlineInterpolate, ɵinterpolate, ɵncd, ɵnov, ɵpad, ɵpid, ɵpod, ɵppd, ɵprd, ɵqud, ɵreflector, ɵregisterModuleFactory, ɵted, ɵunv, ɵvid} from '@angular/core';
|
||||||
|
|
||||||
import {CompileIdentifierMetadata, CompileTokenMetadata} from './compile_metadata';
|
import {CompileIdentifierMetadata, CompileTokenMetadata} from './compile_metadata';
|
||||||
|
|
||||||
|
@ -26,6 +26,7 @@ export class Identifiers {
|
||||||
runtime: ANALYZE_FOR_ENTRY_COMPONENTS
|
runtime: ANALYZE_FOR_ENTRY_COMPONENTS
|
||||||
};
|
};
|
||||||
static ElementRef: IdentifierSpec = {name: 'ElementRef', moduleUrl: CORE, runtime: ElementRef};
|
static ElementRef: IdentifierSpec = {name: 'ElementRef', moduleUrl: CORE, runtime: ElementRef};
|
||||||
|
static NgModuleRef: IdentifierSpec = {name: 'NgModuleRef', moduleUrl: CORE, runtime: NgModuleRef};
|
||||||
static ViewContainerRef:
|
static ViewContainerRef:
|
||||||
IdentifierSpec = {name: 'ViewContainerRef', moduleUrl: CORE, runtime: ViewContainerRef};
|
IdentifierSpec = {name: 'ViewContainerRef', moduleUrl: CORE, runtime: ViewContainerRef};
|
||||||
static ChangeDetectorRef:
|
static ChangeDetectorRef:
|
||||||
|
|
|
@ -216,11 +216,15 @@ class _InjectorBuilder implements ClassBuilder {
|
||||||
result = o.literal(dep.value);
|
result = o.literal(dep.value);
|
||||||
}
|
}
|
||||||
if (!dep.isSkipSelf) {
|
if (!dep.isSkipSelf) {
|
||||||
if (dep.token &&
|
if (dep.token) {
|
||||||
(tokenReference(dep.token) === resolveIdentifier(Identifiers.Injector) ||
|
if (tokenReference(dep.token) === resolveIdentifier(Identifiers.Injector)) {
|
||||||
tokenReference(dep.token) === resolveIdentifier(Identifiers.ComponentFactoryResolver))) {
|
result = o.THIS_EXPR;
|
||||||
result = o.THIS_EXPR;
|
} else if (
|
||||||
|
tokenReference(dep.token) === resolveIdentifier(Identifiers.ComponentFactoryResolver)) {
|
||||||
|
result = o.THIS_EXPR.prop('componentFactoryResolver');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!result) {
|
if (!result) {
|
||||||
result = this._instances.get(tokenReference(dep.token));
|
result = this._instances.get(tokenReference(dep.token));
|
||||||
}
|
}
|
||||||
|
|
|
@ -1125,13 +1125,14 @@ function createComponentFactoryResolver(directives: DirectiveAst[]): ProviderAst
|
||||||
if (componentDirMeta && componentDirMeta.directive.entryComponents.length) {
|
if (componentDirMeta && componentDirMeta.directive.entryComponents.length) {
|
||||||
const entryComponentFactories = componentDirMeta.directive.entryComponents.map(
|
const entryComponentFactories = componentDirMeta.directive.entryComponents.map(
|
||||||
(entryComponent) => o.importExpr({reference: entryComponent.componentFactory}));
|
(entryComponent) => o.importExpr({reference: entryComponent.componentFactory}));
|
||||||
const cfrExpr = o.importExpr(createIdentifier(Identifiers.CodegenComponentFactoryResolver))
|
|
||||||
.instantiate([o.literalArr(entryComponentFactories)]);
|
|
||||||
const token = createIdentifierToken(Identifiers.ComponentFactoryResolver);
|
const token = createIdentifierToken(Identifiers.ComponentFactoryResolver);
|
||||||
|
|
||||||
const classMeta: CompileTypeMetadata = {
|
const classMeta: CompileTypeMetadata = {
|
||||||
diDeps: [
|
diDeps: [
|
||||||
{isValue: true, value: o.literalArr(entryComponentFactories)},
|
{isValue: true, value: o.literalArr(entryComponentFactories)},
|
||||||
{token: token, isSkipSelf: true, isOptional: true}
|
{token: token, isSkipSelf: true, isOptional: true},
|
||||||
|
{token: createIdentifierToken(Identifiers.NgModuleRef)},
|
||||||
],
|
],
|
||||||
lifecycleHooks: [],
|
lifecycleHooks: [],
|
||||||
reference: resolveIdentifier(Identifiers.CodegenComponentFactoryResolver)
|
reference: resolveIdentifier(Identifiers.CodegenComponentFactoryResolver)
|
||||||
|
|
|
@ -8,20 +8,21 @@
|
||||||
|
|
||||||
import {Observable} from 'rxjs/Observable';
|
import {Observable} from 'rxjs/Observable';
|
||||||
import {Observer} from 'rxjs/Observer';
|
import {Observer} from 'rxjs/Observer';
|
||||||
import {Subject} from 'rxjs/Subject';
|
|
||||||
import {Subscription} from 'rxjs/Subscription';
|
import {Subscription} from 'rxjs/Subscription';
|
||||||
import {merge} from 'rxjs/observable/merge';
|
import {merge} from 'rxjs/observable/merge';
|
||||||
import {share} from 'rxjs/operator/share';
|
import {share} from 'rxjs/operator/share';
|
||||||
|
|
||||||
import {ErrorHandler} from '../src/error_handler';
|
import {ErrorHandler} from '../src/error_handler';
|
||||||
import {scheduleMicroTask, stringify} from '../src/util';
|
import {scheduleMicroTask, stringify} from '../src/util';
|
||||||
import {isPromise} from '../src/util/lang';
|
import {isPromise} from '../src/util/lang';
|
||||||
|
|
||||||
import {ApplicationInitStatus} from './application_init';
|
import {ApplicationInitStatus} from './application_init';
|
||||||
import {APP_BOOTSTRAP_LISTENER, PLATFORM_INITIALIZER} from './application_tokens';
|
import {APP_BOOTSTRAP_LISTENER, PLATFORM_INITIALIZER} from './application_tokens';
|
||||||
import {Console} from './console';
|
import {Console} from './console';
|
||||||
import {Injectable, InjectionToken, Injector, Optional, Provider, ReflectiveInjector} from './di';
|
import {Injectable, InjectionToken, Injector, Provider, ReflectiveInjector} from './di';
|
||||||
import {CompilerFactory, CompilerOptions} from './linker/compiler';
|
import {CompilerFactory, CompilerOptions} from './linker/compiler';
|
||||||
import {ComponentFactory, ComponentRef} from './linker/component_factory';
|
import {ComponentFactory, ComponentRef} from './linker/component_factory';
|
||||||
import {ComponentFactoryResolver} from './linker/component_factory_resolver';
|
import {ComponentFactoryBoundToModule, ComponentFactoryResolver} from './linker/component_factory_resolver';
|
||||||
import {NgModuleFactory, NgModuleInjector, NgModuleRef} from './linker/ng_module_factory';
|
import {NgModuleFactory, NgModuleInjector, NgModuleRef} from './linker/ng_module_factory';
|
||||||
import {InternalViewRef, ViewRef} from './linker/view_ref';
|
import {InternalViewRef, ViewRef} from './linker/view_ref';
|
||||||
import {WtfScopeFn, wtfCreateScope, wtfLeave} from './profile/profile';
|
import {WtfScopeFn, wtfCreateScope, wtfLeave} from './profile/profile';
|
||||||
|
@ -328,7 +329,7 @@ export class PlatformRef_ extends PlatformRef {
|
||||||
private _moduleDoBootstrap(moduleRef: NgModuleInjector<any>): void {
|
private _moduleDoBootstrap(moduleRef: NgModuleInjector<any>): void {
|
||||||
const appRef = moduleRef.injector.get(ApplicationRef);
|
const appRef = moduleRef.injector.get(ApplicationRef);
|
||||||
if (moduleRef.bootstrapFactories.length > 0) {
|
if (moduleRef.bootstrapFactories.length > 0) {
|
||||||
moduleRef.bootstrapFactories.forEach((compFactory) => appRef.bootstrap(compFactory));
|
moduleRef.bootstrapFactories.forEach(f => appRef.bootstrap(f));
|
||||||
} else if (moduleRef.instance.ngDoBootstrap) {
|
} else if (moduleRef.instance.ngDoBootstrap) {
|
||||||
moduleRef.instance.ngDoBootstrap(appRef);
|
moduleRef.instance.ngDoBootstrap(appRef);
|
||||||
} else {
|
} else {
|
||||||
|
@ -502,7 +503,13 @@ export class ApplicationRef_ extends ApplicationRef {
|
||||||
componentFactory = this._componentFactoryResolver.resolveComponentFactory(componentOrFactory);
|
componentFactory = this._componentFactoryResolver.resolveComponentFactory(componentOrFactory);
|
||||||
}
|
}
|
||||||
this._rootComponentTypes.push(componentFactory.componentType);
|
this._rootComponentTypes.push(componentFactory.componentType);
|
||||||
const compRef = componentFactory.create(this._injector, [], componentFactory.selector);
|
|
||||||
|
// Create a factory associated with the current module if it's not bound to some other
|
||||||
|
const ngModule = componentFactory instanceof ComponentFactoryBoundToModule ?
|
||||||
|
null :
|
||||||
|
this._injector.get(NgModuleRef);
|
||||||
|
const compRef = componentFactory.create(Injector.NULL, [], componentFactory.selector, ngModule);
|
||||||
|
|
||||||
compRef.onDestroy(() => { this._unloadComponent(compRef); });
|
compRef.onDestroy(() => { this._unloadComponent(compRef); });
|
||||||
const testability = compRef.injector.get(Testability, null);
|
const testability = compRef.injector.get(Testability, null);
|
||||||
if (testability) {
|
if (testability) {
|
||||||
|
|
|
@ -11,6 +11,7 @@ import {Injector} from '../di/injector';
|
||||||
import {Type} from '../type';
|
import {Type} from '../type';
|
||||||
|
|
||||||
import {ElementRef} from './element_ref';
|
import {ElementRef} from './element_ref';
|
||||||
|
import {NgModuleRef} from './ng_module_factory';
|
||||||
import {ViewRef} from './view_ref';
|
import {ViewRef} from './view_ref';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -72,6 +73,7 @@ export abstract class ComponentFactory<C> {
|
||||||
/**
|
/**
|
||||||
* Creates a new component.
|
* Creates a new component.
|
||||||
*/
|
*/
|
||||||
abstract create(injector: Injector, projectableNodes?: any[][], rootSelectorOrNode?: string|any):
|
abstract create(
|
||||||
ComponentRef<C>;
|
injector: Injector, projectableNodes?: any[][], rootSelectorOrNode?: string|any,
|
||||||
|
ngModule?: NgModuleRef<any>): ComponentRef<C>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,12 +6,12 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import {Injector} from '../di/injector';
|
||||||
import {Type} from '../type';
|
import {Type} from '../type';
|
||||||
import {stringify} from '../util';
|
import {stringify} from '../util';
|
||||||
|
|
||||||
import {ComponentFactory} from './component_factory';
|
import {ComponentFactory, ComponentRef} from './component_factory';
|
||||||
|
import {NgModuleRef} from './ng_module_factory';
|
||||||
|
|
||||||
|
|
||||||
export function noComponentFactoryError(component: Function) {
|
export function noComponentFactoryError(component: Function) {
|
||||||
const error = Error(
|
const error = Error(
|
||||||
|
@ -44,7 +44,9 @@ export abstract class ComponentFactoryResolver {
|
||||||
export class CodegenComponentFactoryResolver implements ComponentFactoryResolver {
|
export class CodegenComponentFactoryResolver implements ComponentFactoryResolver {
|
||||||
private _factories = new Map<any, ComponentFactory<any>>();
|
private _factories = new Map<any, ComponentFactory<any>>();
|
||||||
|
|
||||||
constructor(factories: ComponentFactory<any>[], private _parent: ComponentFactoryResolver) {
|
constructor(
|
||||||
|
factories: ComponentFactory<any>[], private _parent: ComponentFactoryResolver,
|
||||||
|
private _ngModule: NgModuleRef<any>) {
|
||||||
for (let i = 0; i < factories.length; i++) {
|
for (let i = 0; i < factories.length; i++) {
|
||||||
const factory = factories[i];
|
const factory = factories[i];
|
||||||
this._factories.set(factory.componentType, factory);
|
this._factories.set(factory.componentType, factory);
|
||||||
|
@ -52,10 +54,22 @@ export class CodegenComponentFactoryResolver implements ComponentFactoryResolver
|
||||||
}
|
}
|
||||||
|
|
||||||
resolveComponentFactory<T>(component: {new (...args: any[]): T}): ComponentFactory<T> {
|
resolveComponentFactory<T>(component: {new (...args: any[]): T}): ComponentFactory<T> {
|
||||||
let result = this._factories.get(component);
|
let factory = this._factories.get(component) || this._parent.resolveComponentFactory(component);
|
||||||
if (!result) {
|
|
||||||
result = this._parent.resolveComponentFactory(component);
|
return factory ? new ComponentFactoryBoundToModule(factory, this._ngModule) : null;
|
||||||
}
|
}
|
||||||
return result;
|
}
|
||||||
|
|
||||||
|
export class ComponentFactoryBoundToModule<C> extends ComponentFactory<C> {
|
||||||
|
constructor(private factory: ComponentFactory<C>, private ngModule: NgModuleRef<any>) { super(); }
|
||||||
|
|
||||||
|
get selector() { return this.factory.selector; }
|
||||||
|
get componentType() { return this.factory.componentType; }
|
||||||
|
|
||||||
|
create(
|
||||||
|
injector: Injector, projectableNodes?: any[][], rootSelectorOrNode?: string|any,
|
||||||
|
ngModule?: NgModuleRef<any>): ComponentRef<C> {
|
||||||
|
return this.factory.create(
|
||||||
|
injector, projectableNodes, rootSelectorOrNode, ngModule || this.ngModule);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,8 +11,7 @@ import {Type} from '../type';
|
||||||
import {stringify} from '../util';
|
import {stringify} from '../util';
|
||||||
|
|
||||||
import {ComponentFactory} from './component_factory';
|
import {ComponentFactory} from './component_factory';
|
||||||
import {CodegenComponentFactoryResolver, ComponentFactoryResolver} from './component_factory_resolver';
|
import {CodegenComponentFactoryResolver, ComponentFactoryBoundToModule, ComponentFactoryResolver} from './component_factory_resolver';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -62,10 +61,7 @@ export class NgModuleFactory<T> {
|
||||||
get moduleType(): Type<T> { return this._moduleType; }
|
get moduleType(): Type<T> { return this._moduleType; }
|
||||||
|
|
||||||
create(parentInjector: Injector): NgModuleRef<T> {
|
create(parentInjector: Injector): NgModuleRef<T> {
|
||||||
if (!parentInjector) {
|
const instance = new this._injectorClass(parentInjector || Injector.NULL);
|
||||||
parentInjector = Injector.NULL;
|
|
||||||
}
|
|
||||||
const instance = new this._injectorClass(parentInjector);
|
|
||||||
instance.create();
|
instance.create();
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
@ -73,18 +69,21 @@ export class NgModuleFactory<T> {
|
||||||
|
|
||||||
const _UNDEFINED = new Object();
|
const _UNDEFINED = new Object();
|
||||||
|
|
||||||
export abstract class NgModuleInjector<T> extends CodegenComponentFactoryResolver implements
|
export abstract class NgModuleInjector<T> implements Injector, NgModuleRef<T> {
|
||||||
Injector,
|
bootstrapFactories: ComponentFactory<any>[];
|
||||||
NgModuleRef<T> {
|
instance: T;
|
||||||
|
|
||||||
private _destroyListeners: (() => void)[] = [];
|
private _destroyListeners: (() => void)[] = [];
|
||||||
private _destroyed: boolean = false;
|
private _destroyed: boolean = false;
|
||||||
|
private _cmpFactoryResolver: CodegenComponentFactoryResolver;
|
||||||
public instance: T;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public parent: Injector, factories: ComponentFactory<any>[],
|
public parent: Injector, factories: ComponentFactory<any>[],
|
||||||
public bootstrapFactories: ComponentFactory<any>[]) {
|
bootstrapFactories: ComponentFactory<any>[]) {
|
||||||
super(factories, parent.get(ComponentFactoryResolver, ComponentFactoryResolver.NULL));
|
this.bootstrapFactories =
|
||||||
|
bootstrapFactories.map(f => new ComponentFactoryBoundToModule(f, this));
|
||||||
|
this._cmpFactoryResolver = new CodegenComponentFactoryResolver(
|
||||||
|
factories, parent.get(ComponentFactoryResolver, ComponentFactoryResolver.NULL), this);
|
||||||
}
|
}
|
||||||
|
|
||||||
create() { this.instance = this.createInternal(); }
|
create() { this.instance = this.createInternal(); }
|
||||||
|
@ -92,9 +91,14 @@ export abstract class NgModuleInjector<T> extends CodegenComponentFactoryResolve
|
||||||
abstract createInternal(): T;
|
abstract createInternal(): T;
|
||||||
|
|
||||||
get(token: any, notFoundValue: any = THROW_IF_NOT_FOUND): any {
|
get(token: any, notFoundValue: any = THROW_IF_NOT_FOUND): any {
|
||||||
if (token === Injector || token === ComponentFactoryResolver) {
|
if (token === Injector || token === NgModuleRef) {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (token === ComponentFactoryResolver) {
|
||||||
|
return this._cmpFactoryResolver;
|
||||||
|
}
|
||||||
|
|
||||||
const result = this.getInternal(token, _UNDEFINED);
|
const result = this.getInternal(token, _UNDEFINED);
|
||||||
return result === _UNDEFINED ? this.parent.get(token, notFoundValue) : result;
|
return result === _UNDEFINED ? this.parent.get(token, notFoundValue) : result;
|
||||||
}
|
}
|
||||||
|
@ -103,7 +107,7 @@ export abstract class NgModuleInjector<T> extends CodegenComponentFactoryResolve
|
||||||
|
|
||||||
get injector(): Injector { return this; }
|
get injector(): Injector { return this; }
|
||||||
|
|
||||||
get componentFactoryResolver(): ComponentFactoryResolver { return this; }
|
get componentFactoryResolver(): ComponentFactoryResolver { return this._cmpFactoryResolver; }
|
||||||
|
|
||||||
destroy(): void {
|
destroy(): void {
|
||||||
if (this._destroyed) {
|
if (this._destroyed) {
|
||||||
|
|
|
@ -133,7 +133,6 @@ export function createPipeInstance(view: ViewData, def: NodeDef): any {
|
||||||
export function createDirectiveInstance(view: ViewData, def: NodeDef): any {
|
export function createDirectiveInstance(view: ViewData, def: NodeDef): any {
|
||||||
// components can see other private services, other directives can't.
|
// components can see other private services, other directives can't.
|
||||||
const allowPrivateServices = (def.flags & NodeFlags.Component) > 0;
|
const allowPrivateServices = (def.flags & NodeFlags.Component) > 0;
|
||||||
const providerDef = def.provider;
|
|
||||||
// directives are always eager and classes!
|
// directives are always eager and classes!
|
||||||
const instance =
|
const instance =
|
||||||
createClass(view, def.parent, allowPrivateServices, def.provider.value, def.provider.deps);
|
createClass(view, def.parent, allowPrivateServices, def.provider.value, def.provider.deps);
|
||||||
|
@ -325,6 +324,25 @@ function callFactory(
|
||||||
return injectable;
|
return injectable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This default value is when checking the hierarchy for a token.
|
||||||
|
//
|
||||||
|
// It means both:
|
||||||
|
// - the token is not provided by the current injector,
|
||||||
|
// - only the element injectors should be checked (ie do not check module injectors
|
||||||
|
//
|
||||||
|
// mod1
|
||||||
|
// /
|
||||||
|
// el1 mod2
|
||||||
|
// \ /
|
||||||
|
// el2
|
||||||
|
//
|
||||||
|
// When requesting el2.injector.get(token), we should check in the following order and return the
|
||||||
|
// first found value:
|
||||||
|
// - el2.injector.get(token, default)
|
||||||
|
// - el1.injector.get(token, NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR) -> do not check the module
|
||||||
|
// - mod2.injector.get(token, default)
|
||||||
|
const NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR = {};
|
||||||
|
|
||||||
export function resolveDep(
|
export function resolveDep(
|
||||||
view: ViewData, elDef: NodeDef, allowPrivateServices: boolean, depDef: DepDef,
|
view: ViewData, elDef: NodeDef, allowPrivateServices: boolean, depDef: DepDef,
|
||||||
notFoundValue = Injector.THROW_IF_NOT_FOUND): any {
|
notFoundValue = Injector.THROW_IF_NOT_FOUND): any {
|
||||||
|
@ -386,7 +404,20 @@ export function resolveDep(
|
||||||
elDef = viewParentEl(view);
|
elDef = viewParentEl(view);
|
||||||
view = view.parent;
|
view = view.parent;
|
||||||
}
|
}
|
||||||
return startView.root.injector.get(depDef.token, notFoundValue);
|
|
||||||
|
const value = startView.root.injector.get(depDef.token, NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR);
|
||||||
|
|
||||||
|
if (value !== NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR ||
|
||||||
|
notFoundValue === NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR) {
|
||||||
|
// Return the value from the root element injector when
|
||||||
|
// - it provides it
|
||||||
|
// (value !== NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR)
|
||||||
|
// - the module injector should not be checked
|
||||||
|
// (notFoundValue === NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR)
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return startView.root.ngModule.injector.get(depDef.token, notFoundValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
function findCompView(view: ViewData, elDef: NodeDef, allowPrivateServices: boolean) {
|
function findCompView(view: ViewData, elDef: NodeDef, allowPrivateServices: boolean) {
|
||||||
|
|
|
@ -11,6 +11,7 @@ import {ChangeDetectorRef} from '../change_detection/change_detection';
|
||||||
import {Injector} from '../di';
|
import {Injector} from '../di';
|
||||||
import {ComponentFactory, ComponentRef} from '../linker/component_factory';
|
import {ComponentFactory, ComponentRef} from '../linker/component_factory';
|
||||||
import {ElementRef} from '../linker/element_ref';
|
import {ElementRef} from '../linker/element_ref';
|
||||||
|
import {NgModuleRef} from '../linker/ng_module_factory';
|
||||||
import {TemplateRef} from '../linker/template_ref';
|
import {TemplateRef} from '../linker/template_ref';
|
||||||
import {ViewContainerRef} from '../linker/view_container_ref';
|
import {ViewContainerRef} from '../linker/view_container_ref';
|
||||||
import {EmbeddedViewRef, InternalViewRef, ViewRef} from '../linker/view_ref';
|
import {EmbeddedViewRef, InternalViewRef, ViewRef} from '../linker/view_ref';
|
||||||
|
@ -52,12 +53,15 @@ class ComponentFactory_ extends ComponentFactory<any> {
|
||||||
* Creates a new component.
|
* Creates a new component.
|
||||||
*/
|
*/
|
||||||
create(
|
create(
|
||||||
injector: Injector, projectableNodes: any[][] = null,
|
injector: Injector, projectableNodes?: any[][], rootSelectorOrNode?: string|any,
|
||||||
rootSelectorOrNode: string|any = null): ComponentRef<any> {
|
ngModule?: NgModuleRef<any>): ComponentRef<any> {
|
||||||
|
if (!ngModule) {
|
||||||
|
throw new Error('ngModule should be provided');
|
||||||
|
}
|
||||||
const viewDef = resolveViewDefinition(this.viewDefFactory);
|
const viewDef = resolveViewDefinition(this.viewDefFactory);
|
||||||
const componentNodeIndex = viewDef.nodes[0].element.componentProvider.index;
|
const componentNodeIndex = viewDef.nodes[0].element.componentProvider.index;
|
||||||
const view = Services.createRootView(
|
const view = Services.createRootView(
|
||||||
injector, projectableNodes || [], rootSelectorOrNode, viewDef, EMPTY_CONTEXT);
|
injector, projectableNodes || [], rootSelectorOrNode, viewDef, ngModule, EMPTY_CONTEXT);
|
||||||
const component = asProviderData(view, componentNodeIndex).instance;
|
const component = asProviderData(view, componentNodeIndex).instance;
|
||||||
view.renderer.setAttribute(asElementData(view, 0).renderElement, 'ng-version', VERSION.full);
|
view.renderer.setAttribute(asElementData(view, 0).renderElement, 'ng-version', VERSION.full);
|
||||||
|
|
||||||
|
@ -107,6 +111,7 @@ class ViewContainerRef_ implements ViewContainerData {
|
||||||
elDef = viewParentEl(view);
|
elDef = viewParentEl(view);
|
||||||
view = view.parent;
|
view = view.parent;
|
||||||
}
|
}
|
||||||
|
|
||||||
return view ? new Injector_(view, elDef) : this._view.root.injector;
|
return view ? new Injector_(view, elDef) : this._view.root.injector;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
import {isDevMode} from '../application_ref';
|
import {isDevMode} from '../application_ref';
|
||||||
import {DebugElement, DebugNode, EventListener, getDebugNode, indexDebugNode, removeDebugNodeFromIndex} from '../debug/debug_node';
|
import {DebugElement, DebugNode, EventListener, getDebugNode, indexDebugNode, removeDebugNodeFromIndex} from '../debug/debug_node';
|
||||||
import {Injector} from '../di';
|
import {Injector} from '../di';
|
||||||
|
import {NgModuleRef} from '../linker/ng_module_factory';
|
||||||
import {Renderer2, RendererFactory2, RendererStyleFlags2, RendererType2} from '../render/api';
|
import {Renderer2, RendererFactory2, RendererStyleFlags2, RendererType2} from '../render/api';
|
||||||
import {Sanitizer, SecurityContext} from '../security';
|
import {Sanitizer, SecurityContext} from '../security';
|
||||||
|
|
||||||
|
@ -20,6 +21,7 @@ import {ArgumentType, BindingType, CheckType, DebugContext, DepFlags, ElementDat
|
||||||
import {NOOP, checkBinding, isComponentView, renderNode, viewParentEl} from './util';
|
import {NOOP, checkBinding, isComponentView, renderNode, viewParentEl} from './util';
|
||||||
import {checkAndUpdateNode, checkAndUpdateView, checkNoChangesNode, checkNoChangesView, createEmbeddedView, createRootView, destroyView} from './view';
|
import {checkAndUpdateNode, checkAndUpdateView, checkNoChangesNode, checkNoChangesView, createEmbeddedView, createRootView, destroyView} from './view';
|
||||||
|
|
||||||
|
|
||||||
let initialized = false;
|
let initialized = false;
|
||||||
|
|
||||||
export function initServicesIfNeeded() {
|
export function initServicesIfNeeded() {
|
||||||
|
@ -80,31 +82,32 @@ function createDebugServices() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function createProdRootView(
|
function createProdRootView(
|
||||||
injector: Injector, projectableNodes: any[][], rootSelectorOrNode: string | any,
|
elInjector: Injector, projectableNodes: any[][], rootSelectorOrNode: string | any,
|
||||||
def: ViewDefinition, context?: any): ViewData {
|
def: ViewDefinition, ngModule: NgModuleRef<any>, context?: any): ViewData {
|
||||||
const rendererFactory: RendererFactory2 = injector.get(RendererFactory2);
|
const rendererFactory: RendererFactory2 = ngModule.injector.get(RendererFactory2);
|
||||||
return createRootView(
|
return createRootView(
|
||||||
createRootData(injector, rendererFactory, projectableNodes, rootSelectorOrNode), def,
|
createRootData(elInjector, ngModule, rendererFactory, projectableNodes, rootSelectorOrNode),
|
||||||
context);
|
def, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
function debugCreateRootView(
|
function debugCreateRootView(
|
||||||
injector: Injector, projectableNodes: any[][], rootSelectorOrNode: string | any,
|
elInjector: Injector, projectableNodes: any[][], rootSelectorOrNode: string | any,
|
||||||
def: ViewDefinition, context?: any): ViewData {
|
def: ViewDefinition, ngModule: NgModuleRef<any>, context?: any): ViewData {
|
||||||
const rendererFactory: RendererFactory2 = injector.get(RendererFactory2);
|
const rendererFactory: RendererFactory2 = ngModule.injector.get(RendererFactory2);
|
||||||
const root = createRootData(
|
const root = createRootData(
|
||||||
injector, new DebugRendererFactory2(rendererFactory), projectableNodes, rootSelectorOrNode);
|
elInjector, ngModule, new DebugRendererFactory2(rendererFactory), projectableNodes,
|
||||||
|
rootSelectorOrNode);
|
||||||
return callWithDebugContext(DebugAction.create, createRootView, null, [root, def, context]);
|
return callWithDebugContext(DebugAction.create, createRootView, null, [root, def, context]);
|
||||||
}
|
}
|
||||||
|
|
||||||
function createRootData(
|
function createRootData(
|
||||||
injector: Injector, rendererFactory: RendererFactory2, projectableNodes: any[][],
|
elInjector: Injector, ngModule: NgModuleRef<any>, rendererFactory: RendererFactory2,
|
||||||
rootSelectorOrNode: any): RootData {
|
projectableNodes: any[][], rootSelectorOrNode: any): RootData {
|
||||||
const sanitizer = injector.get(Sanitizer);
|
const sanitizer = ngModule.injector.get(Sanitizer);
|
||||||
const renderer = rendererFactory.createRenderer(null, null);
|
const renderer = rendererFactory.createRenderer(null, null);
|
||||||
return {
|
return {
|
||||||
injector,
|
ngModule,
|
||||||
projectableNodes,
|
injector: elInjector, projectableNodes,
|
||||||
selectorOrNode: rootSelectorOrNode, sanitizer, rendererFactory, renderer
|
selectorOrNode: rootSelectorOrNode, sanitizer, rendererFactory, renderer
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
import {PipeTransform} from '../change_detection/change_detection';
|
import {PipeTransform} from '../change_detection/change_detection';
|
||||||
import {Injector} from '../di';
|
import {Injector} from '../di';
|
||||||
import {ComponentRef} from '../linker/component_factory';
|
import {ComponentRef} from '../linker/component_factory';
|
||||||
|
import {NgModuleRef} from '../linker/ng_module_factory';
|
||||||
import {QueryList} from '../linker/query_list';
|
import {QueryList} from '../linker/query_list';
|
||||||
import {TemplateRef} from '../linker/template_ref';
|
import {TemplateRef} from '../linker/template_ref';
|
||||||
import {ViewContainerRef} from '../linker/view_container_ref';
|
import {ViewContainerRef} from '../linker/view_container_ref';
|
||||||
|
@ -424,6 +425,7 @@ export function asQueryList(view: ViewData, index: number): QueryList<any> {
|
||||||
|
|
||||||
export interface RootData {
|
export interface RootData {
|
||||||
injector: Injector;
|
injector: Injector;
|
||||||
|
ngModule: NgModuleRef<any>;
|
||||||
projectableNodes: any[][];
|
projectableNodes: any[][];
|
||||||
selectorOrNode: any;
|
selectorOrNode: any;
|
||||||
renderer: Renderer2;
|
renderer: Renderer2;
|
||||||
|
@ -454,7 +456,7 @@ export interface Services {
|
||||||
setCurrentNode(view: ViewData, nodeIndex: number): void;
|
setCurrentNode(view: ViewData, nodeIndex: number): void;
|
||||||
createRootView(
|
createRootView(
|
||||||
injector: Injector, projectableNodes: any[][], rootSelectorOrNode: string|any,
|
injector: Injector, projectableNodes: any[][], rootSelectorOrNode: string|any,
|
||||||
def: ViewDefinition, context?: any): ViewData;
|
def: ViewDefinition, ngModule: NgModuleRef<any>, context?: any): ViewData;
|
||||||
createEmbeddedView(parent: ViewData, anchorDef: NodeDef, context?: any): ViewData;
|
createEmbeddedView(parent: ViewData, anchorDef: NodeDef, context?: any): ViewData;
|
||||||
checkAndUpdateView(view: ViewData): void;
|
checkAndUpdateView(view: ViewData): void;
|
||||||
checkNoChangesView(view: ViewData): void;
|
checkNoChangesView(view: ViewData): void;
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {APP_BOOTSTRAP_LISTENER, APP_INITIALIZER, CompilerFactory, Component, NgModule, NgZone, PlatformRef, TemplateRef, Type, ViewChild, ViewContainerRef} from '@angular/core';
|
import {APP_BOOTSTRAP_LISTENER, APP_INITIALIZER, Compiler, CompilerFactory, Component, NgModule, NgZone, PlatformRef, TemplateRef, Type, ViewChild, ViewContainerRef} from '@angular/core';
|
||||||
import {ApplicationRef, ApplicationRef_} from '@angular/core/src/application_ref';
|
import {ApplicationRef, ApplicationRef_} from '@angular/core/src/application_ref';
|
||||||
import {ErrorHandler} from '@angular/core/src/error_handler';
|
import {ErrorHandler} from '@angular/core/src/error_handler';
|
||||||
import {ComponentRef} from '@angular/core/src/linker/component_factory';
|
import {ComponentRef} from '@angular/core/src/linker/component_factory';
|
||||||
|
@ -72,6 +72,33 @@ export function main() {
|
||||||
return MyModule;
|
return MyModule;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
it('should bootstrap a component from a child module',
|
||||||
|
async(inject([ApplicationRef, Compiler], (app: ApplicationRef, compiler: Compiler) => {
|
||||||
|
@Component({
|
||||||
|
selector: 'bootstrap-app',
|
||||||
|
template: '',
|
||||||
|
})
|
||||||
|
class SomeComponent {
|
||||||
|
}
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
providers: [{provide: 'hello', useValue: 'component'}],
|
||||||
|
declarations: [SomeComponent],
|
||||||
|
entryComponents: [SomeComponent],
|
||||||
|
})
|
||||||
|
class SomeModule {
|
||||||
|
}
|
||||||
|
|
||||||
|
createRootEl();
|
||||||
|
const modFactory = compiler.compileModuleSync(SomeModule);
|
||||||
|
const module = modFactory.create(TestBed);
|
||||||
|
const cmpFactory = module.componentFactoryResolver.resolveComponentFactory(SomeComponent);
|
||||||
|
const component = app.bootstrap(cmpFactory);
|
||||||
|
|
||||||
|
// The component should see the child module providers
|
||||||
|
expect(component.injector.get('hello')).toEqual('component');
|
||||||
|
})));
|
||||||
|
|
||||||
describe('ApplicationRef', () => {
|
describe('ApplicationRef', () => {
|
||||||
beforeEach(() => { TestBed.configureTestingModule({imports: [createModule()]}); });
|
beforeEach(() => { TestBed.configureTestingModule({imports: [createModule()]}); });
|
||||||
|
|
||||||
|
|
|
@ -1264,13 +1264,12 @@ function declareTests({useJit}: {useJit: boolean}) {
|
||||||
TestBed.createComponent(SomeComponent);
|
TestBed.createComponent(SomeComponent);
|
||||||
|
|
||||||
expect(noSelectorComponentFactory.selector).toBe('ng-component');
|
expect(noSelectorComponentFactory.selector).toBe('ng-component');
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
getDOM()
|
getDOM()
|
||||||
.nodeName(
|
.nodeName(noSelectorComponentFactory.create(Injector.NULL).location.nativeElement)
|
||||||
noSelectorComponentFactory.create(TestBed.get(Injector)).location.nativeElement)
|
|
||||||
.toLowerCase())
|
.toLowerCase())
|
||||||
.toEqual('ng-component');
|
.toEqual('ng-component');
|
||||||
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -130,9 +130,13 @@ function declareTests({useJit}: {useJit: boolean}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function createComp<T>(compType: Type<T>, moduleType: Type<any>): ComponentFixture<T> {
|
function createComp<T>(compType: Type<T>, moduleType: Type<any>): ComponentFixture<T> {
|
||||||
const ngModule = createModule(moduleType);
|
const ngModule = createModule(moduleType, injector);
|
||||||
|
|
||||||
const cf = ngModule.componentFactoryResolver.resolveComponentFactory(compType);
|
const cf = ngModule.componentFactoryResolver.resolveComponentFactory(compType);
|
||||||
return new ComponentFixture(cf.create(injector), null, false);
|
|
||||||
|
const comp = cf.create(Injector.NULL);
|
||||||
|
|
||||||
|
return new ComponentFixture(comp, null, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('errors', () => {
|
describe('errors', () => {
|
||||||
|
@ -417,6 +421,7 @@ function declareTests({useJit}: {useJit: boolean}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const compFixture = createComp(CompUsingModuleDirectiveAndPipe, SomeModule);
|
const compFixture = createComp(CompUsingModuleDirectiveAndPipe, SomeModule);
|
||||||
|
|
||||||
compFixture.detectChanges();
|
compFixture.detectChanges();
|
||||||
expect(compFixture.debugElement.children[0].properties['title'])
|
expect(compFixture.debugElement.children[0].properties['title'])
|
||||||
.toBe('transformed someValue');
|
.toBe('transformed someValue');
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {Attribute, ChangeDetectionStrategy, ChangeDetectorRef, Component, DebugElement, Directive, ElementRef, Host, Inject, InjectionToken, Input, Optional, Pipe, PipeTransform, Provider, Self, SkipSelf, TemplateRef, Type, ViewContainerRef} from '@angular/core';
|
import {Attribute, ChangeDetectionStrategy, ChangeDetectorRef, Component, ComponentFactoryResolver, DebugElement, Directive, ElementRef, Host, Inject, InjectionToken, Injector, Input, NgModule, Optional, Pipe, PipeTransform, Provider, Self, SkipSelf, TemplateRef, Type, ViewContainerRef} from '@angular/core';
|
||||||
import {ComponentFixture, TestBed, fakeAsync} from '@angular/core/testing';
|
import {ComponentFixture, TestBed, fakeAsync} from '@angular/core/testing';
|
||||||
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
|
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
|
||||||
import {expect} from '@angular/platform-browser/testing/src/matchers';
|
import {expect} from '@angular/platform-browser/testing/src/matchers';
|
||||||
|
@ -642,6 +642,28 @@ export function main() {
|
||||||
.toBe(el.children[0].nativeElement);
|
.toBe(el.children[0].nativeElement);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should inject ViewContainerRef', () => {
|
||||||
|
@Component({template: ''})
|
||||||
|
class TestComp {
|
||||||
|
constructor(public vcr: ViewContainerRef) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [TestComp],
|
||||||
|
entryComponents: [TestComp],
|
||||||
|
})
|
||||||
|
class TestModule {
|
||||||
|
}
|
||||||
|
|
||||||
|
const testInjector = {};
|
||||||
|
|
||||||
|
const compFactory = TestBed.configureTestingModule({imports: [TestModule]})
|
||||||
|
.get(ComponentFactoryResolver)
|
||||||
|
.resolveComponentFactory(TestComp);
|
||||||
|
const component = compFactory.create(<Injector>testInjector);
|
||||||
|
expect(component.instance.vcr.parentInjector).toBe(testInjector);
|
||||||
|
});
|
||||||
|
|
||||||
it('should inject TemplateRef', () => {
|
it('should inject TemplateRef', () => {
|
||||||
TestBed.configureTestingModule({declarations: [NeedsViewContainerRef, NeedsTemplateRef]});
|
TestBed.configureTestingModule({declarations: [NeedsViewContainerRef, NeedsTemplateRef]});
|
||||||
const el =
|
const el =
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {Injector, RootRenderer, Sanitizer} from '@angular/core';
|
import {Injector, NgModuleRef, RootRenderer, Sanitizer} from '@angular/core';
|
||||||
import {ArgumentType, NodeCheckFn, RootData, Services, ViewData, ViewDefinition, initServicesIfNeeded} from '@angular/core/src/view/index';
|
import {ArgumentType, NodeCheckFn, RootData, Services, ViewData, ViewDefinition, initServicesIfNeeded} from '@angular/core/src/view/index';
|
||||||
import {TestBed} from '@angular/core/testing';
|
import {TestBed} from '@angular/core/testing';
|
||||||
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
|
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
|
||||||
|
@ -33,7 +33,8 @@ export function createRootView(
|
||||||
rootSelectorOrNode?: any): ViewData {
|
rootSelectorOrNode?: any): ViewData {
|
||||||
initServicesIfNeeded();
|
initServicesIfNeeded();
|
||||||
return Services.createRootView(
|
return Services.createRootView(
|
||||||
TestBed.get(Injector), projectableNodes || [], rootSelectorOrNode, def, context);
|
TestBed.get(Injector), projectableNodes || [], rootSelectorOrNode, def,
|
||||||
|
TestBed.get(NgModuleRef), context);
|
||||||
}
|
}
|
||||||
|
|
||||||
export let removeNodes: Node[];
|
export let removeNodes: Node[];
|
||||||
|
|
|
@ -292,8 +292,7 @@ export class TestBed implements Injector {
|
||||||
const imports = [this.ngModule, this._imports];
|
const imports = [this.ngModule, this._imports];
|
||||||
const schemas = this._schemas;
|
const schemas = this._schemas;
|
||||||
|
|
||||||
@NgModule(
|
@NgModule({providers, declarations, imports, schemas})
|
||||||
{providers: providers, declarations: declarations, imports: imports, schemas: schemas})
|
|
||||||
class DynamicTestModule {
|
class DynamicTestModule {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -359,10 +358,12 @@ export class TestBed implements Injector {
|
||||||
this._initIfNeeded();
|
this._initIfNeeded();
|
||||||
const componentFactory = this._moduleWithComponentFactories.componentFactories.find(
|
const componentFactory = this._moduleWithComponentFactories.componentFactories.find(
|
||||||
(compFactory) => compFactory.componentType === component);
|
(compFactory) => compFactory.componentType === component);
|
||||||
|
|
||||||
if (!componentFactory) {
|
if (!componentFactory) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Cannot create the component ${stringify(component)} as it was not imported into the testing module!`);
|
`Cannot create the component ${stringify(component)} as it was not imported into the testing module!`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const noNgZone = this.get(ComponentFixtureNoNgZone, false);
|
const noNgZone = this.get(ComponentFixtureNoNgZone, false);
|
||||||
const autoDetect: boolean = this.get(ComponentFixtureAutoDetect, false);
|
const autoDetect: boolean = this.get(ComponentFixtureAutoDetect, false);
|
||||||
const ngZone: NgZone = noNgZone ? null : this.get(NgZone, null);
|
const ngZone: NgZone = noNgZone ? null : this.get(NgZone, null);
|
||||||
|
@ -371,7 +372,8 @@ export class TestBed implements Injector {
|
||||||
testComponentRenderer.insertRootElement(rootElId);
|
testComponentRenderer.insertRootElement(rootElId);
|
||||||
|
|
||||||
const initComponent = () => {
|
const initComponent = () => {
|
||||||
const componentRef = componentFactory.create(this, [], `#${rootElId}`);
|
const componentRef =
|
||||||
|
componentFactory.create(Injector.NULL, [], `#${rootElId}`, this._moduleRef);
|
||||||
return new ComponentFixture<T>(componentRef, ngZone, autoDetect);
|
return new ComponentFixture<T>(componentRef, ngZone, autoDetect);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {Injector} from '@angular/core';
|
import {Injector, NgModuleRef} from '@angular/core';
|
||||||
import {Observable} from 'rxjs/Observable';
|
import {Observable} from 'rxjs/Observable';
|
||||||
import {Observer} from 'rxjs/Observer';
|
import {Observer} from 'rxjs/Observer';
|
||||||
import {from} from 'rxjs/observable/from';
|
import {from} from 'rxjs/observable/from';
|
||||||
|
@ -55,21 +55,24 @@ function canLoadFails(route: Route): Observable<LoadedRouterConfig> {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function applyRedirects(
|
export function applyRedirects(
|
||||||
injector: Injector, configLoader: RouterConfigLoader, urlSerializer: UrlSerializer,
|
moduleInjector: Injector, configLoader: RouterConfigLoader, urlSerializer: UrlSerializer,
|
||||||
urlTree: UrlTree, config: Routes): Observable<UrlTree> {
|
urlTree: UrlTree, config: Routes): Observable<UrlTree> {
|
||||||
return new ApplyRedirects(injector, configLoader, urlSerializer, urlTree, config).apply();
|
return new ApplyRedirects(moduleInjector, configLoader, urlSerializer, urlTree, config).apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
class ApplyRedirects {
|
class ApplyRedirects {
|
||||||
private allowRedirects: boolean = true;
|
private allowRedirects: boolean = true;
|
||||||
|
private ngModule: NgModuleRef<any>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private injector: Injector, private configLoader: RouterConfigLoader,
|
moduleInjector: Injector, private configLoader: RouterConfigLoader,
|
||||||
private urlSerializer: UrlSerializer, private urlTree: UrlTree, private config: Routes) {}
|
private urlSerializer: UrlSerializer, private urlTree: UrlTree, private config: Routes) {
|
||||||
|
this.ngModule = moduleInjector.get(NgModuleRef);
|
||||||
|
}
|
||||||
|
|
||||||
apply(): Observable<UrlTree> {
|
apply(): Observable<UrlTree> {
|
||||||
const expanded$ =
|
const expanded$ =
|
||||||
this.expandSegmentGroup(this.injector, this.config, this.urlTree.root, PRIMARY_OUTLET);
|
this.expandSegmentGroup(this.ngModule, this.config, this.urlTree.root, PRIMARY_OUTLET);
|
||||||
const urlTrees$ = map.call(
|
const urlTrees$ = map.call(
|
||||||
expanded$, (rootSegmentGroup: UrlSegmentGroup) => this.createUrlTree(
|
expanded$, (rootSegmentGroup: UrlSegmentGroup) => this.createUrlTree(
|
||||||
rootSegmentGroup, this.urlTree.queryParams, this.urlTree.fragment));
|
rootSegmentGroup, this.urlTree.queryParams, this.urlTree.fragment));
|
||||||
|
@ -91,7 +94,7 @@ class ApplyRedirects {
|
||||||
|
|
||||||
private match(tree: UrlTree): Observable<UrlTree> {
|
private match(tree: UrlTree): Observable<UrlTree> {
|
||||||
const expanded$ =
|
const expanded$ =
|
||||||
this.expandSegmentGroup(this.injector, this.config, tree.root, PRIMARY_OUTLET);
|
this.expandSegmentGroup(this.ngModule, this.config, tree.root, PRIMARY_OUTLET);
|
||||||
const mapped$ = map.call(
|
const mapped$ = map.call(
|
||||||
expanded$, (rootSegmentGroup: UrlSegmentGroup) =>
|
expanded$, (rootSegmentGroup: UrlSegmentGroup) =>
|
||||||
this.createUrlTree(rootSegmentGroup, tree.queryParams, tree.fragment));
|
this.createUrlTree(rootSegmentGroup, tree.queryParams, tree.fragment));
|
||||||
|
@ -117,31 +120,33 @@ class ApplyRedirects {
|
||||||
}
|
}
|
||||||
|
|
||||||
private expandSegmentGroup(
|
private expandSegmentGroup(
|
||||||
injector: Injector, routes: Route[], segmentGroup: UrlSegmentGroup,
|
ngModule: NgModuleRef<any>, routes: Route[], segmentGroup: UrlSegmentGroup,
|
||||||
outlet: string): Observable<UrlSegmentGroup> {
|
outlet: string): Observable<UrlSegmentGroup> {
|
||||||
if (segmentGroup.segments.length === 0 && segmentGroup.hasChildren()) {
|
if (segmentGroup.segments.length === 0 && segmentGroup.hasChildren()) {
|
||||||
return map.call(
|
return map.call(
|
||||||
this.expandChildren(injector, routes, segmentGroup),
|
this.expandChildren(ngModule, routes, segmentGroup),
|
||||||
(children: any) => new UrlSegmentGroup([], children));
|
(children: any) => new UrlSegmentGroup([], children));
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.expandSegment(injector, segmentGroup, routes, segmentGroup.segments, outlet, true);
|
return this.expandSegment(ngModule, segmentGroup, routes, segmentGroup.segments, outlet, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private expandChildren(injector: Injector, routes: Route[], segmentGroup: UrlSegmentGroup):
|
private expandChildren(
|
||||||
Observable<{[name: string]: UrlSegmentGroup}> {
|
ngModule: NgModuleRef<any>, routes: Route[],
|
||||||
|
segmentGroup: UrlSegmentGroup): Observable<{[name: string]: UrlSegmentGroup}> {
|
||||||
return waitForMap(
|
return waitForMap(
|
||||||
segmentGroup.children,
|
segmentGroup.children,
|
||||||
(childOutlet, child) => this.expandSegmentGroup(injector, routes, child, childOutlet));
|
(childOutlet, child) => this.expandSegmentGroup(ngModule, routes, child, childOutlet));
|
||||||
}
|
}
|
||||||
|
|
||||||
private expandSegment(
|
private expandSegment(
|
||||||
injector: Injector, segmentGroup: UrlSegmentGroup, routes: Route[], segments: UrlSegment[],
|
ngModule: NgModuleRef<any>, segmentGroup: UrlSegmentGroup, routes: Route[],
|
||||||
outlet: string, allowRedirects: boolean): Observable<UrlSegmentGroup> {
|
segments: UrlSegment[], outlet: string,
|
||||||
|
allowRedirects: boolean): Observable<UrlSegmentGroup> {
|
||||||
const routes$ = of (...routes);
|
const routes$ = of (...routes);
|
||||||
const processedRoutes$ = map.call(routes$, (r: any) => {
|
const processedRoutes$ = map.call(routes$, (r: any) => {
|
||||||
const expanded$ = this.expandSegmentAgainstRoute(
|
const expanded$ = this.expandSegmentAgainstRoute(
|
||||||
injector, segmentGroup, routes, r, segments, outlet, allowRedirects);
|
ngModule, segmentGroup, routes, r, segments, outlet, allowRedirects);
|
||||||
return _catch.call(expanded$, (e: any) => {
|
return _catch.call(expanded$, (e: any) => {
|
||||||
if (e instanceof NoMatch) {
|
if (e instanceof NoMatch) {
|
||||||
return of (null);
|
return of (null);
|
||||||
|
@ -171,7 +176,7 @@ class ApplyRedirects {
|
||||||
}
|
}
|
||||||
|
|
||||||
private expandSegmentAgainstRoute(
|
private expandSegmentAgainstRoute(
|
||||||
injector: Injector, segmentGroup: UrlSegmentGroup, routes: Route[], route: Route,
|
ngModule: NgModuleRef<any>, segmentGroup: UrlSegmentGroup, routes: Route[], route: Route,
|
||||||
paths: UrlSegment[], outlet: string, allowRedirects: boolean): Observable<UrlSegmentGroup> {
|
paths: UrlSegment[], outlet: string, allowRedirects: boolean): Observable<UrlSegmentGroup> {
|
||||||
if (getOutlet(route) !== outlet) {
|
if (getOutlet(route) !== outlet) {
|
||||||
return noMatch(segmentGroup);
|
return noMatch(segmentGroup);
|
||||||
|
@ -182,27 +187,27 @@ class ApplyRedirects {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (route.redirectTo === undefined) {
|
if (route.redirectTo === undefined) {
|
||||||
return this.matchSegmentAgainstRoute(injector, segmentGroup, route, paths);
|
return this.matchSegmentAgainstRoute(ngModule, segmentGroup, route, paths);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.expandSegmentAgainstRouteUsingRedirect(
|
return this.expandSegmentAgainstRouteUsingRedirect(
|
||||||
injector, segmentGroup, routes, route, paths, outlet);
|
ngModule, segmentGroup, routes, route, paths, outlet);
|
||||||
}
|
}
|
||||||
|
|
||||||
private expandSegmentAgainstRouteUsingRedirect(
|
private expandSegmentAgainstRouteUsingRedirect(
|
||||||
injector: Injector, segmentGroup: UrlSegmentGroup, routes: Route[], route: Route,
|
ngModule: NgModuleRef<any>, segmentGroup: UrlSegmentGroup, routes: Route[], route: Route,
|
||||||
segments: UrlSegment[], outlet: string): Observable<UrlSegmentGroup> {
|
segments: UrlSegment[], outlet: string): Observable<UrlSegmentGroup> {
|
||||||
if (route.path === '**') {
|
if (route.path === '**') {
|
||||||
return this.expandWildCardWithParamsAgainstRouteUsingRedirect(
|
return this.expandWildCardWithParamsAgainstRouteUsingRedirect(
|
||||||
injector, routes, route, outlet);
|
ngModule, routes, route, outlet);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.expandRegularSegmentAgainstRouteUsingRedirect(
|
return this.expandRegularSegmentAgainstRouteUsingRedirect(
|
||||||
injector, segmentGroup, routes, route, segments, outlet);
|
ngModule, segmentGroup, routes, route, segments, outlet);
|
||||||
}
|
}
|
||||||
|
|
||||||
private expandWildCardWithParamsAgainstRouteUsingRedirect(
|
private expandWildCardWithParamsAgainstRouteUsingRedirect(
|
||||||
injector: Injector, routes: Route[], route: Route,
|
ngModule: NgModuleRef<any>, routes: Route[], route: Route,
|
||||||
outlet: string): Observable<UrlSegmentGroup> {
|
outlet: string): Observable<UrlSegmentGroup> {
|
||||||
const newTree = this.applyRedirectCommands([], route.redirectTo, {});
|
const newTree = this.applyRedirectCommands([], route.redirectTo, {});
|
||||||
if (route.redirectTo.startsWith('/')) {
|
if (route.redirectTo.startsWith('/')) {
|
||||||
|
@ -211,12 +216,12 @@ class ApplyRedirects {
|
||||||
|
|
||||||
return mergeMap.call(this.lineralizeSegments(route, newTree), (newSegments: UrlSegment[]) => {
|
return mergeMap.call(this.lineralizeSegments(route, newTree), (newSegments: UrlSegment[]) => {
|
||||||
const group = new UrlSegmentGroup(newSegments, {});
|
const group = new UrlSegmentGroup(newSegments, {});
|
||||||
return this.expandSegment(injector, group, routes, newSegments, outlet, false);
|
return this.expandSegment(ngModule, group, routes, newSegments, outlet, false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private expandRegularSegmentAgainstRouteUsingRedirect(
|
private expandRegularSegmentAgainstRouteUsingRedirect(
|
||||||
injector: Injector, segmentGroup: UrlSegmentGroup, routes: Route[], route: Route,
|
ngModule: NgModuleRef<any>, segmentGroup: UrlSegmentGroup, routes: Route[], route: Route,
|
||||||
segments: UrlSegment[], outlet: string): Observable<UrlSegmentGroup> {
|
segments: UrlSegment[], outlet: string): Observable<UrlSegmentGroup> {
|
||||||
const {matched, consumedSegments, lastChild, positionalParamSegments} =
|
const {matched, consumedSegments, lastChild, positionalParamSegments} =
|
||||||
match(segmentGroup, route, segments);
|
match(segmentGroup, route, segments);
|
||||||
|
@ -230,20 +235,21 @@ class ApplyRedirects {
|
||||||
|
|
||||||
return mergeMap.call(this.lineralizeSegments(route, newTree), (newSegments: UrlSegment[]) => {
|
return mergeMap.call(this.lineralizeSegments(route, newTree), (newSegments: UrlSegment[]) => {
|
||||||
return this.expandSegment(
|
return this.expandSegment(
|
||||||
injector, segmentGroup, routes, newSegments.concat(segments.slice(lastChild)), outlet,
|
ngModule, segmentGroup, routes, newSegments.concat(segments.slice(lastChild)), outlet,
|
||||||
false);
|
false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private matchSegmentAgainstRoute(
|
private matchSegmentAgainstRoute(
|
||||||
injector: Injector, rawSegmentGroup: UrlSegmentGroup, route: Route,
|
ngModule: NgModuleRef<any>, rawSegmentGroup: UrlSegmentGroup, route: Route,
|
||||||
segments: UrlSegment[]): Observable<UrlSegmentGroup> {
|
segments: UrlSegment[]): Observable<UrlSegmentGroup> {
|
||||||
if (route.path === '**') {
|
if (route.path === '**') {
|
||||||
if (route.loadChildren) {
|
if (route.loadChildren) {
|
||||||
return map.call(this.configLoader.load(injector, route), (cfg: LoadedRouterConfig) => {
|
return map.call(
|
||||||
(<any>route)._loadedConfig = cfg;
|
this.configLoader.load(ngModule.injector, route), (cfg: LoadedRouterConfig) => {
|
||||||
return new UrlSegmentGroup(segments, {});
|
(<any>route)._loadedConfig = cfg;
|
||||||
});
|
return new UrlSegmentGroup(segments, {});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return of (new UrlSegmentGroup(segments, {}));
|
return of (new UrlSegmentGroup(segments, {}));
|
||||||
|
@ -253,15 +259,17 @@ class ApplyRedirects {
|
||||||
if (!matched) return noMatch(rawSegmentGroup);
|
if (!matched) return noMatch(rawSegmentGroup);
|
||||||
|
|
||||||
const rawSlicedSegments = segments.slice(lastChild);
|
const rawSlicedSegments = segments.slice(lastChild);
|
||||||
const childConfig$ = this.getChildConfig(injector, route);
|
const childConfig$ = this.getChildConfig(ngModule, route);
|
||||||
|
|
||||||
return mergeMap.call(childConfig$, (routerConfig: LoadedRouterConfig) => {
|
return mergeMap.call(childConfig$, (routerConfig: LoadedRouterConfig) => {
|
||||||
const childInjector = routerConfig.injector;
|
const childModule = routerConfig.module;
|
||||||
const childConfig = routerConfig.routes;
|
const childConfig = routerConfig.routes;
|
||||||
|
|
||||||
const {segmentGroup, slicedSegments} =
|
const {segmentGroup, slicedSegments} =
|
||||||
split(rawSegmentGroup, consumedSegments, rawSlicedSegments, childConfig);
|
split(rawSegmentGroup, consumedSegments, rawSlicedSegments, childConfig);
|
||||||
|
|
||||||
if (slicedSegments.length === 0 && segmentGroup.hasChildren()) {
|
if (slicedSegments.length === 0 && segmentGroup.hasChildren()) {
|
||||||
const expanded$ = this.expandChildren(childInjector, childConfig, segmentGroup);
|
const expanded$ = this.expandChildren(childModule, childConfig, segmentGroup);
|
||||||
return map.call(
|
return map.call(
|
||||||
expanded$, (children: any) => new UrlSegmentGroup(consumedSegments, children));
|
expanded$, (children: any) => new UrlSegmentGroup(consumedSegments, children));
|
||||||
}
|
}
|
||||||
|
@ -271,35 +279,37 @@ class ApplyRedirects {
|
||||||
}
|
}
|
||||||
|
|
||||||
const expanded$ = this.expandSegment(
|
const expanded$ = this.expandSegment(
|
||||||
childInjector, segmentGroup, childConfig, slicedSegments, PRIMARY_OUTLET, true);
|
childModule, segmentGroup, childConfig, slicedSegments, PRIMARY_OUTLET, true);
|
||||||
return map.call(
|
return map.call(
|
||||||
expanded$, (cs: UrlSegmentGroup) =>
|
expanded$, (cs: UrlSegmentGroup) =>
|
||||||
new UrlSegmentGroup(consumedSegments.concat(cs.segments), cs.children));
|
new UrlSegmentGroup(consumedSegments.concat(cs.segments), cs.children));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private getChildConfig(injector: Injector, route: Route): Observable<LoadedRouterConfig> {
|
private getChildConfig(ngModule: NgModuleRef<any>, route: Route): Observable<LoadedRouterConfig> {
|
||||||
if (route.children) {
|
if (route.children) {
|
||||||
return of (new LoadedRouterConfig(route.children, injector, null, null));
|
// The children belong to the same module
|
||||||
|
return of (new LoadedRouterConfig(route.children, ngModule));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (route.loadChildren) {
|
if (route.loadChildren) {
|
||||||
return mergeMap.call(runGuards(injector, route), (shouldLoad: any) => {
|
return mergeMap.call(runGuards(ngModule.injector, route), (shouldLoad: any) => {
|
||||||
|
|
||||||
if (shouldLoad) {
|
if (shouldLoad) {
|
||||||
return (<any>route)._loadedConfig ?
|
return (<any>route)._loadedConfig ?
|
||||||
of ((<any>route)._loadedConfig) :
|
of ((<any>route)._loadedConfig) :
|
||||||
map.call(this.configLoader.load(injector, route), (cfg: LoadedRouterConfig) => {
|
map.call(
|
||||||
(<any>route)._loadedConfig = cfg;
|
this.configLoader.load(ngModule.injector, route), (cfg: LoadedRouterConfig) => {
|
||||||
return cfg;
|
(<any>route)._loadedConfig = cfg;
|
||||||
});
|
return cfg;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return canLoadFails(route);
|
return canLoadFails(route);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return of (new LoadedRouterConfig([], injector, null, null));
|
return of (new LoadedRouterConfig([], ngModule));
|
||||||
}
|
}
|
||||||
|
|
||||||
private lineralizeSegments(route: Route, urlTree: UrlTree): Observable<UrlSegment[]> {
|
private lineralizeSegments(route: Route, urlTree: UrlTree): Observable<UrlSegment[]> {
|
||||||
|
@ -386,12 +396,12 @@ class ApplyRedirects {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function runGuards(injector: Injector, route: Route): Observable<boolean> {
|
function runGuards(moduleInjector: Injector, route: Route): Observable<boolean> {
|
||||||
const canLoad = route.canLoad;
|
const canLoad = route.canLoad;
|
||||||
if (!canLoad || canLoad.length === 0) return of (true);
|
if (!canLoad || canLoad.length === 0) return of (true);
|
||||||
|
|
||||||
const obs = map.call(from(canLoad), (c: any) => {
|
const obs = map.call(from(canLoad), (c: any) => {
|
||||||
const guard = injector.get(c);
|
const guard = moduleInjector.get(c);
|
||||||
return wrapIntoObservable(guard.canLoad ? guard.canLoad(route) : guard(route));
|
return wrapIntoObservable(guard.canLoad ? guard.canLoad(route) : guard(route));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -52,7 +52,9 @@ export class RouterOutlet implements OnDestroy {
|
||||||
|
|
||||||
ngOnDestroy(): void { this.parentOutletMap.removeOutlet(this.name ? this.name : PRIMARY_OUTLET); }
|
ngOnDestroy(): void { this.parentOutletMap.removeOutlet(this.name ? this.name : PRIMARY_OUTLET); }
|
||||||
|
|
||||||
|
/** @deprecated since v4 **/
|
||||||
get locationInjector(): Injector { return this.location.injector; }
|
get locationInjector(): Injector { return this.location.injector; }
|
||||||
|
/** @deprecated since v4 **/
|
||||||
get locationFactoryResolver(): ComponentFactoryResolver { return this.resolver; }
|
get locationFactoryResolver(): ComponentFactoryResolver { return this.resolver; }
|
||||||
|
|
||||||
get isActivated(): boolean { return !!this.activated; }
|
get isActivated(): boolean { return !!this.activated; }
|
||||||
|
@ -90,6 +92,7 @@ export class RouterOutlet implements OnDestroy {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @deprecated since v4, use {@link activateWith} */
|
||||||
activate(
|
activate(
|
||||||
activatedRoute: ActivatedRoute, resolver: ComponentFactoryResolver, injector: Injector,
|
activatedRoute: ActivatedRoute, resolver: ComponentFactoryResolver, injector: Injector,
|
||||||
providers: ResolvedReflectiveProvider[], outletMap: RouterOutletMap): void {
|
providers: ResolvedReflectiveProvider[], outletMap: RouterOutletMap): void {
|
||||||
|
@ -105,9 +108,51 @@ export class RouterOutlet implements OnDestroy {
|
||||||
const factory = resolver.resolveComponentFactory(component);
|
const factory = resolver.resolveComponentFactory(component);
|
||||||
|
|
||||||
const inj = ReflectiveInjector.fromResolvedProviders(providers, injector);
|
const inj = ReflectiveInjector.fromResolvedProviders(providers, injector);
|
||||||
|
|
||||||
this.activated = this.location.createComponent(factory, this.location.length, inj, []);
|
this.activated = this.location.createComponent(factory, this.location.length, inj, []);
|
||||||
this.activated.changeDetectorRef.detectChanges();
|
this.activated.changeDetectorRef.detectChanges();
|
||||||
|
|
||||||
this.activateEvents.emit(this.activated.instance);
|
this.activateEvents.emit(this.activated.instance);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
activateWith(
|
||||||
|
activatedRoute: ActivatedRoute, resolver: ComponentFactoryResolver|null,
|
||||||
|
outletMap: RouterOutletMap) {
|
||||||
|
if (this.isActivated) {
|
||||||
|
throw new Error('Cannot activate an already activated outlet');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.outletMap = outletMap;
|
||||||
|
this._activatedRoute = activatedRoute;
|
||||||
|
|
||||||
|
const snapshot = activatedRoute._futureSnapshot;
|
||||||
|
const component = <any>snapshot._routeConfig.component;
|
||||||
|
|
||||||
|
resolver = resolver || this.resolver;
|
||||||
|
const factory = resolver.resolveComponentFactory(component);
|
||||||
|
|
||||||
|
const injector = new OutletInjector(activatedRoute, outletMap, this.location.injector);
|
||||||
|
|
||||||
|
this.activated = this.location.createComponent(factory, this.location.length, injector, []);
|
||||||
|
this.activated.changeDetectorRef.detectChanges();
|
||||||
|
|
||||||
|
this.activateEvents.emit(this.activated.instance);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class OutletInjector implements Injector {
|
||||||
|
constructor(
|
||||||
|
private route: ActivatedRoute, private map: RouterOutletMap, private parent: Injector) {}
|
||||||
|
|
||||||
|
get(token: any, notFoundValue?: any): any {
|
||||||
|
if (token === ActivatedRoute) {
|
||||||
|
return this.route;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (token === RouterOutletMap) {
|
||||||
|
return this.map;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.parent.get(token, notFoundValue);
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,7 +7,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {Location} from '@angular/common';
|
import {Location} from '@angular/common';
|
||||||
import {Compiler, ComponentFactoryResolver, Injector, NgModuleFactoryLoader, ReflectiveInjector, Type, isDevMode} from '@angular/core';
|
import {Compiler, Injector, NgModuleFactoryLoader, NgModuleRef, Type, isDevMode} from '@angular/core';
|
||||||
import {BehaviorSubject} from 'rxjs/BehaviorSubject';
|
import {BehaviorSubject} from 'rxjs/BehaviorSubject';
|
||||||
import {Observable} from 'rxjs/Observable';
|
import {Observable} from 'rxjs/Observable';
|
||||||
import {Subject} from 'rxjs/Subject';
|
import {Subject} from 'rxjs/Subject';
|
||||||
|
@ -225,6 +225,7 @@ export class Router {
|
||||||
private locationSubscription: Subscription;
|
private locationSubscription: Subscription;
|
||||||
private navigationId: number = 0;
|
private navigationId: number = 0;
|
||||||
private configLoader: RouterConfigLoader;
|
private configLoader: RouterConfigLoader;
|
||||||
|
private ngModule: NgModuleRef<any>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Error handler that is invoked when a navigation errors.
|
* Error handler that is invoked when a navigation errors.
|
||||||
|
@ -263,11 +264,13 @@ export class Router {
|
||||||
// TODO: vsavkin make internal after the final is out.
|
// TODO: vsavkin make internal after the final is out.
|
||||||
constructor(
|
constructor(
|
||||||
private rootComponentType: Type<any>, private urlSerializer: UrlSerializer,
|
private rootComponentType: Type<any>, private urlSerializer: UrlSerializer,
|
||||||
private outletMap: RouterOutletMap, private location: Location, private injector: Injector,
|
private outletMap: RouterOutletMap, private location: Location, injector: Injector,
|
||||||
loader: NgModuleFactoryLoader, compiler: Compiler, public config: Routes) {
|
loader: NgModuleFactoryLoader, compiler: Compiler, public config: Routes) {
|
||||||
const onLoadStart = (r: Route) => this.triggerEvent(new RouteConfigLoadStart(r));
|
const onLoadStart = (r: Route) => this.triggerEvent(new RouteConfigLoadStart(r));
|
||||||
const onLoadEnd = (r: Route) => this.triggerEvent(new RouteConfigLoadEnd(r));
|
const onLoadEnd = (r: Route) => this.triggerEvent(new RouteConfigLoadEnd(r));
|
||||||
|
|
||||||
|
this.ngModule = injector.get(NgModuleRef);
|
||||||
|
|
||||||
this.resetConfig(config);
|
this.resetConfig(config);
|
||||||
this.currentUrlTree = createEmptyUrlTree();
|
this.currentUrlTree = createEmptyUrlTree();
|
||||||
this.rawUrlTree = this.currentUrlTree;
|
this.rawUrlTree = this.currentUrlTree;
|
||||||
|
@ -607,8 +610,9 @@ export class Router {
|
||||||
// this operation do not result in any side effects
|
// this operation do not result in any side effects
|
||||||
let urlAndSnapshot$: Observable<{appliedUrl: UrlTree, snapshot: RouterStateSnapshot}>;
|
let urlAndSnapshot$: Observable<{appliedUrl: UrlTree, snapshot: RouterStateSnapshot}>;
|
||||||
if (!precreatedState) {
|
if (!precreatedState) {
|
||||||
|
const moduleInjector = this.ngModule.injector;
|
||||||
const redirectsApplied$ =
|
const redirectsApplied$ =
|
||||||
applyRedirects(this.injector, this.configLoader, this.urlSerializer, url, this.config);
|
applyRedirects(moduleInjector, this.configLoader, this.urlSerializer, url, this.config);
|
||||||
|
|
||||||
urlAndSnapshot$ = mergeMap.call(redirectsApplied$, (appliedUrl: UrlTree) => {
|
urlAndSnapshot$ = mergeMap.call(redirectsApplied$, (appliedUrl: UrlTree) => {
|
||||||
return map.call(
|
return map.call(
|
||||||
|
@ -636,8 +640,9 @@ export class Router {
|
||||||
const preactivationTraverse$ = map.call(
|
const preactivationTraverse$ = map.call(
|
||||||
beforePreactivationDone$,
|
beforePreactivationDone$,
|
||||||
({appliedUrl, snapshot}: {appliedUrl: string, snapshot: RouterStateSnapshot}) => {
|
({appliedUrl, snapshot}: {appliedUrl: string, snapshot: RouterStateSnapshot}) => {
|
||||||
|
const moduleInjector = this.ngModule.injector;
|
||||||
preActivation =
|
preActivation =
|
||||||
new PreActivation(snapshot, this.currentRouterState.snapshot, this.injector);
|
new PreActivation(snapshot, this.currentRouterState.snapshot, moduleInjector);
|
||||||
preActivation.traverse(this.outletMap);
|
preActivation.traverse(this.outletMap);
|
||||||
return {appliedUrl, snapshot};
|
return {appliedUrl, snapshot};
|
||||||
});
|
});
|
||||||
|
@ -771,7 +776,7 @@ export class PreActivation {
|
||||||
private checks: Array<CanActivate|CanDeactivate> = [];
|
private checks: Array<CanActivate|CanDeactivate> = [];
|
||||||
constructor(
|
constructor(
|
||||||
private future: RouterStateSnapshot, private curr: RouterStateSnapshot,
|
private future: RouterStateSnapshot, private curr: RouterStateSnapshot,
|
||||||
private injector: Injector) {}
|
private moduleInjector: Injector) {}
|
||||||
|
|
||||||
traverse(parentOutletMap: RouterOutletMap): void {
|
traverse(parentOutletMap: RouterOutletMap): void {
|
||||||
const futureRoot = this.future._root;
|
const futureRoot = this.future._root;
|
||||||
|
@ -991,7 +996,7 @@ export class PreActivation {
|
||||||
|
|
||||||
private getToken(token: any, snapshot: ActivatedRouteSnapshot): any {
|
private getToken(token: any, snapshot: ActivatedRouteSnapshot): any {
|
||||||
const config = closestLoadedConfig(snapshot);
|
const config = closestLoadedConfig(snapshot);
|
||||||
const injector = config ? config.injector : this.injector;
|
const injector = config ? config.module.injector : this.moduleInjector;
|
||||||
return injector.get(token);
|
return injector.get(token);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1102,26 +1107,10 @@ class ActivateRoutes {
|
||||||
|
|
||||||
private placeComponentIntoOutlet(
|
private placeComponentIntoOutlet(
|
||||||
outletMap: RouterOutletMap, future: ActivatedRoute, outlet: RouterOutlet): void {
|
outletMap: RouterOutletMap, future: ActivatedRoute, outlet: RouterOutlet): void {
|
||||||
const resolved = <any[]>[{provide: ActivatedRoute, useValue: future}, {
|
|
||||||
provide: RouterOutletMap,
|
|
||||||
useValue: outletMap
|
|
||||||
}];
|
|
||||||
|
|
||||||
const config = parentLoadedConfig(future.snapshot);
|
const config = parentLoadedConfig(future.snapshot);
|
||||||
|
const cmpFactoryResolver = config ? config.module.componentFactoryResolver : null;
|
||||||
|
|
||||||
let resolver: ComponentFactoryResolver = null;
|
outlet.activateWith(future, cmpFactoryResolver, outletMap);
|
||||||
let injector: Injector = null;
|
|
||||||
|
|
||||||
if (config) {
|
|
||||||
injector = config.injectorFactory(outlet.locationInjector);
|
|
||||||
resolver = config.factoryResolver;
|
|
||||||
resolved.push({provide: ComponentFactoryResolver, useValue: resolver});
|
|
||||||
} else {
|
|
||||||
injector = outlet.locationInjector;
|
|
||||||
resolver = outlet.locationFactoryResolver;
|
|
||||||
}
|
|
||||||
|
|
||||||
outlet.activate(future, resolver, injector, ReflectiveInjector.resolve(resolved), outletMap);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private deactiveRouteAndItsChildren(
|
private deactiveRouteAndItsChildren(
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {Compiler, ComponentFactoryResolver, InjectionToken, Injector, NgModuleFactory, NgModuleFactoryLoader} from '@angular/core';
|
import {Compiler, InjectionToken, Injector, NgModuleFactory, NgModuleFactoryLoader, NgModuleRef} from '@angular/core';
|
||||||
import {Observable} from 'rxjs/Observable';
|
import {Observable} from 'rxjs/Observable';
|
||||||
import {fromPromise} from 'rxjs/observable/fromPromise';
|
import {fromPromise} from 'rxjs/observable/fromPromise';
|
||||||
import {of } from 'rxjs/observable/of';
|
import {of } from 'rxjs/observable/of';
|
||||||
|
@ -22,9 +22,7 @@ import {flatten, wrapIntoObservable} from './utils/collection';
|
||||||
export const ROUTES = new InjectionToken<Route[][]>('ROUTES');
|
export const ROUTES = new InjectionToken<Route[][]>('ROUTES');
|
||||||
|
|
||||||
export class LoadedRouterConfig {
|
export class LoadedRouterConfig {
|
||||||
constructor(
|
constructor(public routes: Route[], public module: NgModuleRef<any>) {}
|
||||||
public routes: Route[], public injector: Injector,
|
|
||||||
public factoryResolver: ComponentFactoryResolver, public injectorFactory: Function) {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class RouterConfigLoader {
|
export class RouterConfigLoader {
|
||||||
|
@ -46,11 +44,8 @@ export class RouterConfigLoader {
|
||||||
}
|
}
|
||||||
|
|
||||||
const module = factory.create(parentInjector);
|
const module = factory.create(parentInjector);
|
||||||
const injectorFactory = (parent: Injector) => factory.create(parent).injector;
|
|
||||||
|
|
||||||
return new LoadedRouterConfig(
|
return new LoadedRouterConfig(flatten(module.injector.get(ROUTES)), module);
|
||||||
flatten(module.injector.get(ROUTES)), module.injector, module.componentFactoryResolver,
|
|
||||||
injectorFactory);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
*found in the LICENSE file at https://angular.io/license
|
*found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {Compiler, Injectable, Injector, NgModuleFactoryLoader} from '@angular/core';
|
import {Compiler, Injectable, Injector, NgModuleFactoryLoader, NgModuleRef} from '@angular/core';
|
||||||
import {Observable} from 'rxjs/Observable';
|
import {Observable} from 'rxjs/Observable';
|
||||||
import {Subscription} from 'rxjs/Subscription';
|
import {Subscription} from 'rxjs/Subscription';
|
||||||
import {from} from 'rxjs/observable/from';
|
import {from} from 'rxjs/observable/from';
|
||||||
|
@ -91,37 +91,40 @@ export class RouterPreloader {
|
||||||
this.subscription = concatMap.call(navigations, () => this.preload()).subscribe(() => {});
|
this.subscription = concatMap.call(navigations, () => this.preload()).subscribe(() => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
preload(): Observable<any> { return this.processRoutes(this.injector, this.router.config); }
|
preload(): Observable<any> {
|
||||||
|
const ngModule = this.injector.get(NgModuleRef);
|
||||||
|
return this.processRoutes(ngModule, this.router.config);
|
||||||
|
}
|
||||||
|
|
||||||
ngOnDestroy(): void { this.subscription.unsubscribe(); }
|
ngOnDestroy(): void { this.subscription.unsubscribe(); }
|
||||||
|
|
||||||
private processRoutes(injector: Injector, routes: Routes): Observable<void> {
|
private processRoutes(ngModule: NgModuleRef<any>, routes: Routes): Observable<void> {
|
||||||
const res: Observable<any>[] = [];
|
const res: Observable<any>[] = [];
|
||||||
for (const c of routes) {
|
for (const c of routes) {
|
||||||
// we already have the config loaded, just recurse
|
// we already have the config loaded, just recurse
|
||||||
if (c.loadChildren && !c.canLoad && (<any>c)._loadedConfig) {
|
if (c.loadChildren && !c.canLoad && (<any>c)._loadedConfig) {
|
||||||
const childConfig = (<any>c)._loadedConfig;
|
const childConfig = (<any>c)._loadedConfig;
|
||||||
res.push(this.processRoutes(childConfig.injector, childConfig.routes));
|
res.push(this.processRoutes(childConfig.module, childConfig.routes));
|
||||||
|
|
||||||
// no config loaded, fetch the config
|
// no config loaded, fetch the config
|
||||||
} else if (c.loadChildren && !c.canLoad) {
|
} else if (c.loadChildren && !c.canLoad) {
|
||||||
res.push(this.preloadConfig(injector, c));
|
res.push(this.preloadConfig(ngModule, c));
|
||||||
|
|
||||||
// recurse into children
|
// recurse into children
|
||||||
} else if (c.children) {
|
} else if (c.children) {
|
||||||
res.push(this.processRoutes(injector, c.children));
|
res.push(this.processRoutes(ngModule, c.children));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return mergeAll.call(from(res));
|
return mergeAll.call(from(res));
|
||||||
}
|
}
|
||||||
|
|
||||||
private preloadConfig(injector: Injector, route: Route): Observable<void> {
|
private preloadConfig(ngModule: NgModuleRef<any>, route: Route): Observable<void> {
|
||||||
return this.preloadingStrategy.preload(route, () => {
|
return this.preloadingStrategy.preload(route, () => {
|
||||||
const loaded = this.loader.load(injector, route);
|
const loaded = this.loader.load(ngModule.injector, route);
|
||||||
return mergeMap.call(loaded, (config: any): any => {
|
return mergeMap.call(loaded, (config: any): any => {
|
||||||
const c: any = route;
|
const c: any = route;
|
||||||
c._loadedConfig = config;
|
c._loadedConfig = config;
|
||||||
return this.processRoutes(config.injector, config.routes);
|
return this.processRoutes(config.module, config.routes);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,8 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import {NgModuleRef} from '@angular/core';
|
||||||
|
import {TestBed} from '@angular/core/testing';
|
||||||
import {Observable} from 'rxjs/Observable';
|
import {Observable} from 'rxjs/Observable';
|
||||||
import {of } from 'rxjs/observable/of';
|
import {of } from 'rxjs/observable/of';
|
||||||
|
|
||||||
|
@ -16,10 +18,21 @@ import {DefaultUrlSerializer, UrlSegmentGroup, UrlTree, equalSegments} from '../
|
||||||
|
|
||||||
describe('applyRedirects', () => {
|
describe('applyRedirects', () => {
|
||||||
const serializer = new DefaultUrlSerializer();
|
const serializer = new DefaultUrlSerializer();
|
||||||
|
let testModule: NgModuleRef<any>;
|
||||||
|
|
||||||
|
beforeEach(() => { testModule = TestBed.get(NgModuleRef); });
|
||||||
|
|
||||||
it('should return the same url tree when no redirects', () => {
|
it('should return the same url tree when no redirects', () => {
|
||||||
checkRedirect(
|
checkRedirect(
|
||||||
[{path: 'a', component: ComponentA, children: [{path: 'b', component: ComponentB}]}],
|
[
|
||||||
|
{
|
||||||
|
path: 'a',
|
||||||
|
component: ComponentA,
|
||||||
|
children: [
|
||||||
|
{path: 'b', component: ComponentB},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
'/a/b', (t: UrlTree) => { compareTrees(t, tree('/a/b')); });
|
'/a/b', (t: UrlTree) => { compareTrees(t, tree('/a/b')); });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -39,7 +52,7 @@ describe('applyRedirects', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw when cannot handle a positional parameter', () => {
|
it('should throw when cannot handle a positional parameter', () => {
|
||||||
applyRedirects(null, null, serializer, tree('/a/1'), [
|
applyRedirects(testModule.injector, null, serializer, tree('/a/1'), [
|
||||||
{path: 'a/:id', redirectTo: 'a/:other'}
|
{path: 'a/:id', redirectTo: 'a/:other'}
|
||||||
]).subscribe(() => {}, (e) => {
|
]).subscribe(() => {}, (e) => {
|
||||||
expect(e.message).toEqual('Cannot redirect to \'a/:other\'. Cannot find \':other\'.');
|
expect(e.message).toEqual('Cannot redirect to \'a/:other\'. Cannot find \':other\'.');
|
||||||
|
@ -143,18 +156,16 @@ describe('applyRedirects', () => {
|
||||||
|
|
||||||
describe('lazy loading', () => {
|
describe('lazy loading', () => {
|
||||||
it('should load config on demand', () => {
|
it('should load config on demand', () => {
|
||||||
const loadedConfig = new LoadedRouterConfig(
|
const loadedConfig = new LoadedRouterConfig([{path: 'b', component: ComponentB}], testModule);
|
||||||
[{path: 'b', component: ComponentB}], <any>'stubInjector', <any>'stubFactoryResolver',
|
|
||||||
<any>'injectorFactory');
|
|
||||||
const loader = {
|
const loader = {
|
||||||
load: (injector: any, p: any) => {
|
load: (injector: any, p: any) => {
|
||||||
if (injector !== 'providedInjector') throw 'Invalid Injector';
|
if (injector !== testModule.injector) throw 'Invalid Injector';
|
||||||
return of (loadedConfig);
|
return of (loadedConfig);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const config = [{path: 'a', component: ComponentA, loadChildren: 'children'}];
|
const config = [{path: 'a', component: ComponentA, loadChildren: 'children'}];
|
||||||
|
|
||||||
applyRedirects(<any>'providedInjector', <any>loader, serializer, tree('a/b'), config)
|
applyRedirects(testModule.injector, <any>loader, serializer, tree('a/b'), config)
|
||||||
.forEach(r => {
|
.forEach(r => {
|
||||||
compareTrees(r, tree('/a/b'));
|
compareTrees(r, tree('/a/b'));
|
||||||
expect((<any>config[0])._loadedConfig).toBe(loadedConfig);
|
expect((<any>config[0])._loadedConfig).toBe(loadedConfig);
|
||||||
|
@ -167,18 +178,18 @@ describe('applyRedirects', () => {
|
||||||
};
|
};
|
||||||
const config = [{path: 'a', component: ComponentA, loadChildren: 'children'}];
|
const config = [{path: 'a', component: ComponentA, loadChildren: 'children'}];
|
||||||
|
|
||||||
applyRedirects(null, <any>loader, serializer, tree('a/b'), config)
|
applyRedirects(testModule.injector, <any>loader, serializer, tree('a/b'), config)
|
||||||
.subscribe(() => {}, (e) => { expect(e.message).toEqual('Loading Error'); });
|
.subscribe(() => {}, (e) => { expect(e.message).toEqual('Loading Error'); });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should load when all canLoad guards return true', () => {
|
it('should load when all canLoad guards return true', () => {
|
||||||
const loadedConfig = new LoadedRouterConfig(
|
const loadedConfig = new LoadedRouterConfig([{path: 'b', component: ComponentB}], testModule);
|
||||||
[{path: 'b', component: ComponentB}], <any>'stubInjector', <any>'stubFactoryResolver',
|
|
||||||
<any>'injectorFactory');
|
|
||||||
const loader = {load: (injector: any, p: any) => of (loadedConfig)};
|
const loader = {load: (injector: any, p: any) => of (loadedConfig)};
|
||||||
|
|
||||||
const guard = () => true;
|
const guard = () => true;
|
||||||
const injector = {get: () => guard};
|
const injector = {
|
||||||
|
get: (token: any) => token === 'guard1' || token === 'guard2' ? guard : {injector}
|
||||||
|
};
|
||||||
|
|
||||||
const config = [{
|
const config = [{
|
||||||
path: 'a',
|
path: 'a',
|
||||||
|
@ -193,14 +204,23 @@ describe('applyRedirects', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not load when any canLoad guards return false', () => {
|
it('should not load when any canLoad guards return false', () => {
|
||||||
const loadedConfig = new LoadedRouterConfig(
|
const loadedConfig = new LoadedRouterConfig([{path: 'b', component: ComponentB}], testModule);
|
||||||
[{path: 'b', component: ComponentB}], <any>'stubInjector', <any>'stubFactoryResolver',
|
|
||||||
<any>'injectorFactory');
|
|
||||||
const loader = {load: (injector: any, p: any) => of (loadedConfig)};
|
const loader = {load: (injector: any, p: any) => of (loadedConfig)};
|
||||||
|
|
||||||
const trueGuard = () => true;
|
const trueGuard = () => true;
|
||||||
const falseGuard = () => false;
|
const falseGuard = () => false;
|
||||||
const injector = {get: (guardName: any) => guardName === 'guard1' ? trueGuard : falseGuard};
|
const injector = {
|
||||||
|
get: (token: any) => {
|
||||||
|
switch (token) {
|
||||||
|
case 'guard1':
|
||||||
|
return trueGuard;
|
||||||
|
case 'guard2':
|
||||||
|
return falseGuard;
|
||||||
|
case NgModuleRef:
|
||||||
|
return {injector};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const config = [{
|
const config = [{
|
||||||
path: 'a',
|
path: 'a',
|
||||||
|
@ -219,14 +239,23 @@ describe('applyRedirects', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not load when any canLoad guards is rejected (promises)', () => {
|
it('should not load when any canLoad guards is rejected (promises)', () => {
|
||||||
const loadedConfig = new LoadedRouterConfig(
|
const loadedConfig = new LoadedRouterConfig([{path: 'b', component: ComponentB}], testModule);
|
||||||
[{path: 'b', component: ComponentB}], <any>'stubInjector', <any>'stubFactoryResolver',
|
|
||||||
<any>'injectorFactory');
|
|
||||||
const loader = {load: (injector: any, p: any) => of (loadedConfig)};
|
const loader = {load: (injector: any, p: any) => of (loadedConfig)};
|
||||||
|
|
||||||
const trueGuard = () => Promise.resolve(true);
|
const trueGuard = () => Promise.resolve(true);
|
||||||
const falseGuard = () => Promise.reject('someError');
|
const falseGuard = () => Promise.reject('someError');
|
||||||
const injector = {get: (guardName: any) => guardName === 'guard1' ? trueGuard : falseGuard};
|
const injector = {
|
||||||
|
get: (token: any) => {
|
||||||
|
switch (token) {
|
||||||
|
case 'guard1':
|
||||||
|
return trueGuard;
|
||||||
|
case 'guard2':
|
||||||
|
return falseGuard;
|
||||||
|
case NgModuleRef:
|
||||||
|
return {injector};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const config = [{
|
const config = [{
|
||||||
path: 'a',
|
path: 'a',
|
||||||
|
@ -241,13 +270,11 @@ describe('applyRedirects', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should work with objects implementing the CanLoad interface', () => {
|
it('should work with objects implementing the CanLoad interface', () => {
|
||||||
const loadedConfig = new LoadedRouterConfig(
|
const loadedConfig = new LoadedRouterConfig([{path: 'b', component: ComponentB}], testModule);
|
||||||
[{path: 'b', component: ComponentB}], <any>'stubInjector', <any>'stubFactoryResolver',
|
|
||||||
<any>'injectorFactory');
|
|
||||||
const loader = {load: (injector: any, p: any) => of (loadedConfig)};
|
const loader = {load: (injector: any, p: any) => of (loadedConfig)};
|
||||||
|
|
||||||
const guard = {canLoad: () => Promise.resolve(true)};
|
const guard = {canLoad: () => Promise.resolve(true)};
|
||||||
const injector = {get: () => guard};
|
const injector = {get: (token: any) => token === 'guard' ? guard : {injector}};
|
||||||
|
|
||||||
const config =
|
const config =
|
||||||
[{path: 'a', component: ComponentA, canLoad: ['guard'], loadChildren: 'children'}];
|
[{path: 'a', component: ComponentA, canLoad: ['guard'], loadChildren: 'children'}];
|
||||||
|
@ -259,26 +286,21 @@ describe('applyRedirects', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should work with absolute redirects', () => {
|
it('should work with absolute redirects', () => {
|
||||||
const loadedConfig = new LoadedRouterConfig(
|
const loadedConfig = new LoadedRouterConfig([{path: '', component: ComponentB}], testModule);
|
||||||
[{path: '', component: ComponentB}], <any>'stubInjector', <any>'stubFactoryResolver',
|
|
||||||
<any>'injectorFactory');
|
|
||||||
|
|
||||||
const loader = {load: (injector: any, p: any) => of (loadedConfig)};
|
const loader = {load: (injector: any, p: any) => of (loadedConfig)};
|
||||||
|
|
||||||
const config =
|
const config =
|
||||||
[{path: '', pathMatch: 'full', redirectTo: '/a'}, {path: 'a', loadChildren: 'children'}];
|
[{path: '', pathMatch: 'full', redirectTo: '/a'}, {path: 'a', loadChildren: 'children'}];
|
||||||
|
|
||||||
applyRedirects(<any>'providedInjector', <any>loader, serializer, tree(''), config)
|
applyRedirects(testModule.injector, <any>loader, serializer, tree(''), config).forEach(r => {
|
||||||
.forEach(r => {
|
compareTrees(r, tree('a'));
|
||||||
compareTrees(r, tree('a'));
|
expect((<any>config[1])._loadedConfig).toBe(loadedConfig);
|
||||||
expect((<any>config[1])._loadedConfig).toBe(loadedConfig);
|
});
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should load the configuration only once', () => {
|
it('should load the configuration only once', () => {
|
||||||
const loadedConfig = new LoadedRouterConfig(
|
const loadedConfig = new LoadedRouterConfig([{path: '', component: ComponentB}], testModule);
|
||||||
[{path: '', component: ComponentB}], <any>'stubInjector', <any>'stubFactoryResolver',
|
|
||||||
<any>'injectorFactory');
|
|
||||||
|
|
||||||
let called = false;
|
let called = false;
|
||||||
const loader = {
|
const loader = {
|
||||||
|
@ -291,10 +313,10 @@ describe('applyRedirects', () => {
|
||||||
|
|
||||||
const config = [{path: 'a', loadChildren: 'children'}];
|
const config = [{path: 'a', loadChildren: 'children'}];
|
||||||
|
|
||||||
applyRedirects(<any>'providedInjector', <any>loader, serializer, tree('a?k1'), config)
|
applyRedirects(testModule.injector, <any>loader, serializer, tree('a?k1'), config)
|
||||||
.subscribe(r => {});
|
.subscribe(r => {});
|
||||||
|
|
||||||
applyRedirects(<any>'providedInjector', <any>loader, serializer, tree('a?k2'), config)
|
applyRedirects(testModule.injector, <any>loader, serializer, tree('a?k2'), config)
|
||||||
.subscribe(
|
.subscribe(
|
||||||
r => {
|
r => {
|
||||||
compareTrees(r, tree('a?k2'));
|
compareTrees(r, tree('a?k2'));
|
||||||
|
@ -304,43 +326,37 @@ describe('applyRedirects', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should load the configuration of a wildcard route', () => {
|
it('should load the configuration of a wildcard route', () => {
|
||||||
const loadedConfig = new LoadedRouterConfig(
|
const loadedConfig = new LoadedRouterConfig([{path: '', component: ComponentB}], testModule);
|
||||||
[{path: '', component: ComponentB}], <any>'stubInjector', <any>'stubFactoryResolver',
|
|
||||||
<any>'injectorFactory');
|
|
||||||
|
|
||||||
const loader = {load: (injector: any, p: any) => of (loadedConfig)};
|
const loader = {load: (injector: any, p: any) => of (loadedConfig)};
|
||||||
|
|
||||||
const config = [{path: '**', loadChildren: 'children'}];
|
const config = [{path: '**', loadChildren: 'children'}];
|
||||||
|
|
||||||
applyRedirects(<any>'providedInjector', <any>loader, serializer, tree('xyz'), config)
|
applyRedirects(testModule.injector, <any>loader, serializer, tree('xyz'), config)
|
||||||
.forEach(r => { expect((<any>config[0])._loadedConfig).toBe(loadedConfig); });
|
.forEach(r => { expect((<any>config[0])._loadedConfig).toBe(loadedConfig); });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should load the configuration after a local redirect from a wildcard route', () => {
|
it('should load the configuration after a local redirect from a wildcard route', () => {
|
||||||
const loadedConfig = new LoadedRouterConfig(
|
const loadedConfig = new LoadedRouterConfig([{path: '', component: ComponentB}], testModule);
|
||||||
[{path: '', component: ComponentB}], <any>'stubInjector', <any>'stubFactoryResolver',
|
|
||||||
<any>'injectorFactory');
|
|
||||||
|
|
||||||
const loader = {load: (injector: any, p: any) => of (loadedConfig)};
|
const loader = {load: (injector: any, p: any) => of (loadedConfig)};
|
||||||
|
|
||||||
const config =
|
const config =
|
||||||
[{path: 'not-found', loadChildren: 'children'}, {path: '**', redirectTo: 'not-found'}];
|
[{path: 'not-found', loadChildren: 'children'}, {path: '**', redirectTo: 'not-found'}];
|
||||||
|
|
||||||
applyRedirects(<any>'providedInjector', <any>loader, serializer, tree('xyz'), config)
|
applyRedirects(testModule.injector, <any>loader, serializer, tree('xyz'), config)
|
||||||
.forEach(r => { expect((<any>config[0])._loadedConfig).toBe(loadedConfig); });
|
.forEach(r => { expect((<any>config[0])._loadedConfig).toBe(loadedConfig); });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should load the configuration after an absolute redirect from a wildcard route', () => {
|
it('should load the configuration after an absolute redirect from a wildcard route', () => {
|
||||||
const loadedConfig = new LoadedRouterConfig(
|
const loadedConfig = new LoadedRouterConfig([{path: '', component: ComponentB}], testModule);
|
||||||
[{path: '', component: ComponentB}], <any>'stubInjector', <any>'stubFactoryResolver',
|
|
||||||
<any>'injectorFactory');
|
|
||||||
|
|
||||||
const loader = {load: (injector: any, p: any) => of (loadedConfig)};
|
const loader = {load: (injector: any, p: any) => of (loadedConfig)};
|
||||||
|
|
||||||
const config =
|
const config =
|
||||||
[{path: 'not-found', loadChildren: 'children'}, {path: '**', redirectTo: '/not-found'}];
|
[{path: 'not-found', loadChildren: 'children'}, {path: '**', redirectTo: '/not-found'}];
|
||||||
|
|
||||||
applyRedirects(<any>'providedInjector', <any>loader, serializer, tree('xyz'), config)
|
applyRedirects(testModule.injector, <any>loader, serializer, tree('xyz'), config)
|
||||||
.forEach(r => { expect((<any>config[0])._loadedConfig).toBe(loadedConfig); });
|
.forEach(r => { expect((<any>config[0])._loadedConfig).toBe(loadedConfig); });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -388,7 +404,7 @@ describe('applyRedirects', () => {
|
||||||
{path: '', redirectTo: 'a', pathMatch: 'full'}
|
{path: '', redirectTo: 'a', pathMatch: 'full'}
|
||||||
];
|
];
|
||||||
|
|
||||||
applyRedirects(null, null, serializer, tree('b'), config)
|
applyRedirects(testModule.injector, null, serializer, tree('b'), config)
|
||||||
.subscribe(
|
.subscribe(
|
||||||
(_) => { throw 'Should not be reached'; },
|
(_) => { throw 'Should not be reached'; },
|
||||||
e => { expect(e.message).toEqual('Cannot match any routes. URL Segment: \'b\''); });
|
e => { expect(e.message).toEqual('Cannot match any routes. URL Segment: \'b\''); });
|
||||||
|
@ -518,7 +534,7 @@ describe('applyRedirects', () => {
|
||||||
]
|
]
|
||||||
}];
|
}];
|
||||||
|
|
||||||
applyRedirects(null, null, serializer, tree('a/(d//aux:e)'), config)
|
applyRedirects(testModule.injector, null, serializer, tree('a/(d//aux:e)'), config)
|
||||||
.subscribe(
|
.subscribe(
|
||||||
(_) => { throw 'Should not be reached'; },
|
(_) => { throw 'Should not be reached'; },
|
||||||
e => { expect(e.message).toEqual('Cannot match any routes. URL Segment: \'a\''); });
|
e => { expect(e.message).toEqual('Cannot match any routes. URL Segment: \'a\''); });
|
||||||
|
@ -549,7 +565,7 @@ describe('applyRedirects', () => {
|
||||||
|
|
||||||
it('should error when no children matching and some url is left', () => {
|
it('should error when no children matching and some url is left', () => {
|
||||||
applyRedirects(
|
applyRedirects(
|
||||||
null, null, serializer, tree('/a/c'),
|
testModule.injector, null, serializer, tree('/a/c'),
|
||||||
[{path: 'a', component: ComponentA, children: [{path: 'b', component: ComponentB}]}])
|
[{path: 'a', component: ComponentA, children: [{path: 'b', component: ComponentB}]}])
|
||||||
.subscribe(
|
.subscribe(
|
||||||
(_) => { throw 'Should not be reached'; },
|
(_) => { throw 'Should not be reached'; },
|
||||||
|
@ -599,7 +615,7 @@ describe('applyRedirects', () => {
|
||||||
|
|
||||||
it('should throw when using non-absolute redirects', () => {
|
it('should throw when using non-absolute redirects', () => {
|
||||||
applyRedirects(
|
applyRedirects(
|
||||||
null, null, serializer, tree('a'),
|
testModule.injector, null, serializer, tree('a'),
|
||||||
[
|
[
|
||||||
{path: 'a', redirectTo: 'b(aux:c)'},
|
{path: 'a', redirectTo: 'b(aux:c)'},
|
||||||
])
|
])
|
||||||
|
@ -614,7 +630,7 @@ describe('applyRedirects', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
function checkRedirect(config: Routes, url: string, callback: any): void {
|
function checkRedirect(config: Routes, url: string, callback: any): void {
|
||||||
applyRedirects(null, null, new DefaultUrlSerializer(), tree(url), config)
|
applyRedirects(TestBed, null, new DefaultUrlSerializer(), tree(url), config)
|
||||||
.subscribe(callback, e => { throw e; });
|
.subscribe(callback, e => { throw e; });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {CommonModule, Location} from '@angular/common';
|
import {CommonModule, Location} from '@angular/common';
|
||||||
import {Component, NgModule, NgModuleFactoryLoader} from '@angular/core';
|
import {Component, Inject, Injectable, NgModule, NgModuleFactoryLoader, NgModuleRef} from '@angular/core';
|
||||||
import {ComponentFixture, TestBed, fakeAsync, inject, tick} from '@angular/core/testing';
|
import {ComponentFixture, TestBed, fakeAsync, inject, tick} from '@angular/core/testing';
|
||||||
import {By} from '@angular/platform-browser/src/dom/debug/by';
|
import {By} from '@angular/platform-browser/src/dom/debug/by';
|
||||||
import {expect} from '@angular/platform-browser/testing/src/matchers';
|
import {expect} from '@angular/platform-browser/testing/src/matchers';
|
||||||
|
@ -2422,6 +2422,184 @@ describe('Integration', () => {
|
||||||
expect(fixture.nativeElement).toHaveText('lazy-loaded-parent [lazy-loaded-child]');
|
expect(fixture.nativeElement).toHaveText('lazy-loaded-parent [lazy-loaded-child]');
|
||||||
})));
|
})));
|
||||||
|
|
||||||
|
it('should have 2 injector trees: module and element',
|
||||||
|
fakeAsync(inject(
|
||||||
|
[Router, Location, NgModuleFactoryLoader],
|
||||||
|
(router: Router, location: Location, loader: SpyNgModuleFactoryLoader) => {
|
||||||
|
@Component({
|
||||||
|
selector: 'lazy',
|
||||||
|
template: 'parent[<router-outlet></router-outlet>]',
|
||||||
|
viewProviders: [
|
||||||
|
{provide: 'shadow', useValue: 'from parent component'},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
class Parent {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({selector: 'lazy', template: 'child'})
|
||||||
|
class Child {
|
||||||
|
}
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [Parent],
|
||||||
|
imports: [RouterModule.forChild([{
|
||||||
|
path: 'parent',
|
||||||
|
component: Parent,
|
||||||
|
children: [
|
||||||
|
{path: 'child', loadChildren: 'child'},
|
||||||
|
]
|
||||||
|
}])],
|
||||||
|
providers: [
|
||||||
|
{provide: 'moduleName', useValue: 'parent'},
|
||||||
|
{provide: 'fromParent', useValue: 'from parent'},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
class ParentModule {
|
||||||
|
}
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [Child],
|
||||||
|
imports: [RouterModule.forChild([{path: '', component: Child}])],
|
||||||
|
providers: [
|
||||||
|
{provide: 'moduleName', useValue: 'child'},
|
||||||
|
{provide: 'fromChild', useValue: 'from child'},
|
||||||
|
{provide: 'shadow', useValue: 'from child module'},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
class ChildModule {
|
||||||
|
}
|
||||||
|
|
||||||
|
loader.stubbedModules = {
|
||||||
|
parent: ParentModule,
|
||||||
|
child: ChildModule,
|
||||||
|
};
|
||||||
|
|
||||||
|
const fixture = createRoot(router, RootCmp);
|
||||||
|
router.resetConfig([{path: 'lazy', loadChildren: 'parent'}]);
|
||||||
|
router.navigateByUrl('/lazy/parent/child');
|
||||||
|
advance(fixture);
|
||||||
|
expect(location.path()).toEqual('/lazy/parent/child');
|
||||||
|
expect(fixture.nativeElement).toHaveText('parent[child]');
|
||||||
|
|
||||||
|
const pInj = fixture.debugElement.query(By.directive(Parent)).injector;
|
||||||
|
const cInj = fixture.debugElement.query(By.directive(Child)).injector;
|
||||||
|
|
||||||
|
expect(pInj.get('moduleName')).toEqual('parent');
|
||||||
|
expect(pInj.get('fromParent')).toEqual('from parent');
|
||||||
|
expect(pInj.get(Parent)).toBeAnInstanceOf(Parent);
|
||||||
|
expect(pInj.get('fromChild', null)).toEqual(null);
|
||||||
|
expect(pInj.get(Child, null)).toEqual(null);
|
||||||
|
|
||||||
|
expect(cInj.get('moduleName')).toEqual('child');
|
||||||
|
expect(cInj.get('fromParent')).toEqual('from parent');
|
||||||
|
expect(cInj.get('fromChild')).toEqual('from child');
|
||||||
|
expect(cInj.get(Parent)).toBeAnInstanceOf(Parent);
|
||||||
|
expect(cInj.get(Child)).toBeAnInstanceOf(Child);
|
||||||
|
// The child module can not shadow the parent component
|
||||||
|
expect(cInj.get('shadow')).toEqual('from parent component');
|
||||||
|
|
||||||
|
const pmInj = pInj.get(NgModuleRef).injector;
|
||||||
|
const cmInj = cInj.get(NgModuleRef).injector;
|
||||||
|
|
||||||
|
expect(pmInj.get('moduleName')).toEqual('parent');
|
||||||
|
expect(cmInj.get('moduleName')).toEqual('child');
|
||||||
|
|
||||||
|
expect(pmInj.get(Parent, '-')).toEqual('-');
|
||||||
|
expect(cmInj.get(Parent, '-')).toEqual('-');
|
||||||
|
expect(pmInj.get(Child, '-')).toEqual('-');
|
||||||
|
expect(cmInj.get(Child, '-')).toEqual('-');
|
||||||
|
})));
|
||||||
|
|
||||||
|
// https://github.com/angular/angular/issues/12889
|
||||||
|
it('should create a single instance of lazy-loaded modules',
|
||||||
|
fakeAsync(inject(
|
||||||
|
[Router, Location, NgModuleFactoryLoader],
|
||||||
|
(router: Router, location: Location, loader: SpyNgModuleFactoryLoader) => {
|
||||||
|
@Component({
|
||||||
|
selector: 'lazy',
|
||||||
|
template: 'lazy-loaded-parent [<router-outlet></router-outlet>]'
|
||||||
|
})
|
||||||
|
class ParentLazyLoadedComponent {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({selector: 'lazy', template: 'lazy-loaded-child'})
|
||||||
|
class ChildLazyLoadedComponent {
|
||||||
|
}
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [ParentLazyLoadedComponent, ChildLazyLoadedComponent],
|
||||||
|
imports: [RouterModule.forChild([{
|
||||||
|
path: 'loaded',
|
||||||
|
component: ParentLazyLoadedComponent,
|
||||||
|
children: [{path: 'child', component: ChildLazyLoadedComponent}]
|
||||||
|
}])]
|
||||||
|
})
|
||||||
|
class LoadedModule {
|
||||||
|
static instances = 0;
|
||||||
|
constructor() { LoadedModule.instances++; }
|
||||||
|
}
|
||||||
|
|
||||||
|
loader.stubbedModules = {expected: LoadedModule};
|
||||||
|
const fixture = createRoot(router, RootCmp);
|
||||||
|
router.resetConfig([{path: 'lazy', loadChildren: 'expected'}]);
|
||||||
|
router.navigateByUrl('/lazy/loaded/child');
|
||||||
|
advance(fixture);
|
||||||
|
expect(fixture.nativeElement).toHaveText('lazy-loaded-parent [lazy-loaded-child]');
|
||||||
|
expect(LoadedModule.instances).toEqual(1);
|
||||||
|
})));
|
||||||
|
|
||||||
|
// https://github.com/angular/angular/issues/13870
|
||||||
|
it('should create a single instance of guards for lazy-loaded modules',
|
||||||
|
fakeAsync(inject(
|
||||||
|
[Router, Location, NgModuleFactoryLoader],
|
||||||
|
(router: Router, location: Location, loader: SpyNgModuleFactoryLoader) => {
|
||||||
|
@Injectable()
|
||||||
|
class Service {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
class Resolver implements Resolve<Service> {
|
||||||
|
constructor(public service: Service) {}
|
||||||
|
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
|
||||||
|
return this.service;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({selector: 'lazy', template: 'lazy'})
|
||||||
|
class LazyLoadedComponent {
|
||||||
|
resolvedService: Service;
|
||||||
|
constructor(public injectedService: Service, route: ActivatedRoute) {
|
||||||
|
this.resolvedService = route.snapshot.data['service'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [LazyLoadedComponent],
|
||||||
|
providers: [Service, Resolver],
|
||||||
|
imports: [
|
||||||
|
RouterModule.forChild([{
|
||||||
|
path: 'loaded',
|
||||||
|
component: LazyLoadedComponent,
|
||||||
|
resolve: {'service': Resolver},
|
||||||
|
}]),
|
||||||
|
]
|
||||||
|
})
|
||||||
|
class LoadedModule {
|
||||||
|
}
|
||||||
|
|
||||||
|
loader.stubbedModules = {expected: LoadedModule};
|
||||||
|
const fixture = createRoot(router, RootCmp);
|
||||||
|
router.resetConfig([{path: 'lazy', loadChildren: 'expected'}]);
|
||||||
|
router.navigateByUrl('/lazy/loaded');
|
||||||
|
advance(fixture);
|
||||||
|
|
||||||
|
expect(fixture.nativeElement).toHaveText('lazy');
|
||||||
|
const lzc =
|
||||||
|
fixture.debugElement.query(By.directive(LazyLoadedComponent)).componentInstance;
|
||||||
|
expect(lzc.injectedService).toBe(lzc.resolvedService);
|
||||||
|
})));
|
||||||
|
|
||||||
|
|
||||||
it('should emit RouteConfigLoadStart and RouteConfigLoadEnd event when route is lazy loaded',
|
it('should emit RouteConfigLoadStart and RouteConfigLoadEnd event when route is lazy loaded',
|
||||||
fakeAsync(inject(
|
fakeAsync(inject(
|
||||||
[Router, Location, NgModuleFactoryLoader],
|
[Router, Location, NgModuleFactoryLoader],
|
||||||
|
@ -2547,7 +2725,6 @@ describe('Integration', () => {
|
||||||
|
|
||||||
describe('should use the injector of the lazily-loaded configuration', () => {
|
describe('should use the injector of the lazily-loaded configuration', () => {
|
||||||
class LazyLoadedServiceDefinedInModule {}
|
class LazyLoadedServiceDefinedInModule {}
|
||||||
class LazyLoadedServiceDefinedInCmp {}
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'eager-parent',
|
selector: 'eager-parent',
|
||||||
|
@ -2556,11 +2733,17 @@ describe('Integration', () => {
|
||||||
class EagerParentComponent {
|
class EagerParentComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({selector: 'lazy-parent', template: 'lazy-parent <router-outlet></router-outlet>'})
|
@Component({
|
||||||
|
selector: 'lazy-parent',
|
||||||
|
template: 'lazy-parent <router-outlet></router-outlet>',
|
||||||
|
})
|
||||||
class LazyParentComponent {
|
class LazyParentComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({selector: 'lazy-child', template: 'lazy-child'})
|
@Component({
|
||||||
|
selector: 'lazy-child',
|
||||||
|
template: 'lazy-child',
|
||||||
|
})
|
||||||
class LazyChildComponent {
|
class LazyChildComponent {
|
||||||
constructor(
|
constructor(
|
||||||
lazy: LazyParentComponent, // should be able to inject lazy/direct parent
|
lazy: LazyParentComponent, // should be able to inject lazy/direct parent
|
||||||
|
@ -2593,7 +2776,11 @@ describe('Integration', () => {
|
||||||
class TestModule {
|
class TestModule {
|
||||||
}
|
}
|
||||||
|
|
||||||
beforeEach(() => { TestBed.configureTestingModule({imports: [TestModule]}); });
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [TestModule],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('should use the injector of the lazily-loaded configuration',
|
it('should use the injector of the lazily-loaded configuration',
|
||||||
fakeAsync(inject(
|
fakeAsync(inject(
|
||||||
|
|
|
@ -227,7 +227,7 @@ export interface ComponentDecorator {
|
||||||
export declare abstract class ComponentFactory<C> {
|
export declare abstract class ComponentFactory<C> {
|
||||||
readonly abstract componentType: Type<any>;
|
readonly abstract componentType: Type<any>;
|
||||||
readonly abstract selector: string;
|
readonly abstract selector: string;
|
||||||
abstract create(injector: Injector, projectableNodes?: any[][], rootSelectorOrNode?: string | any): ComponentRef<C>;
|
abstract create(injector: Injector, projectableNodes?: any[][], rootSelectorOrNode?: string | any, ngModule?: NgModuleRef<any>): ComponentRef<C>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @stable */
|
/** @stable */
|
||||||
|
|
|
@ -320,11 +320,12 @@ export declare class RouterOutlet implements OnDestroy {
|
||||||
readonly component: Object;
|
readonly component: Object;
|
||||||
deactivateEvents: EventEmitter<any>;
|
deactivateEvents: EventEmitter<any>;
|
||||||
readonly isActivated: boolean;
|
readonly isActivated: boolean;
|
||||||
readonly locationFactoryResolver: ComponentFactoryResolver;
|
/** @deprecated */ readonly locationFactoryResolver: ComponentFactoryResolver;
|
||||||
readonly locationInjector: Injector;
|
/** @deprecated */ readonly locationInjector: Injector;
|
||||||
outletMap: RouterOutletMap;
|
outletMap: RouterOutletMap;
|
||||||
constructor(parentOutletMap: RouterOutletMap, location: ViewContainerRef, resolver: ComponentFactoryResolver, name: string);
|
constructor(parentOutletMap: RouterOutletMap, location: ViewContainerRef, resolver: ComponentFactoryResolver, name: string);
|
||||||
activate(activatedRoute: ActivatedRoute, resolver: ComponentFactoryResolver, injector: Injector, providers: ResolvedReflectiveProvider[], outletMap: RouterOutletMap): void;
|
/** @deprecated */ activate(activatedRoute: ActivatedRoute, resolver: ComponentFactoryResolver, injector: Injector, providers: ResolvedReflectiveProvider[], outletMap: RouterOutletMap): void;
|
||||||
|
activateWith(activatedRoute: ActivatedRoute, resolver: ComponentFactoryResolver | null, outletMap: RouterOutletMap): void;
|
||||||
attach(ref: ComponentRef<any>, activatedRoute: ActivatedRoute): void;
|
attach(ref: ComponentRef<any>, activatedRoute: ActivatedRoute): void;
|
||||||
deactivate(): void;
|
deactivate(): void;
|
||||||
detach(): ComponentRef<any>;
|
detach(): ComponentRef<any>;
|
||||||
|
|
Loading…
Reference in New Issue