Skip to content

Commit 8e9858f

Browse files
marclavalIgorMinar
authored andcommitted
fix(ivy): align NgModuleRef implementation between Ivy and ViewEngine (angular#27482)
Solves FW-765 and FW-767 PR Close angular#27482
1 parent 159ab1c commit 8e9858f

File tree

6 files changed

+384
-285
lines changed

6 files changed

+384
-285
lines changed

packages/core/src/render3/component_ref.ts

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,15 @@ import {createElementRef} from './view_engine_compatibility';
3434
import {RootViewRef, ViewRef} from './view_ref';
3535

3636
export class ComponentFactoryResolver extends viewEngine_ComponentFactoryResolver {
37+
/**
38+
* @param ngModule The NgModuleRef to which all resolved factories are bound.
39+
*/
40+
constructor(private ngModule?: viewEngine_NgModuleRef<any>) { super(); }
41+
3742
resolveComponentFactory<T>(component: Type<T>): viewEngine_ComponentFactory<T> {
3843
ngDevMode && assertComponentType(component);
3944
const componentDef = getComponentDef(component) !;
40-
return new ComponentFactory(componentDef);
45+
return new ComponentFactory(componentDef, this.ngModule);
4146
}
4247
}
4348

@@ -75,10 +80,13 @@ function createChainedInjector(rootViewInjector: Injector, moduleInjector: Injec
7580
get: <T>(token: Type<T>| InjectionToken<T>, notFoundValue?: T): T => {
7681
const value = rootViewInjector.get(token, NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR);
7782

78-
if (value !== NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR) {
83+
if (value !== NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR ||
84+
notFoundValue === NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR) {
7985
// Return the value from the root element injector when
8086
// - it provides it
8187
// (value !== NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR)
88+
// - the module injector should not be checked
89+
// (notFoundValue === NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR)
8290
return value;
8391
}
8492

@@ -103,7 +111,12 @@ export class ComponentFactory<T> extends viewEngine_ComponentFactory<T> {
103111
return toRefArray(this.componentDef.outputs);
104112
}
105113

106-
constructor(private componentDef: ComponentDef<any>) {
114+
/**
115+
* @param componentDef The component definition.
116+
* @param ngModule The NgModuleRef to which the factory is bound.
117+
*/
118+
constructor(
119+
private componentDef: ComponentDef<any>, private ngModule?: viewEngine_NgModuleRef<any>) {
107120
super();
108121
this.componentType = componentDef.type;
109122
this.selector = componentDef.selectors[0][0] as string;
@@ -114,6 +127,7 @@ export class ComponentFactory<T> extends viewEngine_ComponentFactory<T> {
114127
injector: Injector, projectableNodes?: any[][]|undefined, rootSelectorOrNode?: any,
115128
ngModule?: viewEngine_NgModuleRef<any>|undefined): viewEngine_ComponentRef<T> {
116129
const isInternalRootView = rootSelectorOrNode === undefined;
130+
ngModule = ngModule || this.ngModule;
117131

118132
const rootViewInjector =
119133
ngModule ? createChainedInjector(injector, ngModule.injector) : injector;

packages/core/src/render3/ng_module_ref.ts

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,35 +6,38 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
import {Injector} from '../di/injector';
9+
import {INJECTOR, Injector} from '../di/injector';
10+
import {InjectFlags} from '../di/injector_compatibility';
1011
import {StaticProvider} from '../di/provider';
1112
import {createInjector} from '../di/r3_injector';
1213
import {ComponentFactoryResolver as viewEngine_ComponentFactoryResolver} from '../linker/component_factory_resolver';
1314
import {InternalNgModuleRef, NgModuleFactory as viewEngine_NgModuleFactory, NgModuleRef as viewEngine_NgModuleRef} from '../linker/ng_module_factory';
1415
import {NgModuleDef} from '../metadata/ng_module';
1516
import {Type} from '../type';
1617
import {stringify} from '../util';
18+
1719
import {assertDefined} from './assert';
1820
import {ComponentFactoryResolver} from './component_ref';
1921
import {getNgModuleDef} from './definition';
2022

2123
export interface NgModuleType { ngModuleDef: NgModuleDef<any>; }
2224

23-
export const COMPONENT_FACTORY_RESOLVER: StaticProvider = {
25+
const COMPONENT_FACTORY_RESOLVER: StaticProvider = {
2426
provide: viewEngine_ComponentFactoryResolver,
25-
useFactory: () => new ComponentFactoryResolver(),
26-
deps: [],
27+
useClass: ComponentFactoryResolver,
28+
deps: [viewEngine_NgModuleRef],
2729
};
2830

2931
export class NgModuleRef<T> extends viewEngine_NgModuleRef<T> implements InternalNgModuleRef<T> {
3032
// tslint:disable-next-line:require-internal-with-underscore
3133
_bootstrapComponents: Type<any>[] = [];
32-
injector: Injector;
33-
componentFactoryResolver: viewEngine_ComponentFactoryResolver;
34+
// tslint:disable-next-line:require-internal-with-underscore
35+
_r3Injector: Injector;
36+
injector: Injector = this;
3437
instance: T;
3538
destroyCbs: (() => void)[]|null = [];
3639

37-
constructor(ngModuleType: Type<T>, parentInjector: Injector|null) {
40+
constructor(ngModuleType: Type<T>, public _parent: Injector|null) {
3841
super();
3942
const ngModuleDef = getNgModuleDef(ngModuleType);
4043
ngDevMode && assertDefined(
@@ -43,14 +46,26 @@ export class NgModuleRef<T> extends viewEngine_NgModuleRef<T> implements Interna
4346

4447
this._bootstrapComponents = ngModuleDef !.bootstrap;
4548
const additionalProviders: StaticProvider[] = [
46-
COMPONENT_FACTORY_RESOLVER, {
49+
{
4750
provide: viewEngine_NgModuleRef,
4851
useValue: this,
49-
}
52+
},
53+
COMPONENT_FACTORY_RESOLVER
5054
];
51-
this.injector = createInjector(ngModuleType, parentInjector, additionalProviders);
52-
this.instance = this.injector.get(ngModuleType);
53-
this.componentFactoryResolver = new ComponentFactoryResolver();
55+
this._r3Injector = createInjector(ngModuleType, _parent, additionalProviders);
56+
this.instance = this.get(ngModuleType);
57+
}
58+
59+
get(token: any, notFoundValue: any = Injector.THROW_IF_NOT_FOUND,
60+
injectFlags: InjectFlags = InjectFlags.Default): any {
61+
if (token === Injector || token === viewEngine_NgModuleRef || token === INJECTOR) {
62+
return this;
63+
}
64+
return this._r3Injector.get(token, notFoundValue, injectFlags);
65+
}
66+
67+
get componentFactoryResolver(): viewEngine_ComponentFactoryResolver {
68+
return this.get(viewEngine_ComponentFactoryResolver);
5469
}
5570

5671
destroy(): void {

packages/core/src/render3/view_engine_compatibility.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,7 @@ export function createContainerRef(
235235
injector?: Injector|undefined, projectableNodes?: any[][]|undefined,
236236
ngModuleRef?: viewEngine_NgModuleRef<any>|undefined): viewEngine_ComponentRef<C> {
237237
const contextInjector = injector || this.parentInjector;
238-
if (!ngModuleRef && contextInjector) {
238+
if (!ngModuleRef && (componentFactory as any).ngModule == null && contextInjector) {
239239
ngModuleRef = contextInjector.get(viewEngine_NgModuleRef, null);
240240
}
241241

packages/core/test/render3/component_ref_spec.ts

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,5 +187,87 @@ describe('ComponentFactory', () => {
187187
expect(mSanitizerFactorySpy).toHaveBeenCalled();
188188
});
189189
});
190+
191+
describe('(when the factory is bound to a `ngModuleRef`)', () => {
192+
it('should retrieve `RendererFactory2` from the specified injector first', () => {
193+
const injector = Injector.create([
194+
{provide: RendererFactory2, useValue: {createRenderer: createRenderer2Spy}},
195+
]);
196+
(cf as any).ngModule = {
197+
injector: Injector.create([
198+
{provide: RendererFactory2, useValue: {createRenderer: createRenderer3Spy}},
199+
])
200+
};
201+
202+
cf.create(injector);
203+
204+
expect(createRenderer2Spy).toHaveBeenCalled();
205+
expect(createRenderer3Spy).not.toHaveBeenCalled();
206+
});
207+
208+
it('should retrieve `RendererFactory2` from the `ngModuleRef` if not provided by the injector',
209+
() => {
210+
const injector = Injector.create([]);
211+
(cf as any).ngModule = {
212+
injector: Injector.create([
213+
{provide: RendererFactory2, useValue: {createRenderer: createRenderer2Spy}},
214+
])
215+
};
216+
217+
cf.create(injector);
218+
219+
expect(createRenderer2Spy).toHaveBeenCalled();
220+
expect(createRenderer3Spy).not.toHaveBeenCalled();
221+
});
222+
223+
it('should fall back to `domRendererFactory3` if `RendererFactory2` is not provided', () => {
224+
const injector = Injector.create([]);
225+
(cf as any).ngModule = {injector: Injector.create([])};
226+
227+
cf.create(injector);
228+
229+
expect(createRenderer2Spy).not.toHaveBeenCalled();
230+
expect(createRenderer3Spy).toHaveBeenCalled();
231+
});
232+
233+
it('should retrieve `Sanitizer` from the specified injector first', () => {
234+
const iSanitizerFactorySpy =
235+
jasmine.createSpy('Injector#sanitizerFactory').and.returnValue({});
236+
const injector = Injector.create([
237+
{provide: Sanitizer, useFactory: iSanitizerFactorySpy, deps: []},
238+
]);
239+
240+
const mSanitizerFactorySpy =
241+
jasmine.createSpy('NgModuleRef#sanitizerFactory').and.returnValue({});
242+
(cf as any).ngModule = {
243+
injector: Injector.create([
244+
{provide: Sanitizer, useFactory: mSanitizerFactorySpy, deps: []},
245+
])
246+
};
247+
248+
cf.create(injector);
249+
250+
expect(iSanitizerFactorySpy).toHaveBeenCalled();
251+
expect(mSanitizerFactorySpy).not.toHaveBeenCalled();
252+
});
253+
254+
it('should retrieve `Sanitizer` from the `ngModuleRef` if not provided by the injector',
255+
() => {
256+
const injector = Injector.create([]);
257+
258+
const mSanitizerFactorySpy =
259+
jasmine.createSpy('NgModuleRef#sanitizerFactory').and.returnValue({});
260+
(cf as any).ngModule = {
261+
injector: Injector.create([
262+
{provide: Sanitizer, useFactory: mSanitizerFactorySpy, deps: []},
263+
])
264+
};
265+
266+
267+
cf.create(injector);
268+
269+
expect(mSanitizerFactorySpy).toHaveBeenCalled();
270+
});
271+
});
190272
});
191273
});

0 commit comments

Comments
 (0)