Skip to content

Commit 431eb30

Browse files
tboschmhevery
authored andcommitted
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
1 parent 8e6995c commit 431eb30

File tree

6 files changed

+160
-15
lines changed

6 files changed

+160
-15
lines changed

packages/core/src/linker/view_container_ref.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import {Injector} from '../di/injector';
1010
import {ComponentFactory, ComponentRef} from './component_factory';
1111
import {ElementRef} from './element_ref';
12+
import {NgModuleRef} from './ng_module_factory';
1213
import {TemplateRef} from './template_ref';
1314
import {EmbeddedViewRef, ViewRef} from './view_ref';
1415

@@ -83,7 +84,7 @@ export abstract class ViewContainerRef {
8384
*/
8485
abstract createComponent<C>(
8586
componentFactory: ComponentFactory<C>, index?: number, injector?: Injector,
86-
projectableNodes?: any[][]): ComponentRef<C>;
87+
projectableNodes?: any[][], ngModule?: NgModuleRef<any>): ComponentRef<C>;
8788

8889
/**
8990
* Inserts a View identified by a {@link ViewRef} into the container at the specified `index`.

packages/core/src/view/provider.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -356,7 +356,7 @@ export function resolveDep(
356356
}
357357
const tokenKey = depDef.tokenKey;
358358

359-
if (depDef.flags & DepFlags.SkipSelf) {
359+
if (elDef && (depDef.flags & DepFlags.SkipSelf)) {
360360
allowPrivateServices = false;
361361
elDef = elDef.parent;
362362
}

packages/core/src/view/refs.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {ApplicationRef} from '../application_ref';
1010
import {ChangeDetectorRef} from '../change_detection/change_detection';
1111
import {Injector} from '../di';
1212
import {ComponentFactory, ComponentRef} from '../linker/component_factory';
13+
import {ComponentFactoryBoundToModule} from '../linker/component_factory_resolver';
1314
import {ElementRef} from '../linker/element_ref';
1415
import {NgModuleRef} from '../linker/ng_module_factory';
1516
import {TemplateRef} from '../linker/template_ref';
@@ -137,7 +138,7 @@ class ViewContainerRef_ implements ViewContainerData {
137138
view = view.parent;
138139
}
139140

140-
return view ? new Injector_(view, elDef) : this._view.root.injector;
141+
return view ? new Injector_(view, elDef) : new Injector_(this._view, null);
141142
}
142143

143144
clear(): void {
@@ -169,9 +170,13 @@ class ViewContainerRef_ implements ViewContainerData {
169170

170171
createComponent<C>(
171172
componentFactory: ComponentFactory<C>, index?: number, injector?: Injector,
172-
projectableNodes?: any[][]): ComponentRef<C> {
173+
projectableNodes?: any[][], ngModuleRef?: NgModuleRef<any>): ComponentRef<C> {
173174
const contextInjector = injector || this.parentInjector;
174-
const componentRef = componentFactory.create(contextInjector, projectableNodes);
175+
if (!ngModuleRef && !(componentFactory instanceof ComponentFactoryBoundToModule)) {
176+
ngModuleRef = contextInjector.get(NgModuleRef);
177+
}
178+
const componentRef =
179+
componentFactory.create(contextInjector, projectableNodes, undefined, ngModuleRef);
175180
this.insert(componentRef.hostView, index);
176181
return componentRef;
177182
}
@@ -298,9 +303,10 @@ export function createInjector(view: ViewData, elDef: NodeDef): Injector {
298303
}
299304

300305
class Injector_ implements Injector {
301-
constructor(private view: ViewData, private elDef: NodeDef) {}
306+
constructor(private view: ViewData, private elDef: NodeDef|null) {}
302307
get(token: any, notFoundValue: any = Injector.THROW_IF_NOT_FOUND): any {
303-
const allowPrivateServices = (this.elDef.flags & NodeFlags.ComponentView) !== 0;
308+
const allowPrivateServices =
309+
this.elDef ? (this.elDef.flags & NodeFlags.ComponentView) !== 0 : false;
304310
return Services.resolveDep(
305311
this.view, this.elDef, allowPrivateServices,
306312
{flags: DepFlags.None, token, tokenKey: tokenKey(token)}, notFoundValue);

packages/core/test/linker/integration_spec.ts

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

99
import {CommonModule} from '@angular/common';
10-
import {ComponentFactory, EventEmitter, Host, Inject, Injectable, InjectionToken, Injector, NO_ERRORS_SCHEMA, NgModule, OnDestroy, ReflectiveInjector, SkipSelf} from '@angular/core';
10+
import {Compiler, ComponentFactory, EventEmitter, Host, Inject, Injectable, InjectionToken, Injector, NO_ERRORS_SCHEMA, NgModule, NgModuleRef, OnDestroy, ReflectiveInjector, SkipSelf} from '@angular/core';
1111
import {ChangeDetectionStrategy, ChangeDetectorRef, PipeTransform} from '@angular/core/src/change_detection/change_detection';
1212
import {getDebugContext} from '@angular/core/src/errors';
1313
import {ComponentFactoryResolver} from '@angular/core/src/linker/component_factory_resolver';
@@ -22,6 +22,7 @@ import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
2222
import {DOCUMENT} from '@angular/platform-browser/src/dom/dom_tokens';
2323
import {dispatchEvent, el} from '@angular/platform-browser/testing/src/browser_util';
2424
import {expect} from '@angular/platform-browser/testing/src/matchers';
25+
2526
import {stringify} from '../../src/util';
2627

2728
const ANCHOR_ELEMENT = new InjectionToken('AnchorElement');
@@ -1019,7 +1020,7 @@ function declareTests({useJit}: {useJit: boolean}) {
10191020
fixture.destroy();
10201021
});
10211022

1022-
describe('dynamic ViewContainers', () => {
1023+
describe('ViewContainerRef.createComponent', () => {
10231024
beforeEach(() => {
10241025
// we need a module to declarate ChildCompUsingService as an entryComponent otherwise the
10251026
// factory doesn't get created
@@ -1036,7 +1037,7 @@ function declareTests({useJit}: {useJit: boolean}) {
10361037
MyComp, {add: {template: '<div><dynamic-vp #dynamic></dynamic-vp></div>'}});
10371038
});
10381039

1039-
it('should allow to create a ViewContainerRef at any bound location', async(() => {
1040+
it('should allow to create a component at any bound location', async(() => {
10401041
const fixture = TestBed.configureTestingModule({schemas: [NO_ERRORS_SCHEMA]})
10411042
.createComponent(MyComp);
10421043
const tc = fixture.debugElement.children[0].children[0];
@@ -1047,7 +1048,7 @@ function declareTests({useJit}: {useJit: boolean}) {
10471048
.toHaveText('dynamic greet');
10481049
}));
10491050

1050-
it('should allow to create multiple ViewContainerRef at a location', async(() => {
1051+
it('should allow to create multiple components at a location', async(() => {
10511052
const fixture = TestBed.configureTestingModule({schemas: [NO_ERRORS_SCHEMA]})
10521053
.createComponent(MyComp);
10531054
const tc = fixture.debugElement.children[0].children[0];
@@ -1060,6 +1061,122 @@ function declareTests({useJit}: {useJit: boolean}) {
10601061
expect(fixture.debugElement.children[0].children[2].nativeElement)
10611062
.toHaveText('dynamic greet');
10621063
}));
1064+
1065+
it('should create a component that has been freshly compiled', () => {
1066+
@Component({template: ''})
1067+
class RootComp {
1068+
constructor(public vc: ViewContainerRef) {}
1069+
}
1070+
1071+
@NgModule({
1072+
declarations: [RootComp],
1073+
providers: [{provide: 'someToken', useValue: 'someRootValue'}],
1074+
})
1075+
class RootModule {
1076+
}
1077+
1078+
@Component({template: ''})
1079+
class MyComp {
1080+
constructor(@Inject('someToken') public someToken: string) {}
1081+
}
1082+
1083+
@NgModule({
1084+
declarations: [MyComp],
1085+
providers: [{provide: 'someToken', useValue: 'someValue'}],
1086+
})
1087+
class MyModule {
1088+
}
1089+
1090+
const compFixture =
1091+
TestBed.configureTestingModule({imports: [RootModule]}).createComponent(RootComp);
1092+
const compiler = <Compiler>TestBed.get(Compiler);
1093+
const myCompFactory =
1094+
<ComponentFactory<MyComp>>compiler.compileModuleAndAllComponentsSync(MyModule)
1095+
.componentFactories[0];
1096+
1097+
// Note: the ComponentFactory was created directly via the compiler, i.e. it
1098+
// does not have an association to an NgModuleRef.
1099+
// -> expect the providers of the module that the view container belongs to.
1100+
const compRef = compFixture.componentInstance.vc.createComponent(myCompFactory);
1101+
expect(compRef.instance.someToken).toBe('someRootValue');
1102+
});
1103+
1104+
it('should create a component with the passed NgModuleRef', () => {
1105+
@Component({template: ''})
1106+
class RootComp {
1107+
constructor(public vc: ViewContainerRef) {}
1108+
}
1109+
1110+
@Component({template: ''})
1111+
class MyComp {
1112+
constructor(@Inject('someToken') public someToken: string) {}
1113+
}
1114+
1115+
@NgModule({
1116+
declarations: [RootComp, MyComp],
1117+
entryComponents: [MyComp],
1118+
providers: [{provide: 'someToken', useValue: 'someRootValue'}],
1119+
})
1120+
class RootModule {
1121+
}
1122+
1123+
@NgModule({providers: [{provide: 'someToken', useValue: 'someValue'}]})
1124+
class MyModule {
1125+
}
1126+
1127+
const compFixture =
1128+
TestBed.configureTestingModule({imports: [RootModule]}).createComponent(RootComp);
1129+
const compiler = <Compiler>TestBed.get(Compiler);
1130+
const myModule = compiler.compileModuleSync(MyModule).create(TestBed.get(NgModuleRef));
1131+
const myCompFactory = (<ComponentFactoryResolver>TestBed.get(ComponentFactoryResolver))
1132+
.resolveComponentFactory(MyComp);
1133+
1134+
// Note: MyComp was declared as entryComponent in the RootModule,
1135+
// but we pass MyModule to the createComponent call.
1136+
// -> expect the providers of MyModule!
1137+
const compRef = compFixture.componentInstance.vc.createComponent(
1138+
myCompFactory, undefined, undefined, undefined, myModule);
1139+
expect(compRef.instance.someToken).toBe('someValue');
1140+
});
1141+
1142+
it('should create a component with the NgModuleRef of the ComponentFactoryResolver', () => {
1143+
@Component({template: ''})
1144+
class RootComp {
1145+
constructor(public vc: ViewContainerRef) {}
1146+
}
1147+
1148+
@NgModule({
1149+
declarations: [RootComp],
1150+
providers: [{provide: 'someToken', useValue: 'someRootValue'}],
1151+
})
1152+
class RootModule {
1153+
}
1154+
1155+
@Component({template: ''})
1156+
class MyComp {
1157+
constructor(@Inject('someToken') public someToken: string) {}
1158+
}
1159+
1160+
@NgModule({
1161+
declarations: [MyComp],
1162+
entryComponents: [MyComp],
1163+
providers: [{provide: 'someToken', useValue: 'someValue'}],
1164+
})
1165+
class MyModule {
1166+
}
1167+
1168+
const compFixture =
1169+
TestBed.configureTestingModule({imports: [RootModule]}).createComponent(RootComp);
1170+
const compiler = <Compiler>TestBed.get(Compiler);
1171+
const myModule = compiler.compileModuleSync(MyModule).create(TestBed.get(NgModuleRef));
1172+
const myCompFactory = myModule.componentFactoryResolver.resolveComponentFactory(MyComp);
1173+
1174+
// Note: MyComp was declared as entryComponent in MyModule,
1175+
// and we don't pass an explicit ModuleRef to the createComponent call.
1176+
// -> expect the providers of MyModule!
1177+
const compRef = compFixture.componentInstance.vc.createComponent(myCompFactory);
1178+
expect(compRef.instance.someToken).toBe('someValue');
1179+
});
10631180
});
10641181

10651182
it('should support static attributes', () => {

packages/core/test/linker/view_injector_integration_spec.ts

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -564,6 +564,24 @@ export function main() {
564564
.toThrowError(
565565
/Template parse errors:\nNo provider for SimpleDirective \("\[ERROR ->\]<div needsDirectiveFromHost><\/div>"\): .*SimpleComponent.html@0:0/);
566566
});
567+
568+
it('should allow to use the NgModule injector from a root ViewContainerRef.parentInjector',
569+
() => {
570+
@Component({template: ''})
571+
class MyComp {
572+
constructor(public vc: ViewContainerRef) {}
573+
}
574+
575+
const compFixture = TestBed
576+
.configureTestingModule({
577+
declarations: [MyComp],
578+
providers: [{provide: 'someToken', useValue: 'someValue'}]
579+
})
580+
.createComponent(MyComp);
581+
582+
expect(compFixture.componentInstance.vc.parentInjector.get('someToken'))
583+
.toBe('someValue');
584+
});
567585
});
568586

569587
describe('static attributes', () => {
@@ -655,13 +673,16 @@ export function main() {
655673
class TestModule {
656674
}
657675

658-
const testInjector = {};
676+
const testInjector = <Injector>{
677+
get: (token: any, notFoundValue: any) =>
678+
token === 'someToken' ? 'someNewValue' : notFoundValue
679+
};
659680

660681
const compFactory = TestBed.configureTestingModule({imports: [TestModule]})
661682
.get(ComponentFactoryResolver)
662683
.resolveComponentFactory(TestComp);
663-
const component = compFactory.create(<Injector>testInjector);
664-
expect(component.instance.vcr.parentInjector).toBe(testInjector);
684+
const component = compFactory.create(testInjector);
685+
expect(component.instance.vcr.parentInjector.get('someToken')).toBe('someNewValue');
665686
});
666687

667688
it('should inject TemplateRef', () => {

tools/public_api_guard/core/core.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1086,7 +1086,7 @@ export declare abstract class ViewContainerRef {
10861086
readonly abstract length: number;
10871087
readonly abstract parentInjector: Injector;
10881088
abstract clear(): void;
1089-
abstract createComponent<C>(componentFactory: ComponentFactory<C>, index?: number, injector?: Injector, projectableNodes?: any[][]): ComponentRef<C>;
1089+
abstract createComponent<C>(componentFactory: ComponentFactory<C>, index?: number, injector?: Injector, projectableNodes?: any[][], ngModule?: NgModuleRef<any>): ComponentRef<C>;
10901090
abstract createEmbeddedView<C>(templateRef: TemplateRef<C>, context?: C, index?: number): EmbeddedViewRef<C>;
10911091
abstract detach(index?: number): ViewRef;
10921092
abstract get(index: number): ViewRef;

0 commit comments

Comments
 (0)