fix(core): provide NgModuleRef
in ViewContainerRef.createComponent
. (#15350)
This is needed to support the corner cases: - usage of a `ComponentFactory` that was created on the fly via `Compiler` - overwriting of the `NgModuleRef` that is associated to a `ComponentFactory` by the `ComponentFactoryResolver` from which it was read. Fixes #15241
This commit is contained in:
parent
8e6995c91e
commit
431eb309f3
@ -9,6 +9,7 @@
|
|||||||
import {Injector} from '../di/injector';
|
import {Injector} from '../di/injector';
|
||||||
import {ComponentFactory, ComponentRef} from './component_factory';
|
import {ComponentFactory, ComponentRef} from './component_factory';
|
||||||
import {ElementRef} from './element_ref';
|
import {ElementRef} from './element_ref';
|
||||||
|
import {NgModuleRef} from './ng_module_factory';
|
||||||
import {TemplateRef} from './template_ref';
|
import {TemplateRef} from './template_ref';
|
||||||
import {EmbeddedViewRef, ViewRef} from './view_ref';
|
import {EmbeddedViewRef, ViewRef} from './view_ref';
|
||||||
|
|
||||||
@ -83,7 +84,7 @@ export abstract class ViewContainerRef {
|
|||||||
*/
|
*/
|
||||||
abstract createComponent<C>(
|
abstract createComponent<C>(
|
||||||
componentFactory: ComponentFactory<C>, index?: number, injector?: Injector,
|
componentFactory: ComponentFactory<C>, index?: number, injector?: Injector,
|
||||||
projectableNodes?: any[][]): ComponentRef<C>;
|
projectableNodes?: any[][], ngModule?: NgModuleRef<any>): ComponentRef<C>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inserts a View identified by a {@link ViewRef} into the container at the specified `index`.
|
* Inserts a View identified by a {@link ViewRef} into the container at the specified `index`.
|
||||||
|
@ -356,7 +356,7 @@ export function resolveDep(
|
|||||||
}
|
}
|
||||||
const tokenKey = depDef.tokenKey;
|
const tokenKey = depDef.tokenKey;
|
||||||
|
|
||||||
if (depDef.flags & DepFlags.SkipSelf) {
|
if (elDef && (depDef.flags & DepFlags.SkipSelf)) {
|
||||||
allowPrivateServices = false;
|
allowPrivateServices = false;
|
||||||
elDef = elDef.parent;
|
elDef = elDef.parent;
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@ import {ApplicationRef} from '../application_ref';
|
|||||||
import {ChangeDetectorRef} from '../change_detection/change_detection';
|
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 {ComponentFactoryBoundToModule} from '../linker/component_factory_resolver';
|
||||||
import {ElementRef} from '../linker/element_ref';
|
import {ElementRef} from '../linker/element_ref';
|
||||||
import {NgModuleRef} from '../linker/ng_module_factory';
|
import {NgModuleRef} from '../linker/ng_module_factory';
|
||||||
import {TemplateRef} from '../linker/template_ref';
|
import {TemplateRef} from '../linker/template_ref';
|
||||||
@ -137,7 +138,7 @@ class ViewContainerRef_ implements ViewContainerData {
|
|||||||
view = view.parent;
|
view = view.parent;
|
||||||
}
|
}
|
||||||
|
|
||||||
return view ? new Injector_(view, elDef) : this._view.root.injector;
|
return view ? new Injector_(view, elDef) : new Injector_(this._view, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
clear(): void {
|
clear(): void {
|
||||||
@ -169,9 +170,13 @@ class ViewContainerRef_ implements ViewContainerData {
|
|||||||
|
|
||||||
createComponent<C>(
|
createComponent<C>(
|
||||||
componentFactory: ComponentFactory<C>, index?: number, injector?: Injector,
|
componentFactory: ComponentFactory<C>, index?: number, injector?: Injector,
|
||||||
projectableNodes?: any[][]): ComponentRef<C> {
|
projectableNodes?: any[][], ngModuleRef?: NgModuleRef<any>): ComponentRef<C> {
|
||||||
const contextInjector = injector || this.parentInjector;
|
const contextInjector = injector || this.parentInjector;
|
||||||
const componentRef = componentFactory.create(contextInjector, projectableNodes);
|
if (!ngModuleRef && !(componentFactory instanceof ComponentFactoryBoundToModule)) {
|
||||||
|
ngModuleRef = contextInjector.get(NgModuleRef);
|
||||||
|
}
|
||||||
|
const componentRef =
|
||||||
|
componentFactory.create(contextInjector, projectableNodes, undefined, ngModuleRef);
|
||||||
this.insert(componentRef.hostView, index);
|
this.insert(componentRef.hostView, index);
|
||||||
return componentRef;
|
return componentRef;
|
||||||
}
|
}
|
||||||
@ -298,9 +303,10 @@ export function createInjector(view: ViewData, elDef: NodeDef): Injector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class Injector_ implements Injector {
|
class Injector_ implements Injector {
|
||||||
constructor(private view: ViewData, private elDef: NodeDef) {}
|
constructor(private view: ViewData, private elDef: NodeDef|null) {}
|
||||||
get(token: any, notFoundValue: any = Injector.THROW_IF_NOT_FOUND): any {
|
get(token: any, notFoundValue: any = Injector.THROW_IF_NOT_FOUND): any {
|
||||||
const allowPrivateServices = (this.elDef.flags & NodeFlags.ComponentView) !== 0;
|
const allowPrivateServices =
|
||||||
|
this.elDef ? (this.elDef.flags & NodeFlags.ComponentView) !== 0 : false;
|
||||||
return Services.resolveDep(
|
return Services.resolveDep(
|
||||||
this.view, this.elDef, allowPrivateServices,
|
this.view, this.elDef, allowPrivateServices,
|
||||||
{flags: DepFlags.None, token, tokenKey: tokenKey(token)}, notFoundValue);
|
{flags: DepFlags.None, token, tokenKey: tokenKey(token)}, notFoundValue);
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {CommonModule} from '@angular/common';
|
import {CommonModule} from '@angular/common';
|
||||||
import {ComponentFactory, EventEmitter, Host, Inject, Injectable, InjectionToken, Injector, NO_ERRORS_SCHEMA, NgModule, OnDestroy, ReflectiveInjector, SkipSelf} from '@angular/core';
|
import {Compiler, ComponentFactory, EventEmitter, Host, Inject, Injectable, InjectionToken, Injector, NO_ERRORS_SCHEMA, NgModule, NgModuleRef, OnDestroy, ReflectiveInjector, SkipSelf} from '@angular/core';
|
||||||
import {ChangeDetectionStrategy, ChangeDetectorRef, PipeTransform} from '@angular/core/src/change_detection/change_detection';
|
import {ChangeDetectionStrategy, ChangeDetectorRef, PipeTransform} from '@angular/core/src/change_detection/change_detection';
|
||||||
import {getDebugContext} from '@angular/core/src/errors';
|
import {getDebugContext} from '@angular/core/src/errors';
|
||||||
import {ComponentFactoryResolver} from '@angular/core/src/linker/component_factory_resolver';
|
import {ComponentFactoryResolver} from '@angular/core/src/linker/component_factory_resolver';
|
||||||
@ -22,6 +22,7 @@ import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
|
|||||||
import {DOCUMENT} from '@angular/platform-browser/src/dom/dom_tokens';
|
import {DOCUMENT} from '@angular/platform-browser/src/dom/dom_tokens';
|
||||||
import {dispatchEvent, el} from '@angular/platform-browser/testing/src/browser_util';
|
import {dispatchEvent, el} from '@angular/platform-browser/testing/src/browser_util';
|
||||||
import {expect} from '@angular/platform-browser/testing/src/matchers';
|
import {expect} from '@angular/platform-browser/testing/src/matchers';
|
||||||
|
|
||||||
import {stringify} from '../../src/util';
|
import {stringify} from '../../src/util';
|
||||||
|
|
||||||
const ANCHOR_ELEMENT = new InjectionToken('AnchorElement');
|
const ANCHOR_ELEMENT = new InjectionToken('AnchorElement');
|
||||||
@ -1019,7 +1020,7 @@ function declareTests({useJit}: {useJit: boolean}) {
|
|||||||
fixture.destroy();
|
fixture.destroy();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('dynamic ViewContainers', () => {
|
describe('ViewContainerRef.createComponent', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
// we need a module to declarate ChildCompUsingService as an entryComponent otherwise the
|
// we need a module to declarate ChildCompUsingService as an entryComponent otherwise the
|
||||||
// factory doesn't get created
|
// factory doesn't get created
|
||||||
@ -1036,7 +1037,7 @@ function declareTests({useJit}: {useJit: boolean}) {
|
|||||||
MyComp, {add: {template: '<div><dynamic-vp #dynamic></dynamic-vp></div>'}});
|
MyComp, {add: {template: '<div><dynamic-vp #dynamic></dynamic-vp></div>'}});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should allow to create a ViewContainerRef at any bound location', async(() => {
|
it('should allow to create a component at any bound location', async(() => {
|
||||||
const fixture = TestBed.configureTestingModule({schemas: [NO_ERRORS_SCHEMA]})
|
const fixture = TestBed.configureTestingModule({schemas: [NO_ERRORS_SCHEMA]})
|
||||||
.createComponent(MyComp);
|
.createComponent(MyComp);
|
||||||
const tc = fixture.debugElement.children[0].children[0];
|
const tc = fixture.debugElement.children[0].children[0];
|
||||||
@ -1047,7 +1048,7 @@ function declareTests({useJit}: {useJit: boolean}) {
|
|||||||
.toHaveText('dynamic greet');
|
.toHaveText('dynamic greet');
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should allow to create multiple ViewContainerRef at a location', async(() => {
|
it('should allow to create multiple components at a location', async(() => {
|
||||||
const fixture = TestBed.configureTestingModule({schemas: [NO_ERRORS_SCHEMA]})
|
const fixture = TestBed.configureTestingModule({schemas: [NO_ERRORS_SCHEMA]})
|
||||||
.createComponent(MyComp);
|
.createComponent(MyComp);
|
||||||
const tc = fixture.debugElement.children[0].children[0];
|
const tc = fixture.debugElement.children[0].children[0];
|
||||||
@ -1060,6 +1061,122 @@ function declareTests({useJit}: {useJit: boolean}) {
|
|||||||
expect(fixture.debugElement.children[0].children[2].nativeElement)
|
expect(fixture.debugElement.children[0].children[2].nativeElement)
|
||||||
.toHaveText('dynamic greet');
|
.toHaveText('dynamic greet');
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
it('should create a component that has been freshly compiled', () => {
|
||||||
|
@Component({template: ''})
|
||||||
|
class RootComp {
|
||||||
|
constructor(public vc: ViewContainerRef) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [RootComp],
|
||||||
|
providers: [{provide: 'someToken', useValue: 'someRootValue'}],
|
||||||
|
})
|
||||||
|
class RootModule {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({template: ''})
|
||||||
|
class MyComp {
|
||||||
|
constructor(@Inject('someToken') public someToken: string) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [MyComp],
|
||||||
|
providers: [{provide: 'someToken', useValue: 'someValue'}],
|
||||||
|
})
|
||||||
|
class MyModule {
|
||||||
|
}
|
||||||
|
|
||||||
|
const compFixture =
|
||||||
|
TestBed.configureTestingModule({imports: [RootModule]}).createComponent(RootComp);
|
||||||
|
const compiler = <Compiler>TestBed.get(Compiler);
|
||||||
|
const myCompFactory =
|
||||||
|
<ComponentFactory<MyComp>>compiler.compileModuleAndAllComponentsSync(MyModule)
|
||||||
|
.componentFactories[0];
|
||||||
|
|
||||||
|
// Note: the ComponentFactory was created directly via the compiler, i.e. it
|
||||||
|
// does not have an association to an NgModuleRef.
|
||||||
|
// -> expect the providers of the module that the view container belongs to.
|
||||||
|
const compRef = compFixture.componentInstance.vc.createComponent(myCompFactory);
|
||||||
|
expect(compRef.instance.someToken).toBe('someRootValue');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create a component with the passed NgModuleRef', () => {
|
||||||
|
@Component({template: ''})
|
||||||
|
class RootComp {
|
||||||
|
constructor(public vc: ViewContainerRef) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({template: ''})
|
||||||
|
class MyComp {
|
||||||
|
constructor(@Inject('someToken') public someToken: string) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [RootComp, MyComp],
|
||||||
|
entryComponents: [MyComp],
|
||||||
|
providers: [{provide: 'someToken', useValue: 'someRootValue'}],
|
||||||
|
})
|
||||||
|
class RootModule {
|
||||||
|
}
|
||||||
|
|
||||||
|
@NgModule({providers: [{provide: 'someToken', useValue: 'someValue'}]})
|
||||||
|
class MyModule {
|
||||||
|
}
|
||||||
|
|
||||||
|
const compFixture =
|
||||||
|
TestBed.configureTestingModule({imports: [RootModule]}).createComponent(RootComp);
|
||||||
|
const compiler = <Compiler>TestBed.get(Compiler);
|
||||||
|
const myModule = compiler.compileModuleSync(MyModule).create(TestBed.get(NgModuleRef));
|
||||||
|
const myCompFactory = (<ComponentFactoryResolver>TestBed.get(ComponentFactoryResolver))
|
||||||
|
.resolveComponentFactory(MyComp);
|
||||||
|
|
||||||
|
// Note: MyComp was declared as entryComponent in the RootModule,
|
||||||
|
// but we pass MyModule to the createComponent call.
|
||||||
|
// -> expect the providers of MyModule!
|
||||||
|
const compRef = compFixture.componentInstance.vc.createComponent(
|
||||||
|
myCompFactory, undefined, undefined, undefined, myModule);
|
||||||
|
expect(compRef.instance.someToken).toBe('someValue');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create a component with the NgModuleRef of the ComponentFactoryResolver', () => {
|
||||||
|
@Component({template: ''})
|
||||||
|
class RootComp {
|
||||||
|
constructor(public vc: ViewContainerRef) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [RootComp],
|
||||||
|
providers: [{provide: 'someToken', useValue: 'someRootValue'}],
|
||||||
|
})
|
||||||
|
class RootModule {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({template: ''})
|
||||||
|
class MyComp {
|
||||||
|
constructor(@Inject('someToken') public someToken: string) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [MyComp],
|
||||||
|
entryComponents: [MyComp],
|
||||||
|
providers: [{provide: 'someToken', useValue: 'someValue'}],
|
||||||
|
})
|
||||||
|
class MyModule {
|
||||||
|
}
|
||||||
|
|
||||||
|
const compFixture =
|
||||||
|
TestBed.configureTestingModule({imports: [RootModule]}).createComponent(RootComp);
|
||||||
|
const compiler = <Compiler>TestBed.get(Compiler);
|
||||||
|
const myModule = compiler.compileModuleSync(MyModule).create(TestBed.get(NgModuleRef));
|
||||||
|
const myCompFactory = myModule.componentFactoryResolver.resolveComponentFactory(MyComp);
|
||||||
|
|
||||||
|
// Note: MyComp was declared as entryComponent in MyModule,
|
||||||
|
// and we don't pass an explicit ModuleRef to the createComponent call.
|
||||||
|
// -> expect the providers of MyModule!
|
||||||
|
const compRef = compFixture.componentInstance.vc.createComponent(myCompFactory);
|
||||||
|
expect(compRef.instance.someToken).toBe('someValue');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should support static attributes', () => {
|
it('should support static attributes', () => {
|
||||||
|
@ -564,6 +564,24 @@ export function main() {
|
|||||||
.toThrowError(
|
.toThrowError(
|
||||||
/Template parse errors:\nNo provider for SimpleDirective \("\[ERROR ->\]<div needsDirectiveFromHost><\/div>"\): .*SimpleComponent.html@0:0/);
|
/Template parse errors:\nNo provider for SimpleDirective \("\[ERROR ->\]<div needsDirectiveFromHost><\/div>"\): .*SimpleComponent.html@0:0/);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should allow to use the NgModule injector from a root ViewContainerRef.parentInjector',
|
||||||
|
() => {
|
||||||
|
@Component({template: ''})
|
||||||
|
class MyComp {
|
||||||
|
constructor(public vc: ViewContainerRef) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
const compFixture = TestBed
|
||||||
|
.configureTestingModule({
|
||||||
|
declarations: [MyComp],
|
||||||
|
providers: [{provide: 'someToken', useValue: 'someValue'}]
|
||||||
|
})
|
||||||
|
.createComponent(MyComp);
|
||||||
|
|
||||||
|
expect(compFixture.componentInstance.vc.parentInjector.get('someToken'))
|
||||||
|
.toBe('someValue');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('static attributes', () => {
|
describe('static attributes', () => {
|
||||||
@ -655,13 +673,16 @@ export function main() {
|
|||||||
class TestModule {
|
class TestModule {
|
||||||
}
|
}
|
||||||
|
|
||||||
const testInjector = {};
|
const testInjector = <Injector>{
|
||||||
|
get: (token: any, notFoundValue: any) =>
|
||||||
|
token === 'someToken' ? 'someNewValue' : notFoundValue
|
||||||
|
};
|
||||||
|
|
||||||
const compFactory = TestBed.configureTestingModule({imports: [TestModule]})
|
const compFactory = TestBed.configureTestingModule({imports: [TestModule]})
|
||||||
.get(ComponentFactoryResolver)
|
.get(ComponentFactoryResolver)
|
||||||
.resolveComponentFactory(TestComp);
|
.resolveComponentFactory(TestComp);
|
||||||
const component = compFactory.create(<Injector>testInjector);
|
const component = compFactory.create(testInjector);
|
||||||
expect(component.instance.vcr.parentInjector).toBe(testInjector);
|
expect(component.instance.vcr.parentInjector.get('someToken')).toBe('someNewValue');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should inject TemplateRef', () => {
|
it('should inject TemplateRef', () => {
|
||||||
|
2
tools/public_api_guard/core/core.d.ts
vendored
2
tools/public_api_guard/core/core.d.ts
vendored
@ -1086,7 +1086,7 @@ export declare abstract class ViewContainerRef {
|
|||||||
readonly abstract length: number;
|
readonly abstract length: number;
|
||||||
readonly abstract parentInjector: Injector;
|
readonly abstract parentInjector: Injector;
|
||||||
abstract clear(): void;
|
abstract clear(): void;
|
||||||
abstract createComponent<C>(componentFactory: ComponentFactory<C>, index?: number, injector?: Injector, projectableNodes?: any[][]): ComponentRef<C>;
|
abstract createComponent<C>(componentFactory: ComponentFactory<C>, index?: number, injector?: Injector, projectableNodes?: any[][], ngModule?: NgModuleRef<any>): ComponentRef<C>;
|
||||||
abstract createEmbeddedView<C>(templateRef: TemplateRef<C>, context?: C, index?: number): EmbeddedViewRef<C>;
|
abstract createEmbeddedView<C>(templateRef: TemplateRef<C>, context?: C, index?: number): EmbeddedViewRef<C>;
|
||||||
abstract detach(index?: number): ViewRef;
|
abstract detach(index?: number): ViewRef;
|
||||||
abstract get(index: number): ViewRef;
|
abstract get(index: number): ViewRef;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user