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 =
|
||||
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}.
|
||||
*/
|
||||
|
@ -122,7 +141,7 @@ export class ComponentFactory<T> extends viewEngine_ComponentFactory<T> {
|
|||
// Create the root view. Uses empty TView and ContentTemplate.
|
||||
const rootView: LViewData = createLViewData(
|
||||
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
|
||||
const oldView = enterView(rootView, null);
|
||||
|
|
|
@ -236,9 +236,12 @@ export function getParentInjectorTNode(
|
|||
}
|
||||
|
||||
let viewOffset = getParentInjectorViewOffset(location);
|
||||
// view offset is 1
|
||||
let parentView = startView;
|
||||
let parentTNode = startView[HOST_NODE] as TElementNode;
|
||||
while (viewOffset > 0) {
|
||||
|
||||
// view offset is superior to 1
|
||||
while (viewOffset > 1) {
|
||||
parentView = parentView[DECLARATION_VIEW] !;
|
||||
parentTNode = parentView[HOST_NODE] as TElementNode;
|
||||
viewOffset--;
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
import {ChangeDetectorRef as ViewEngine_ChangeDetectorRef} from '../change_detection/change_detector_ref';
|
||||
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 {ElementRef as ViewEngine_ElementRef} from '../linker/element_ref';
|
||||
import {NgModuleRef as viewEngine_NgModuleRef} from '../linker/ng_module_factory';
|
||||
|
@ -159,7 +160,8 @@ export class NodeInjector implements Injector {
|
|||
private _hostView: LViewData) {}
|
||||
|
||||
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_CHECK_ONLY_ELEMENT_INJECTOR"
|
||||
},
|
||||
{
|
||||
"name": "NOT_YET"
|
||||
},
|
||||
|
@ -632,6 +635,9 @@
|
|||
{
|
||||
"name": "couldBeInjectableType"
|
||||
},
|
||||
{
|
||||
"name": "createChainedInjector"
|
||||
},
|
||||
{
|
||||
"name": "createElementRef"
|
||||
},
|
||||
|
|
|
@ -503,6 +503,9 @@
|
|||
{
|
||||
"name": "NOT_FOUND"
|
||||
},
|
||||
{
|
||||
"name": "NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR"
|
||||
},
|
||||
{
|
||||
"name": "NOT_YET"
|
||||
},
|
||||
|
@ -1310,6 +1313,9 @@
|
|||
{
|
||||
"name": "couldBeInjectableType"
|
||||
},
|
||||
{
|
||||
"name": "createChainedInjector"
|
||||
},
|
||||
{
|
||||
"name": "createContainerRef"
|
||||
},
|
||||
|
|
|
@ -6,12 +6,14 @@
|
|||
* 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 {ProvidersFeature, defineComponent, defineDirective, directiveInject} from '../../src/render3/index';
|
||||
import {bind, container, containerRefreshEnd, containerRefreshStart, element, elementEnd, elementStart, embeddedViewEnd, embeddedViewStart, text, textBinding} from '../../src/render3/instructions';
|
||||
import {ProvidersFeature, defineComponent, defineDirective, directiveInject, injectComponentFactoryResolver} from '../../src/render3/index';
|
||||
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 {NgModuleFactory} from '../../src/render3/ng_module_ref';
|
||||
|
||||
import {getRendererFactory2} from './imported_renderer2';
|
||||
import {ComponentFixture} from './render_util';
|
||||
|
||||
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:', () => {
|
||||
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', () => {
|
||||
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', () => {
|
||||
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;
|
||||
const fixture =
|
||||
new TemplateFixture(createTemplate, updateTemplate, 3, 1, [DirectiveWithVCRef]);
|
||||
|
@ -965,13 +967,33 @@ describe('ViewContainerRef', () => {
|
|||
});
|
||||
|
||||
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 {
|
||||
static ngInjectorDef = defineInjector({
|
||||
factory: () => new MyAppModule(),
|
||||
imports: [],
|
||||
providers: [
|
||||
{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;
|
||||
|
@ -982,7 +1004,10 @@ describe('ViewContainerRef', () => {
|
|||
class SomeModule {
|
||||
static ngInjectorDef = defineInjector({
|
||||
factory: () => new SomeModule(),
|
||||
providers: [{provide: NgModuleRef, useValue: ngModuleRef}]
|
||||
providers: [
|
||||
{provide: NgModuleRef, useValue: ngModuleRef},
|
||||
{provide: String, useValue: 'injector'}
|
||||
]
|
||||
});
|
||||
}
|
||||
const injector = createInjector(SomeModule);
|
||||
|
@ -993,11 +1018,12 @@ describe('ViewContainerRef', () => {
|
|||
expect(fixture.html).toEqual('<p vcref=""></p>');
|
||||
expect(templateExecutionCounter).toEqual(0);
|
||||
|
||||
directiveInstance !.vcref.createComponent(
|
||||
const componentRef = directiveInstance !.vcref.createComponent(
|
||||
directiveInstance !.cfr.resolveComponentFactory(EmbeddedComponent), 0, injector);
|
||||
fixture.update();
|
||||
expect(fixture.html).toEqual('<p vcref=""></p><embedded-cmp>foo</embedded-cmp>');
|
||||
expect(templateExecutionCounter).toEqual(2);
|
||||
expect(componentRef.instance.s).toEqual('injector');
|
||||
|
||||
directiveInstance !.vcref.createComponent(
|
||||
directiveInstance !.cfr.resolveComponentFactory(EmbeddedComponent), 0, undefined,
|
||||
|
|
Loading…
Reference in New Issue