Skip to content

Commit

Permalink
fix: element injector vs module injector (#15044)
Browse files Browse the repository at this point in the history
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`
  • Loading branch information
vicb authored and chuckjaz committed Mar 14, 2017
1 parent f093501 commit 13686bb
Show file tree
Hide file tree
Showing 29 changed files with 627 additions and 242 deletions.
16 changes: 13 additions & 3 deletions modules/benchmarks/src/tree/ng2_next/tree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
*/

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 {DomRendererFactory2} from '@angular/platform-browser/src/dom/dom_renderer';
import {DomSanitizerImpl, SafeStyle} from '@angular/platform-browser/src/security/dom_sanitization_service';
Expand Down Expand Up @@ -84,7 +84,7 @@ function TreeComponent_0(): ViewDefinition {
});
}

export class AppModule implements Injector {
export class AppModule implements Injector, NgModuleRef<any> {
private sanitizer: DomSanitizerImpl;
private componentFactory: ComponentFactory<TreeComponent>;
private renderer2: RendererFactory2;
Expand All @@ -108,12 +108,22 @@ export class AppModule implements Injector {
return this.sanitizer;
case RootRenderer:
return null;
case NgModuleRef:
return this;
}
return Injector.NULL.get(token, notFoundValue);
}

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(); }

get injector() { return this; }
get componentFactoryResolver(): ComponentFactoryResolver { return null; }
get instance() { return this; }
destroy() {}
onDestroy(callback: () => void) {}
}
27 changes: 13 additions & 14 deletions packages/common/src/directives/ng_component_outlet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@

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.
* `NgComponentOutlet` provides a declarative approach for dynamic component creation.
Expand Down Expand Up @@ -81,34 +79,35 @@ export class NgComponentOutlet implements OnChanges, OnDestroy {
constructor(private _viewContainerRef: ViewContainerRef) {}

ngOnChanges(changes: SimpleChanges) {
if (this._componentRef) {
this._viewContainerRef.remove(this._viewContainerRef.indexOf(this._componentRef.hostView));
}
this._viewContainerRef.clear();
this._componentRef = null;

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.ngComponentOutletNgModuleFactory) {
this._moduleRef = this.ngComponentOutletNgModuleFactory.create(injector);
const parentModule = elInjector.get(NgModuleRef);
this._moduleRef = this.ngComponentOutletNgModuleFactory.create(parentModule.injector);
} else {
this._moduleRef = null;
}
}
if (this._moduleRef) {
injector = this._moduleRef.injector;
}

let componentFactory =
injector.get(ComponentFactoryResolver).resolveComponentFactory(this.ngComponentOutlet);
const componentFactoryResolver = this._moduleRef ? this._moduleRef.componentFactoryResolver :
elInjector.get(ComponentFactoryResolver);

const componentFactory =
componentFactoryResolver.resolveComponentFactory(this.ngComponentOutlet);

this._componentRef = this._viewContainerRef.createComponent(
componentFactory, this._viewContainerRef.length, injector, this.ngComponentOutletContent);
componentFactory, this._viewContainerRef.length, elInjector,
this.ngComponentOutletContent);
}
}

ngOnDestroy() {
if (this._moduleRef) this._moduleRef.destroy();
}
Expand Down
10 changes: 5 additions & 5 deletions packages/common/test/directives/ng_component_outlet_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,13 +177,13 @@ export function main() {
it('should not re-create moduleRef when it didn\'t actually change', async(() => {
const compiler = TestBed.get(Compiler) as Compiler;
const fixture = TestBed.createComponent(TestComponent);

fixture.componentInstance.module = compiler.compileModuleSync(TestModule2);
fixture.componentInstance.currentComponent = Module2InjectedComponent;
fixture.detectChanges();

expect(fixture.nativeElement).toHaveText('baz');

const moduleRef = fixture.componentInstance.ngComponentOutlet['_moduleRef'];

fixture.componentInstance.currentComponent = Module2InjectedComponent2;
fixture.detectChanges();

Expand Down Expand Up @@ -247,11 +247,11 @@ class TestComponent {
export class TestModule {
}

@Component({selector: 'mdoule-2-injected-component', template: 'baz'})
@Component({selector: 'module-2-injected-component', template: 'baz'})
class Module2InjectedComponent {
}

@Component({selector: 'mdoule-2-injected-component-2', template: 'baz2'})
@Component({selector: 'module-2-injected-component-2', template: 'baz2'})
class Module2InjectedComponent2 {
}

Expand All @@ -264,7 +264,7 @@ class Module2InjectedComponent2 {
export class TestModule2 {
}

@Component({selector: 'mdoule-3-injected-component', template: 'bat'})
@Component({selector: 'module-3-injected-component', template: 'bat'})
class Module3InjectedComponent {
}

Expand Down
3 changes: 2 additions & 1 deletion packages/compiler/src/identifiers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* 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';

Expand All @@ -26,6 +26,7 @@ export class Identifiers {
runtime: ANALYZE_FOR_ENTRY_COMPONENTS
};
static ElementRef: IdentifierSpec = {name: 'ElementRef', moduleUrl: CORE, runtime: ElementRef};
static NgModuleRef: IdentifierSpec = {name: 'NgModuleRef', moduleUrl: CORE, runtime: NgModuleRef};
static ViewContainerRef:
IdentifierSpec = {name: 'ViewContainerRef', moduleUrl: CORE, runtime: ViewContainerRef};
static ChangeDetectorRef:
Expand Down
12 changes: 8 additions & 4 deletions packages/compiler/src/ng_module_compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -216,11 +216,15 @@ class _InjectorBuilder implements ClassBuilder {
result = o.literal(dep.value);
}
if (!dep.isSkipSelf) {
if (dep.token &&
(tokenReference(dep.token) === resolveIdentifier(Identifiers.Injector) ||
tokenReference(dep.token) === resolveIdentifier(Identifiers.ComponentFactoryResolver))) {
result = o.THIS_EXPR;
if (dep.token) {
if (tokenReference(dep.token) === resolveIdentifier(Identifiers.Injector)) {
result = o.THIS_EXPR;
} else if (
tokenReference(dep.token) === resolveIdentifier(Identifiers.ComponentFactoryResolver)) {
result = o.THIS_EXPR.prop('componentFactoryResolver');
}
}

if (!result) {
result = this._instances.get(tokenReference(dep.token));
}
Expand Down
7 changes: 4 additions & 3 deletions packages/compiler/src/view_compiler/view_compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1125,13 +1125,14 @@ function createComponentFactoryResolver(directives: DirectiveAst[]): ProviderAst
if (componentDirMeta && componentDirMeta.directive.entryComponents.length) {
const entryComponentFactories = componentDirMeta.directive.entryComponents.map(
(entryComponent) => o.importExpr({reference: entryComponent.componentFactory}));
const cfrExpr = o.importExpr(createIdentifier(Identifiers.CodegenComponentFactoryResolver))
.instantiate([o.literalArr(entryComponentFactories)]);

const token = createIdentifierToken(Identifiers.ComponentFactoryResolver);

const classMeta: CompileTypeMetadata = {
diDeps: [
{isValue: true, value: o.literalArr(entryComponentFactories)},
{token: token, isSkipSelf: true, isOptional: true}
{token: token, isSkipSelf: true, isOptional: true},
{token: createIdentifierToken(Identifiers.NgModuleRef)},
],
lifecycleHooks: [],
reference: resolveIdentifier(Identifiers.CodegenComponentFactoryResolver)
Expand Down
17 changes: 12 additions & 5 deletions packages/core/src/application_ref.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,21 @@

import {Observable} from 'rxjs/Observable';
import {Observer} from 'rxjs/Observer';
import {Subject} from 'rxjs/Subject';
import {Subscription} from 'rxjs/Subscription';
import {merge} from 'rxjs/observable/merge';
import {share} from 'rxjs/operator/share';

import {ErrorHandler} from '../src/error_handler';
import {scheduleMicroTask, stringify} from '../src/util';
import {isPromise} from '../src/util/lang';

import {ApplicationInitStatus} from './application_init';
import {APP_BOOTSTRAP_LISTENER, PLATFORM_INITIALIZER} from './application_tokens';
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 {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 {InternalViewRef, ViewRef} from './linker/view_ref';
import {WtfScopeFn, wtfCreateScope, wtfLeave} from './profile/profile';
Expand Down Expand Up @@ -328,7 +329,7 @@ export class PlatformRef_ extends PlatformRef {
private _moduleDoBootstrap(moduleRef: NgModuleInjector<any>): void {
const appRef = moduleRef.injector.get(ApplicationRef);
if (moduleRef.bootstrapFactories.length > 0) {
moduleRef.bootstrapFactories.forEach((compFactory) => appRef.bootstrap(compFactory));
moduleRef.bootstrapFactories.forEach(f => appRef.bootstrap(f));
} else if (moduleRef.instance.ngDoBootstrap) {
moduleRef.instance.ngDoBootstrap(appRef);
} else {
Expand Down Expand Up @@ -502,7 +503,13 @@ export class ApplicationRef_ extends ApplicationRef {
componentFactory = this._componentFactoryResolver.resolveComponentFactory(componentOrFactory);
}
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); });
const testability = compRef.injector.get(Testability, null);
if (testability) {
Expand Down
6 changes: 4 additions & 2 deletions packages/core/src/linker/component_factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {Injector} from '../di/injector';
import {Type} from '../type';

import {ElementRef} from './element_ref';
import {NgModuleRef} from './ng_module_factory';
import {ViewRef} from './view_ref';

/**
Expand Down Expand Up @@ -72,6 +73,7 @@ export abstract class ComponentFactory<C> {
/**
* Creates a new component.
*/
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>;
}
32 changes: 23 additions & 9 deletions packages/core/src/linker/component_factory_resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@
* found in the LICENSE file at https://angular.io/license
*/

import {Injector} from '../di/injector';
import {Type} from '../type';
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) {
const error = Error(
Expand Down Expand Up @@ -44,18 +44,32 @@ export abstract class ComponentFactoryResolver {
export class CodegenComponentFactoryResolver implements ComponentFactoryResolver {
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++) {
const factory = factories[i];
this._factories.set(factory.componentType, factory);
}
}

resolveComponentFactory<T>(component: {new (...args: any[]): T}): ComponentFactory<T> {
let result = this._factories.get(component);
if (!result) {
result = this._parent.resolveComponentFactory(component);
}
return result;
let factory = this._factories.get(component) || this._parent.resolveComponentFactory(component);

return factory ? new ComponentFactoryBoundToModule(factory, this._ngModule) : null;
}
}

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);
}
}
34 changes: 19 additions & 15 deletions packages/core/src/linker/ng_module_factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@ import {Type} from '../type';
import {stringify} from '../util';

import {ComponentFactory} from './component_factory';
import {CodegenComponentFactoryResolver, ComponentFactoryResolver} from './component_factory_resolver';

import {CodegenComponentFactoryResolver, ComponentFactoryBoundToModule, ComponentFactoryResolver} from './component_factory_resolver';


/**
Expand Down Expand Up @@ -62,39 +61,44 @@ export class NgModuleFactory<T> {
get moduleType(): Type<T> { return this._moduleType; }

create(parentInjector: Injector): NgModuleRef<T> {
if (!parentInjector) {
parentInjector = Injector.NULL;
}
const instance = new this._injectorClass(parentInjector);
const instance = new this._injectorClass(parentInjector || Injector.NULL);
instance.create();
return instance;
}
}

const _UNDEFINED = new Object();

export abstract class NgModuleInjector<T> extends CodegenComponentFactoryResolver implements
Injector,
NgModuleRef<T> {
export abstract class NgModuleInjector<T> implements Injector, NgModuleRef<T> {
bootstrapFactories: ComponentFactory<any>[];
instance: T;

private _destroyListeners: (() => void)[] = [];
private _destroyed: boolean = false;

public instance: T;
private _cmpFactoryResolver: CodegenComponentFactoryResolver;

constructor(
public parent: Injector, factories: ComponentFactory<any>[],
public bootstrapFactories: ComponentFactory<any>[]) {
super(factories, parent.get(ComponentFactoryResolver, ComponentFactoryResolver.NULL));
bootstrapFactories: ComponentFactory<any>[]) {
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(); }

abstract createInternal(): T;

get(token: any, notFoundValue: any = THROW_IF_NOT_FOUND): any {
if (token === Injector || token === ComponentFactoryResolver) {
if (token === Injector || token === NgModuleRef) {
return this;
}

if (token === ComponentFactoryResolver) {
return this._cmpFactoryResolver;
}

const result = this.getInternal(token, _UNDEFINED);
return result === _UNDEFINED ? this.parent.get(token, notFoundValue) : result;
}
Expand All @@ -103,7 +107,7 @@ export abstract class NgModuleInjector<T> extends CodegenComponentFactoryResolve

get injector(): Injector { return this; }

get componentFactoryResolver(): ComponentFactoryResolver { return this; }
get componentFactoryResolver(): ComponentFactoryResolver { return this._cmpFactoryResolver; }

destroy(): void {
if (this._destroyed) {
Expand Down
Loading

0 comments on commit 13686bb

Please sign in to comment.