fix(ivy): use the root view injector when resolving dependencies (#27090)
PR Close #27090
This commit is contained in:
parent
8a626288a6
commit
1c9e526a83
|
@ -72,6 +72,25 @@ export const SCHEDULER = new InjectionToken<((fn: () => void) => void)>('SCHEDUL
|
||||||
export const WRAP_RENDERER_FACTORY2 =
|
export const WRAP_RENDERER_FACTORY2 =
|
||||||
new InjectionToken<(rf: RendererFactory2) => RendererFactory2>('WRAP_RENDERER_FACTORY2');
|
new InjectionToken<(rf: RendererFactory2) => RendererFactory2>('WRAP_RENDERER_FACTORY2');
|
||||||
|
|
||||||
|
const NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR = {};
|
||||||
|
|
||||||
|
function createChainedInjector(rootViewInjector: Injector, moduleInjector: Injector): Injector {
|
||||||
|
return {
|
||||||
|
get: <T>(token: Type<T>| InjectionToken<T>, notFoundValue?: T): T => {
|
||||||
|
const value = rootViewInjector.get(token, NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR);
|
||||||
|
|
||||||
|
if (value !== NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR) {
|
||||||
|
// Return the value from the root element injector when
|
||||||
|
// - it provides it
|
||||||
|
// (value !== NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR)
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return moduleInjector.get(token, notFoundValue);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render3 implementation of {@link viewEngine_ComponentFactory}.
|
* Render3 implementation of {@link viewEngine_ComponentFactory}.
|
||||||
*/
|
*/
|
||||||
|
@ -122,7 +141,7 @@ export class ComponentFactory<T> extends viewEngine_ComponentFactory<T> {
|
||||||
// Create the root view. Uses empty TView and ContentTemplate.
|
// Create the root view. Uses empty TView and ContentTemplate.
|
||||||
const rootView: LViewData = createLViewData(
|
const rootView: LViewData = createLViewData(
|
||||||
renderer, createTView(-1, null, 1, 0, null, null, null), rootContext, rootFlags);
|
renderer, createTView(-1, null, 1, 0, null, null, null), rootContext, rootFlags);
|
||||||
rootView[INJECTOR] = ngModule && ngModule.injector || null;
|
rootView[INJECTOR] = ngModule ? createChainedInjector(injector, ngModule.injector) : injector;
|
||||||
|
|
||||||
// rootView is the parent when bootstrapping
|
// rootView is the parent when bootstrapping
|
||||||
const oldView = enterView(rootView, null);
|
const oldView = enterView(rootView, null);
|
||||||
|
|
|
@ -236,9 +236,12 @@ export function getParentInjectorTNode(
|
||||||
}
|
}
|
||||||
|
|
||||||
let viewOffset = getParentInjectorViewOffset(location);
|
let viewOffset = getParentInjectorViewOffset(location);
|
||||||
|
// view offset is 1
|
||||||
let parentView = startView;
|
let parentView = startView;
|
||||||
let parentTNode = startView[HOST_NODE] as TElementNode;
|
let parentTNode = startView[HOST_NODE] as TElementNode;
|
||||||
while (viewOffset > 0) {
|
|
||||||
|
// view offset is superior to 1
|
||||||
|
while (viewOffset > 1) {
|
||||||
parentView = parentView[DECLARATION_VIEW] !;
|
parentView = parentView[DECLARATION_VIEW] !;
|
||||||
parentTNode = parentView[HOST_NODE] as TElementNode;
|
parentTNode = parentView[HOST_NODE] as TElementNode;
|
||||||
viewOffset--;
|
viewOffset--;
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
|
|
||||||
import {ChangeDetectorRef as ViewEngine_ChangeDetectorRef} from '../change_detection/change_detector_ref';
|
import {ChangeDetectorRef as ViewEngine_ChangeDetectorRef} from '../change_detection/change_detector_ref';
|
||||||
import {Injector, NullInjector} from '../di/injector';
|
import {Injector, NullInjector} from '../di/injector';
|
||||||
|
import {InjectFlags} from '../di/injector_compatibility';
|
||||||
import {ComponentFactory as viewEngine_ComponentFactory, ComponentRef as viewEngine_ComponentRef} from '../linker/component_factory';
|
import {ComponentFactory as viewEngine_ComponentFactory, ComponentRef as viewEngine_ComponentRef} from '../linker/component_factory';
|
||||||
import {ElementRef as ViewEngine_ElementRef} from '../linker/element_ref';
|
import {ElementRef as ViewEngine_ElementRef} from '../linker/element_ref';
|
||||||
import {NgModuleRef as viewEngine_NgModuleRef} from '../linker/ng_module_factory';
|
import {NgModuleRef as viewEngine_NgModuleRef} from '../linker/ng_module_factory';
|
||||||
|
@ -159,7 +160,8 @@ export class NodeInjector implements Injector {
|
||||||
private _hostView: LViewData) {}
|
private _hostView: LViewData) {}
|
||||||
|
|
||||||
get(token: any, notFoundValue?: any): any {
|
get(token: any, notFoundValue?: any): any {
|
||||||
return getOrCreateInjectable(this._tNode, this._hostView, token, notFoundValue);
|
return getOrCreateInjectable(
|
||||||
|
this._tNode, this._hostView, token, InjectFlags.Default, notFoundValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -251,6 +251,9 @@
|
||||||
{
|
{
|
||||||
"name": "NOT_FOUND"
|
"name": "NOT_FOUND"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "NOT_YET"
|
"name": "NOT_YET"
|
||||||
},
|
},
|
||||||
|
@ -632,6 +635,9 @@
|
||||||
{
|
{
|
||||||
"name": "couldBeInjectableType"
|
"name": "couldBeInjectableType"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "createChainedInjector"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "createElementRef"
|
"name": "createElementRef"
|
||||||
},
|
},
|
||||||
|
|
|
@ -503,6 +503,9 @@
|
||||||
{
|
{
|
||||||
"name": "NOT_FOUND"
|
"name": "NOT_FOUND"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "NOT_YET"
|
"name": "NOT_YET"
|
||||||
},
|
},
|
||||||
|
@ -1310,6 +1313,9 @@
|
||||||
{
|
{
|
||||||
"name": "couldBeInjectableType"
|
"name": "couldBeInjectableType"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "createChainedInjector"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "createContainerRef"
|
"name": "createContainerRef"
|
||||||
},
|
},
|
||||||
|
|
|
@ -6,12 +6,14 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {Component as _Component, ElementRef, InjectFlags, Injectable as _Injectable, InjectionToken, InjectorType, Provider, createInjector, defineInjectable, defineInjector, inject} from '../../src/core';
|
import {Component as _Component, ComponentFactoryResolver, ElementRef, InjectFlags, Injectable as _Injectable, InjectionToken, InjectorType, Provider, RendererFactory2, ViewContainerRef, createInjector, defineInjectable, defineInjector, inject, ɵNgModuleDef as NgModuleDef} from '../../src/core';
|
||||||
import {forwardRef} from '../../src/di/forward_ref';
|
import {forwardRef} from '../../src/di/forward_ref';
|
||||||
import {ProvidersFeature, defineComponent, defineDirective, directiveInject} from '../../src/render3/index';
|
import {ProvidersFeature, defineComponent, defineDirective, directiveInject, injectComponentFactoryResolver} from '../../src/render3/index';
|
||||||
import {bind, container, containerRefreshEnd, containerRefreshStart, element, elementEnd, elementStart, embeddedViewEnd, embeddedViewStart, text, textBinding} from '../../src/render3/instructions';
|
import {bind, container, containerRefreshEnd, containerRefreshStart, element, elementEnd, elementStart, embeddedViewEnd, embeddedViewStart, interpolation1, text, textBinding} from '../../src/render3/instructions';
|
||||||
import {RenderFlags} from '../../src/render3/interfaces/definition';
|
import {RenderFlags} from '../../src/render3/interfaces/definition';
|
||||||
|
import {NgModuleFactory} from '../../src/render3/ng_module_ref';
|
||||||
|
|
||||||
|
import {getRendererFactory2} from './imported_renderer2';
|
||||||
import {ComponentFixture} from './render_util';
|
import {ComponentFixture} from './render_util';
|
||||||
|
|
||||||
const Component: typeof _Component = function(...args: any[]): any {
|
const Component: typeof _Component = function(...args: any[]): any {
|
||||||
|
@ -984,6 +986,137 @@ describe('providers', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('- dynamic components dependency resolution', () => {
|
||||||
|
let hostComponent: HostComponent|null = null;
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
template: `{{s}}`,
|
||||||
|
})
|
||||||
|
class EmbeddedComponent {
|
||||||
|
constructor(private s: String) {}
|
||||||
|
|
||||||
|
static ngComponentDef = defineComponent({
|
||||||
|
type: EmbeddedComponent,
|
||||||
|
selectors: [['embedded-cmp']],
|
||||||
|
factory: () => new EmbeddedComponent(directiveInject(String)),
|
||||||
|
consts: 1,
|
||||||
|
vars: 1,
|
||||||
|
template: (rf: RenderFlags, cmp: EmbeddedComponent) => {
|
||||||
|
if (rf & RenderFlags.Create) {
|
||||||
|
text(0);
|
||||||
|
}
|
||||||
|
if (rf & RenderFlags.Update) {
|
||||||
|
textBinding(0, interpolation1('', cmp.s, ''));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({template: `foo`, providers: [{provide: String, useValue: 'From host component'}]})
|
||||||
|
class HostComponent {
|
||||||
|
constructor(public vcref: ViewContainerRef, public cfr: ComponentFactoryResolver) {}
|
||||||
|
|
||||||
|
static ngComponentDef = defineComponent({
|
||||||
|
type: HostComponent,
|
||||||
|
selectors: [['host-cmp']],
|
||||||
|
factory: () => hostComponent = new HostComponent(
|
||||||
|
directiveInject(ViewContainerRef as any), injectComponentFactoryResolver()),
|
||||||
|
consts: 1,
|
||||||
|
vars: 0,
|
||||||
|
template: (rf: RenderFlags, cmp: HostComponent) => {
|
||||||
|
if (rf & RenderFlags.Create) {
|
||||||
|
text(0, 'foo');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
features: [
|
||||||
|
ProvidersFeature([{provide: String, useValue: 'From host component'}]),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
template: `<host-cmp></host-cmp>`,
|
||||||
|
providers: [{provide: String, useValue: 'From app component'}]
|
||||||
|
})
|
||||||
|
class AppComponent {
|
||||||
|
constructor() {}
|
||||||
|
|
||||||
|
static ngComponentDef = defineComponent({
|
||||||
|
type: AppComponent,
|
||||||
|
selectors: [['app-cmp']],
|
||||||
|
factory: () => new AppComponent(),
|
||||||
|
consts: 1,
|
||||||
|
vars: 0,
|
||||||
|
template: (rf: RenderFlags, cmp: AppComponent) => {
|
||||||
|
if (rf & RenderFlags.Create) {
|
||||||
|
element(0, 'host-cmp');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
features: [
|
||||||
|
ProvidersFeature([{provide: String, useValue: 'From app component'}]),
|
||||||
|
],
|
||||||
|
directives: [HostComponent]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
it('should not cross the root view boundary, and use the root view injector', () => {
|
||||||
|
const fixture = new ComponentFixture(AppComponent);
|
||||||
|
expect(fixture.html).toEqual('<host-cmp>foo</host-cmp>');
|
||||||
|
|
||||||
|
hostComponent !.vcref.createComponent(
|
||||||
|
hostComponent !.cfr.resolveComponentFactory(EmbeddedComponent), undefined, {
|
||||||
|
get: (token: any, notFoundValue?: any) => {
|
||||||
|
return token === String ? 'From custom root view injector' : notFoundValue;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
fixture.update();
|
||||||
|
expect(fixture.html)
|
||||||
|
.toEqual(
|
||||||
|
'<host-cmp>foo</host-cmp><embedded-cmp>From custom root view injector</embedded-cmp>');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not cross the root view boundary, and use the module injector if no root view injector',
|
||||||
|
() => {
|
||||||
|
|
||||||
|
const fixture = new ComponentFixture(AppComponent);
|
||||||
|
expect(fixture.html).toEqual('<host-cmp>foo</host-cmp>');
|
||||||
|
|
||||||
|
class MyAppModule {
|
||||||
|
static ngInjectorDef = defineInjector({
|
||||||
|
factory: () => new MyAppModule(),
|
||||||
|
imports: [],
|
||||||
|
providers: [
|
||||||
|
{provide: RendererFactory2, useValue: getRendererFactory2(document)},
|
||||||
|
{provide: String, useValue: 'From module injector'}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
static ngModuleDef: NgModuleDef<any> = { bootstrap: [] } as any;
|
||||||
|
}
|
||||||
|
const myAppModuleFactory = new NgModuleFactory(MyAppModule);
|
||||||
|
const ngModuleRef = myAppModuleFactory.create(null);
|
||||||
|
|
||||||
|
hostComponent !.vcref.createComponent(
|
||||||
|
hostComponent !.cfr.resolveComponentFactory(EmbeddedComponent), undefined,
|
||||||
|
{get: (token: any, notFoundValue?: any) => notFoundValue}, undefined, ngModuleRef);
|
||||||
|
fixture.update();
|
||||||
|
expect(fixture.html)
|
||||||
|
.toMatch(
|
||||||
|
/<host-cmp>foo<\/host-cmp><embedded-cmp _nghost-c(\d+)="">From module injector<\/embedded-cmp>/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should cross the root view boundary to the parent of the host, thanks to the default root view injector',
|
||||||
|
() => {
|
||||||
|
const fixture = new ComponentFixture(AppComponent);
|
||||||
|
expect(fixture.html).toEqual('<host-cmp>foo</host-cmp>');
|
||||||
|
|
||||||
|
hostComponent !.vcref.createComponent(
|
||||||
|
hostComponent !.cfr.resolveComponentFactory(EmbeddedComponent));
|
||||||
|
fixture.update();
|
||||||
|
expect(fixture.html)
|
||||||
|
.toEqual('<host-cmp>foo</host-cmp><embedded-cmp>From app component</embedded-cmp>');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('deps boundary:', () => {
|
describe('deps boundary:', () => {
|
||||||
it('the deps of a token declared in providers should not be resolved with tokens from viewProviders',
|
it('the deps of a token declared in providers should not be resolved with tokens from viewProviders',
|
||||||
() => {
|
() => {
|
||||||
|
|
|
@ -923,24 +923,26 @@ describe('ViewContainerRef', () => {
|
||||||
describe('createComponent', () => {
|
describe('createComponent', () => {
|
||||||
let templateExecutionCounter = 0;
|
let templateExecutionCounter = 0;
|
||||||
|
|
||||||
class EmbeddedComponent {
|
|
||||||
static ngComponentDef = defineComponent({
|
|
||||||
type: EmbeddedComponent,
|
|
||||||
encapsulation: ViewEncapsulation.None,
|
|
||||||
selectors: [['embedded-cmp']],
|
|
||||||
factory: () => new EmbeddedComponent(),
|
|
||||||
consts: 1,
|
|
||||||
vars: 0,
|
|
||||||
template: (rf: RenderFlags, cmp: EmbeddedComponent) => {
|
|
||||||
templateExecutionCounter++;
|
|
||||||
if (rf & RenderFlags.Create) {
|
|
||||||
text(0, 'foo');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
it('should work without Injector and NgModuleRef', () => {
|
it('should work without Injector and NgModuleRef', () => {
|
||||||
|
class EmbeddedComponent {
|
||||||
|
constructor() {}
|
||||||
|
|
||||||
|
static ngComponentDef = defineComponent({
|
||||||
|
type: EmbeddedComponent,
|
||||||
|
encapsulation: ViewEncapsulation.None,
|
||||||
|
selectors: [['embedded-cmp']],
|
||||||
|
factory: () => new EmbeddedComponent(),
|
||||||
|
consts: 1,
|
||||||
|
vars: 0,
|
||||||
|
template: (rf: RenderFlags, cmp: EmbeddedComponent) => {
|
||||||
|
templateExecutionCounter++;
|
||||||
|
if (rf & RenderFlags.Create) {
|
||||||
|
text(0, 'foo');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
templateExecutionCounter = 0;
|
templateExecutionCounter = 0;
|
||||||
const fixture =
|
const fixture =
|
||||||
new TemplateFixture(createTemplate, updateTemplate, 3, 1, [DirectiveWithVCRef]);
|
new TemplateFixture(createTemplate, updateTemplate, 3, 1, [DirectiveWithVCRef]);
|
||||||
|
@ -965,13 +967,33 @@ describe('ViewContainerRef', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should work with NgModuleRef and Injector', () => {
|
it('should work with NgModuleRef and Injector', () => {
|
||||||
|
class EmbeddedComponent {
|
||||||
|
constructor(public s: String) {}
|
||||||
|
|
||||||
|
static ngComponentDef = defineComponent({
|
||||||
|
type: EmbeddedComponent,
|
||||||
|
encapsulation: ViewEncapsulation.None,
|
||||||
|
selectors: [['embedded-cmp']],
|
||||||
|
factory: () => new EmbeddedComponent(directiveInject(String)),
|
||||||
|
consts: 1,
|
||||||
|
vars: 0,
|
||||||
|
template: (rf: RenderFlags, cmp: EmbeddedComponent) => {
|
||||||
|
templateExecutionCounter++;
|
||||||
|
if (rf & RenderFlags.Create) {
|
||||||
|
text(0, 'foo');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
class MyAppModule {
|
class MyAppModule {
|
||||||
static ngInjectorDef = defineInjector({
|
static ngInjectorDef = defineInjector({
|
||||||
factory: () => new MyAppModule(),
|
factory: () => new MyAppModule(),
|
||||||
imports: [],
|
imports: [],
|
||||||
providers: [
|
providers: [
|
||||||
{provide: APP_ROOT, useValue: true},
|
{provide: APP_ROOT, useValue: true},
|
||||||
{provide: RendererFactory2, useValue: getRendererFactory2(document)}
|
{provide: RendererFactory2, useValue: getRendererFactory2(document)},
|
||||||
|
{provide: String, useValue: 'module'}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
static ngModuleDef: NgModuleDef<any> = { bootstrap: [] } as any;
|
static ngModuleDef: NgModuleDef<any> = { bootstrap: [] } as any;
|
||||||
|
@ -982,7 +1004,10 @@ describe('ViewContainerRef', () => {
|
||||||
class SomeModule {
|
class SomeModule {
|
||||||
static ngInjectorDef = defineInjector({
|
static ngInjectorDef = defineInjector({
|
||||||
factory: () => new SomeModule(),
|
factory: () => new SomeModule(),
|
||||||
providers: [{provide: NgModuleRef, useValue: ngModuleRef}]
|
providers: [
|
||||||
|
{provide: NgModuleRef, useValue: ngModuleRef},
|
||||||
|
{provide: String, useValue: 'injector'}
|
||||||
|
]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const injector = createInjector(SomeModule);
|
const injector = createInjector(SomeModule);
|
||||||
|
@ -993,11 +1018,12 @@ describe('ViewContainerRef', () => {
|
||||||
expect(fixture.html).toEqual('<p vcref=""></p>');
|
expect(fixture.html).toEqual('<p vcref=""></p>');
|
||||||
expect(templateExecutionCounter).toEqual(0);
|
expect(templateExecutionCounter).toEqual(0);
|
||||||
|
|
||||||
directiveInstance !.vcref.createComponent(
|
const componentRef = directiveInstance !.vcref.createComponent(
|
||||||
directiveInstance !.cfr.resolveComponentFactory(EmbeddedComponent), 0, injector);
|
directiveInstance !.cfr.resolveComponentFactory(EmbeddedComponent), 0, injector);
|
||||||
fixture.update();
|
fixture.update();
|
||||||
expect(fixture.html).toEqual('<p vcref=""></p><embedded-cmp>foo</embedded-cmp>');
|
expect(fixture.html).toEqual('<p vcref=""></p><embedded-cmp>foo</embedded-cmp>');
|
||||||
expect(templateExecutionCounter).toEqual(2);
|
expect(templateExecutionCounter).toEqual(2);
|
||||||
|
expect(componentRef.instance.s).toEqual('injector');
|
||||||
|
|
||||||
directiveInstance !.vcref.createComponent(
|
directiveInstance !.vcref.createComponent(
|
||||||
directiveInstance !.cfr.resolveComponentFactory(EmbeddedComponent), 0, undefined,
|
directiveInstance !.cfr.resolveComponentFactory(EmbeddedComponent), 0, undefined,
|
||||||
|
|
Loading…
Reference in New Issue