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 {ComponentFactory, ComponentRef} from './component_factory';
|
||||
import {ElementRef} from './element_ref';
|
||||
import {NgModuleRef} from './ng_module_factory';
|
||||
import {TemplateRef} from './template_ref';
|
||||
import {EmbeddedViewRef, ViewRef} from './view_ref';
|
||||
|
||||
|
@ -83,7 +84,7 @@ export abstract class ViewContainerRef {
|
|||
*/
|
||||
abstract createComponent<C>(
|
||||
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`.
|
||||
|
|
|
@ -356,7 +356,7 @@ export function resolveDep(
|
|||
}
|
||||
const tokenKey = depDef.tokenKey;
|
||||
|
||||
if (depDef.flags & DepFlags.SkipSelf) {
|
||||
if (elDef && (depDef.flags & DepFlags.SkipSelf)) {
|
||||
allowPrivateServices = false;
|
||||
elDef = elDef.parent;
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import {ApplicationRef} from '../application_ref';
|
|||
import {ChangeDetectorRef} from '../change_detection/change_detection';
|
||||
import {Injector} from '../di';
|
||||
import {ComponentFactory, ComponentRef} from '../linker/component_factory';
|
||||
import {ComponentFactoryBoundToModule} from '../linker/component_factory_resolver';
|
||||
import {ElementRef} from '../linker/element_ref';
|
||||
import {NgModuleRef} from '../linker/ng_module_factory';
|
||||
import {TemplateRef} from '../linker/template_ref';
|
||||
|
@ -137,7 +138,7 @@ class ViewContainerRef_ implements ViewContainerData {
|
|||
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 {
|
||||
|
@ -169,9 +170,13 @@ class ViewContainerRef_ implements ViewContainerData {
|
|||
|
||||
createComponent<C>(
|
||||
componentFactory: ComponentFactory<C>, index?: number, injector?: Injector,
|
||||
projectableNodes?: any[][]): ComponentRef<C> {
|
||||
projectableNodes?: any[][], ngModuleRef?: NgModuleRef<any>): ComponentRef<C> {
|
||||
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);
|
||||
return componentRef;
|
||||
}
|
||||
|
@ -298,9 +303,10 @@ export function createInjector(view: ViewData, elDef: NodeDef): 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 {
|
||||
const allowPrivateServices = (this.elDef.flags & NodeFlags.ComponentView) !== 0;
|
||||
const allowPrivateServices =
|
||||
this.elDef ? (this.elDef.flags & NodeFlags.ComponentView) !== 0 : false;
|
||||
return Services.resolveDep(
|
||||
this.view, this.elDef, allowPrivateServices,
|
||||
{flags: DepFlags.None, token, tokenKey: tokenKey(token)}, notFoundValue);
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
*/
|
||||
|
||||
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 {getDebugContext} from '@angular/core/src/errors';
|
||||
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 {dispatchEvent, el} from '@angular/platform-browser/testing/src/browser_util';
|
||||
import {expect} from '@angular/platform-browser/testing/src/matchers';
|
||||
|
||||
import {stringify} from '../../src/util';
|
||||
|
||||
const ANCHOR_ELEMENT = new InjectionToken('AnchorElement');
|
||||
|
@ -1019,7 +1020,7 @@ function declareTests({useJit}: {useJit: boolean}) {
|
|||
fixture.destroy();
|
||||
});
|
||||
|
||||
describe('dynamic ViewContainers', () => {
|
||||
describe('ViewContainerRef.createComponent', () => {
|
||||
beforeEach(() => {
|
||||
// we need a module to declarate ChildCompUsingService as an entryComponent otherwise the
|
||||
// factory doesn't get created
|
||||
|
@ -1036,7 +1037,7 @@ function declareTests({useJit}: {useJit: boolean}) {
|
|||
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]})
|
||||
.createComponent(MyComp);
|
||||
const tc = fixture.debugElement.children[0].children[0];
|
||||
|
@ -1047,7 +1048,7 @@ function declareTests({useJit}: {useJit: boolean}) {
|
|||
.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]})
|
||||
.createComponent(MyComp);
|
||||
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)
|
||||
.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', () => {
|
||||
|
|
|
@ -564,6 +564,24 @@ export function main() {
|
|||
.toThrowError(
|
||||
/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', () => {
|
||||
|
@ -655,13 +673,16 @@ export function main() {
|
|||
class TestModule {
|
||||
}
|
||||
|
||||
const testInjector = {};
|
||||
const testInjector = <Injector>{
|
||||
get: (token: any, notFoundValue: any) =>
|
||||
token === 'someToken' ? 'someNewValue' : notFoundValue
|
||||
};
|
||||
|
||||
const compFactory = TestBed.configureTestingModule({imports: [TestModule]})
|
||||
.get(ComponentFactoryResolver)
|
||||
.resolveComponentFactory(TestComp);
|
||||
const component = compFactory.create(<Injector>testInjector);
|
||||
expect(component.instance.vcr.parentInjector).toBe(testInjector);
|
||||
const component = compFactory.create(testInjector);
|
||||
expect(component.instance.vcr.parentInjector.get('someToken')).toBe('someNewValue');
|
||||
});
|
||||
|
||||
it('should inject TemplateRef', () => {
|
||||
|
|
|
@ -1086,7 +1086,7 @@ export declare abstract class ViewContainerRef {
|
|||
readonly abstract length: number;
|
||||
readonly abstract parentInjector: Injector;
|
||||
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 detach(index?: number): ViewRef;
|
||||
abstract get(index: number): ViewRef;
|
||||
|
|
Loading…
Reference in New Issue