feat(core): allow for injector to be specified when creating an embedded view (#44666)

Adds support for passing in an optional injector when creating an embedded view through `ViewContainerRef.createEmbeddedView` and `TemplateRef.createEmbeddedView`. The injector allows for the DI behavior to be customized within the specific template.

Fixes #14935.

PR Close #44666
This commit is contained in:
Kristiyan Kostadinov
2022-01-19 09:28:18 +01:00
committed by Dylan Hunn
parent fb27867ab8
commit b49ffcd50e
11 changed files with 716 additions and 34 deletions
+20 -4
View File
@@ -6,10 +6,12 @@
* found in the LICENSE file at https://angular.io/license
*/
import {Injector} from '../di/injector';
import {assertLContainer} from '../render3/assert';
import {ChainedInjector} from '../render3/chained_injector';
import {createLView, renderView} from '../render3/instructions/shared';
import {TContainerNode, TNode, TNodeType} from '../render3/interfaces/node';
import {DECLARATION_LCONTAINER, LView, LViewFlags, QUERIES, TView} from '../render3/interfaces/view';
import {DECLARATION_LCONTAINER, INJECTOR, LView, LViewFlags, QUERIES, TView} from '../render3/interfaces/view';
import {getCurrentTNode, getLView} from '../render3/state';
import {ViewRef as R3_ViewRef} from '../render3/view_ref';
import {assertDefined} from '../util/assert';
@@ -55,9 +57,10 @@ export abstract class TemplateRef<C> {
* and attaches it to the view container.
* @param context The data-binding context of the embedded view, as declared
* in the `<ng-template>` usage.
* @param injector Injector to be used within the embedded view.
* @returns The new embedded view object.
*/
abstract createEmbeddedView(context: C): EmbeddedViewRef<C>;
abstract createEmbeddedView(context: C, injector?: Injector): EmbeddedViewRef<C>;
/**
* @internal
@@ -77,11 +80,12 @@ const R3TemplateRef = class TemplateRef<T> extends ViewEngineTemplateRef<T> {
super();
}
override createEmbeddedView(context: T): EmbeddedViewRef<T> {
override createEmbeddedView(context: T, injector?: Injector): EmbeddedViewRef<T> {
const embeddedTView = this._declarationTContainer.tViews as TView;
const embeddedLView = createLView(
this._declarationLView, embeddedTView, context, LViewFlags.CheckAlways, null,
embeddedTView.declTNode, null, null, null, null);
embeddedTView.declTNode, null, null, null,
createEmbeddedViewInjector(injector, this._declarationLView[INJECTOR]));
const declarationLContainer = this._declarationLView[this._declarationTContainer.index];
ngDevMode && assertLContainer(declarationLContainer);
@@ -98,6 +102,18 @@ const R3TemplateRef = class TemplateRef<T> extends ViewEngineTemplateRef<T> {
}
};
function createEmbeddedViewInjector(
embeddedViewInjector: Injector|undefined, declarationViewInjector: Injector|null): Injector|
null {
if (!embeddedViewInjector) {
return null;
}
return declarationViewInjector ?
new ChainedInjector(embeddedViewInjector, declarationViewInjector) :
embeddedViewInjector;
}
/**
* Creates a TemplateRef given a node.
*
+38 -2
View File
@@ -90,6 +90,24 @@ export abstract class ViewContainerRef {
*/
abstract get length(): number;
/**
* Instantiates an embedded view and inserts it
* into this container.
* @param templateRef The HTML template that defines the view.
* @param context The data-binding context of the embedded view, as declared
* in the `<ng-template>` usage.
* @param options Extra configuration for the created view. Includes:
* * index: The 0-based index at which to insert the new view into this container.
* If not specified, appends the new view as the last entry.
* * injector: Injector to be used within the embedded view.
*
* @returns The `ViewRef` instance for the newly created view.
*/
abstract createEmbeddedView<C>(templateRef: TemplateRef<C>, context?: C, options?: {
index?: number,
injector?: Injector
}): EmbeddedViewRef<C>;
/**
* Instantiates an embedded view and inserts it
* into this container.
@@ -258,9 +276,27 @@ const R3ViewContainerRef = class ViewContainerRef extends VE_ViewContainerRef {
return this._lContainer.length - CONTAINER_HEADER_OFFSET;
}
override createEmbeddedView<C>(templateRef: TemplateRef<C>, context?: C, options?: {
index?: number,
injector?: Injector
}): EmbeddedViewRef<C>;
override createEmbeddedView<C>(templateRef: TemplateRef<C>, context?: C, index?: number):
EmbeddedViewRef<C> {
const viewRef = templateRef.createEmbeddedView(context || <any>{});
EmbeddedViewRef<C>;
override createEmbeddedView<C>(templateRef: TemplateRef<C>, context?: C, indexOrOptions?: number|{
index?: number,
injector?: Injector
}): EmbeddedViewRef<C> {
let index: number|undefined;
let injector: Injector|undefined;
if (typeof indexOrOptions === 'number') {
index = indexOrOptions;
} else if (indexOrOptions != null) {
index = indexOrOptions.index;
injector = indexOrOptions.injector;
}
const viewRef = templateRef.createEmbeddedView(context || <any>{}, injector);
this.insert(viewRef, index);
return viewRef;
}
@@ -0,0 +1,36 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {Injector} from '../di/injector';
import {InjectFlags} from '../di/interface/injector';
import {ProviderToken} from '../di/provider_token';
import {NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR} from '../view/provider_flags';
/**
* Injector that looks up a value using a specific injector, before falling back to the module
* injector. Used primarily when creating components or embedded views dynamically.
*/
export class ChainedInjector implements Injector {
constructor(private injector: Injector, private parentInjector: Injector) {}
get<T>(token: ProviderToken<T>, notFoundValue?: T, flags?: InjectFlags): T {
const value = this.injector.get(token, NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR as T, flags);
if (value !== NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR ||
notFoundValue === 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)
// - the module injector should not be checked
// (notFoundValue === NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR)
return value;
}
return this.parentInjector.get(token, notFoundValue, flags);
}
}
+5 -26
View File
@@ -9,8 +9,6 @@
import {ChangeDetectorRef as ViewEngine_ChangeDetectorRef} from '../change_detection/change_detector_ref';
import {InjectionToken} from '../di/injection_token';
import {Injector} from '../di/injector';
import {InjectFlags} from '../di/interface/injector';
import {ProviderToken} from '../di/provider_token';
import {Type} from '../interface/type';
import {ComponentFactory as viewEngine_ComponentFactory, ComponentRef as viewEngine_ComponentRef} from '../linker/component_factory';
import {ComponentFactoryResolver as viewEngine_ComponentFactoryResolver} from '../linker/component_factory_resolver';
@@ -19,8 +17,9 @@ import {NgModuleRef as viewEngine_NgModuleRef} from '../linker/ng_module_factory
import {RendererFactory2} from '../render/api';
import {Sanitizer} from '../sanitization/sanitizer';
import {VERSION} from '../version';
import {NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR} from '../view/provider_flags';
import {assertComponentType} from './assert';
import {ChainedInjector} from './chained_injector';
import {createRootComponent, createRootComponentView, createRootContext, LifecycleHooksFeature} from './component';
import {getComponentDef} from './definition';
import {NodeInjector} from './di';
@@ -79,26 +78,6 @@ export const SCHEDULER = new InjectionToken<((fn: () => void) => void)>('SCHEDUL
factory: () => defaultScheduler,
});
function createChainedInjector(rootViewInjector: Injector, moduleInjector: Injector): Injector {
return {
get: <T>(token: ProviderToken<T>, notFoundValue?: T, flags?: InjectFlags): T => {
const value = rootViewInjector.get(token, NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR as T, flags);
if (value !== NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR ||
notFoundValue === 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)
// - the module injector should not be checked
// (notFoundValue === NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR)
return value;
}
return moduleInjector.get(token, notFoundValue, flags);
}
};
}
/**
* Render3 implementation of {@link viewEngine_ComponentFactory}.
*/
@@ -135,11 +114,11 @@ export class ComponentFactory<T> extends viewEngine_ComponentFactory<T> {
ngModule?: viewEngine_NgModuleRef<any>|undefined): viewEngine_ComponentRef<T> {
ngModule = ngModule || this.ngModule;
const rootViewInjector =
ngModule ? createChainedInjector(injector, ngModule.injector) : injector;
const rootViewInjector = ngModule ? new ChainedInjector(injector, ngModule.injector) : injector;
const rendererFactory =
rootViewInjector.get(RendererFactory2, domRendererFactory3) as RendererFactory3;
rootViewInjector.get(RendererFactory2, domRendererFactory3 as RendererFactory2) as
RendererFactory3;
const sanitizer = rootViewInjector.get(Sanitizer, null);
const hostRenderer = rendererFactory.createRenderer(null, this.componentDef);
@@ -48,7 +48,7 @@ import {LView, TData} from './view';
* index + 7: cumulative bloom filter
* index + 8: cumulative bloom filter
* index + TNODE: TNode associated with this `NodeInjector`
* `canst tNode = tView.data[index + NodeInjectorOffset.TNODE]`
* `const tNode = tView.data[index + NodeInjectorOffset.TNODE]`
* ```
*/
export const enum NodeInjectorOffset {