mirror of
https://github.com/angular/angular
synced 2026-05-24 09:28:37 +00:00
feat(core): allow for injector to be specified when creating an embedded view (#45156)
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. This is a second stab at the changes in #44666. The difference this time is that the new injector acts as a node injector, rather than a module injector. Fixes #14935. PR Close #45156
This commit is contained in:
parent
9366a3c5f3
commit
94c949a60a
40 changed files with 1598 additions and 178 deletions
|
|
@ -585,8 +585,9 @@ export class NgTemplateOutlet implements OnChanges {
|
|||
ngOnChanges(changes: SimpleChanges): void;
|
||||
ngTemplateOutlet: TemplateRef<any> | null;
|
||||
ngTemplateOutletContext: Object | null;
|
||||
ngTemplateOutletInjector: Injector | null;
|
||||
// (undocumented)
|
||||
static ɵdir: i0.ɵɵDirectiveDeclaration<NgTemplateOutlet, "[ngTemplateOutlet]", never, { "ngTemplateOutletContext": "ngTemplateOutletContext"; "ngTemplateOutlet": "ngTemplateOutlet"; }, {}, never>;
|
||||
static ɵdir: i0.ɵɵDirectiveDeclaration<NgTemplateOutlet, "[ngTemplateOutlet]", never, { "ngTemplateOutletContext": "ngTemplateOutletContext"; "ngTemplateOutlet": "ngTemplateOutlet"; "ngTemplateOutletInjector": "ngTemplateOutletInjector"; }, {}, never>;
|
||||
// (undocumented)
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<NgTemplateOutlet, never>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1233,7 +1233,7 @@ export type StaticProvider = ValueProvider | ExistingProvider | StaticClassProvi
|
|||
|
||||
// @public
|
||||
export abstract class TemplateRef<C> {
|
||||
abstract createEmbeddedView(context: C): EmbeddedViewRef<C>;
|
||||
abstract createEmbeddedView(context: C, injector?: Injector): EmbeddedViewRef<C>;
|
||||
abstract readonly elementRef: ElementRef;
|
||||
}
|
||||
|
||||
|
|
@ -1379,6 +1379,10 @@ export abstract class ViewContainerRef {
|
|||
}): ComponentRef<C>;
|
||||
// @deprecated
|
||||
abstract createComponent<C>(componentFactory: ComponentFactory<C>, index?: number, injector?: Injector, projectableNodes?: any[][], ngModuleRef?: NgModuleRef<any>): ComponentRef<C>;
|
||||
abstract createEmbeddedView<C>(templateRef: TemplateRef<C>, context?: C, options?: {
|
||||
index?: number;
|
||||
injector?: Injector;
|
||||
}): EmbeddedViewRef<C>;
|
||||
abstract createEmbeddedView<C>(templateRef: TemplateRef<C>, context?: C, index?: number): EmbeddedViewRef<C>;
|
||||
abstract detach(index?: number): ViewRef | null;
|
||||
abstract get element(): ElementRef;
|
||||
|
|
|
|||
|
|
@ -15,8 +15,8 @@
|
|||
"master": {
|
||||
"uncompressed": {
|
||||
"runtime": 4343,
|
||||
"main": 450179,
|
||||
"polyfills": 37823,
|
||||
"main": 450900,
|
||||
"polyfills": 37817,
|
||||
"styles": 70416,
|
||||
"light-theme": 77582,
|
||||
"dark-theme": 77711
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@
|
|||
"master": {
|
||||
"uncompressed": {
|
||||
"runtime": 1083,
|
||||
"main": 126218,
|
||||
"polyfills": 37778
|
||||
"main": 127046,
|
||||
"polyfills": 37772
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -15,8 +15,8 @@
|
|||
"main": "TODO(i): temporarily increase the payload size limit from 17597 - this needs to be investigate further what caused the increase.",
|
||||
"main": "Likely there is a missing PURE annotation https://github.com/angular/angular/pull/43344",
|
||||
"main": "Tracking issue: https://github.com/angular/angular/issues/43568",
|
||||
"main": 20378,
|
||||
"polyfills": 37802
|
||||
"main": 20601,
|
||||
"polyfills": 37796
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -24,8 +24,8 @@
|
|||
"master": {
|
||||
"uncompressed": {
|
||||
"runtime": 1105,
|
||||
"main": 131882,
|
||||
"polyfills": 37800
|
||||
"main": 132710,
|
||||
"polyfills": 37794
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -33,8 +33,8 @@
|
|||
"master": {
|
||||
"uncompressed": {
|
||||
"runtime": 929,
|
||||
"main": 124544,
|
||||
"polyfills": 38488
|
||||
"main": 125295,
|
||||
"polyfills": 38482
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -42,8 +42,8 @@
|
|||
"master": {
|
||||
"uncompressed": {
|
||||
"runtime": 2835,
|
||||
"main": 233348,
|
||||
"polyfills": 37796,
|
||||
"main": 233902,
|
||||
"polyfills": 37790,
|
||||
"src_app_lazy_lazy_module_ts": 795
|
||||
}
|
||||
}
|
||||
|
|
@ -52,8 +52,8 @@
|
|||
"master": {
|
||||
"uncompressed": {
|
||||
"runtime": 1063,
|
||||
"main": 158556,
|
||||
"polyfills": 37758
|
||||
"main": 159384,
|
||||
"polyfills": 37752
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -61,8 +61,8 @@
|
|||
"master": {
|
||||
"uncompressed": {
|
||||
"runtime": 1070,
|
||||
"main": 158300,
|
||||
"polyfills": 37768
|
||||
"main": 159051,
|
||||
"polyfills": 37762
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Directive, EmbeddedViewRef, Input, OnChanges, SimpleChange, SimpleChanges, TemplateRef, ViewContainerRef} from '@angular/core';
|
||||
import {Directive, EmbeddedViewRef, Injector, Input, OnChanges, SimpleChanges, TemplateRef, ViewContainerRef} from '@angular/core';
|
||||
|
||||
/**
|
||||
* @ngModule CommonModule
|
||||
|
|
@ -49,20 +49,31 @@ export class NgTemplateOutlet implements OnChanges {
|
|||
*/
|
||||
@Input() public ngTemplateOutlet: TemplateRef<any>|null = null;
|
||||
|
||||
/** Injector to be used within the embedded view. */
|
||||
@Input() public ngTemplateOutletInjector: Injector|null = null;
|
||||
|
||||
constructor(private _viewContainerRef: ViewContainerRef) {}
|
||||
|
||||
/** @nodoc */
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
if (changes['ngTemplateOutlet']) {
|
||||
if (changes['ngTemplateOutlet'] || changes['ngTemplateOutletInjector']) {
|
||||
const viewContainerRef = this._viewContainerRef;
|
||||
|
||||
if (this._viewRef) {
|
||||
viewContainerRef.remove(viewContainerRef.indexOf(this._viewRef));
|
||||
}
|
||||
|
||||
this._viewRef = this.ngTemplateOutlet ?
|
||||
viewContainerRef.createEmbeddedView(this.ngTemplateOutlet, this.ngTemplateOutletContext) :
|
||||
null;
|
||||
if (this.ngTemplateOutlet) {
|
||||
const {
|
||||
ngTemplateOutlet: template,
|
||||
ngTemplateOutletContext: context,
|
||||
ngTemplateOutletInjector: injector
|
||||
} = this;
|
||||
this._viewRef = viewContainerRef.createEmbeddedView(
|
||||
template, context, injector ? {injector} : undefined);
|
||||
} else {
|
||||
this._viewRef = null;
|
||||
}
|
||||
} else if (
|
||||
this._viewRef && changes['ngTemplateOutletContext'] && this.ngTemplateOutletContext) {
|
||||
this._viewRef.context = this.ngTemplateOutletContext;
|
||||
|
|
|
|||
|
|
@ -96,8 +96,10 @@ describe('insert/remove', () => {
|
|||
|
||||
const uniqueValue = {};
|
||||
fixture.componentInstance.currentComponent = InjectedComponent;
|
||||
fixture.componentInstance.injector = Injector.create(
|
||||
[{provide: TEST_TOKEN, useValue: uniqueValue}], fixture.componentRef.injector);
|
||||
fixture.componentInstance.injector = Injector.create({
|
||||
providers: [{provide: TEST_TOKEN, useValue: uniqueValue}],
|
||||
parent: fixture.componentRef.injector,
|
||||
});
|
||||
|
||||
fixture.detectChanges();
|
||||
let cmpRef: ComponentRef<InjectedComponent> = fixture.componentInstance.cmpRef!;
|
||||
|
|
@ -250,6 +252,21 @@ describe('insert/remove', () => {
|
|||
|
||||
expect(fixture.nativeElement).toHaveText('bat');
|
||||
}));
|
||||
|
||||
it('should override providers from parent component using custom injector', waitForAsync(() => {
|
||||
TestBed.overrideComponent(InjectedComponent, {set: {template: 'Value: {{testToken}}'}});
|
||||
TestBed.overrideComponent(
|
||||
TestComponent, {set: {providers: [{provide: TEST_TOKEN, useValue: 'parent'}]}});
|
||||
const fixture = TestBed.createComponent(TestComponent);
|
||||
fixture.componentInstance.currentComponent = InjectedComponent;
|
||||
fixture.componentInstance.injector = Injector.create({
|
||||
providers: [{provide: TEST_TOKEN, useValue: 'child'}],
|
||||
parent: fixture.componentInstance.vcRef.injector
|
||||
});
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(fixture.nativeElement).toHaveText('Value: child');
|
||||
}));
|
||||
});
|
||||
|
||||
const TEST_TOKEN = new InjectionToken('TestToken');
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
*/
|
||||
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {Component, ContentChildren, Directive, Injectable, NO_ERRORS_SCHEMA, OnDestroy, QueryList, TemplateRef} from '@angular/core';
|
||||
import {Component, ContentChildren, Directive, Inject, Injectable, InjectionToken, Injector, NO_ERRORS_SCHEMA, OnDestroy, Provider, QueryList, TemplateRef} from '@angular/core';
|
||||
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
|
||||
import {expect} from '@angular/platform-browser/testing/src/matchers';
|
||||
|
||||
|
|
@ -29,7 +29,13 @@ describe('NgTemplateOutlet', () => {
|
|||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [TestComponent, CaptureTplRefs, DestroyableCmpt, MultiContextComponent],
|
||||
declarations: [
|
||||
TestComponent,
|
||||
CaptureTplRefs,
|
||||
DestroyableCmpt,
|
||||
MultiContextComponent,
|
||||
InjectValueComponent,
|
||||
],
|
||||
imports: [CommonModule],
|
||||
providers: [DestroyedSpyService]
|
||||
});
|
||||
|
|
@ -262,8 +268,41 @@ describe('NgTemplateOutlet', () => {
|
|||
expect(componentInstance.context1).toEqual({name: 'two'});
|
||||
expect(componentInstance.context2).toEqual({name: 'one'});
|
||||
});
|
||||
|
||||
it('should be able to specify an injector', waitForAsync(() => {
|
||||
const template = `<ng-template #tpl><inject-value></inject-value></ng-template>` +
|
||||
`<ng-container *ngTemplateOutlet="tpl; injector: injector"></ng-container>`;
|
||||
fixture = createTestComponent(template);
|
||||
fixture.componentInstance.injector =
|
||||
Injector.create({providers: [{provide: templateToken, useValue: 'world'}]});
|
||||
detectChangesAndExpectText('Hello world');
|
||||
}));
|
||||
|
||||
it('should re-render if the injector changes', waitForAsync(() => {
|
||||
const template = `<ng-template #tpl><inject-value></inject-value></ng-template>` +
|
||||
`<ng-container *ngTemplateOutlet="tpl; injector: injector"></ng-container>`;
|
||||
fixture = createTestComponent(template);
|
||||
fixture.componentInstance.injector =
|
||||
Injector.create({providers: [{provide: templateToken, useValue: 'world'}]});
|
||||
detectChangesAndExpectText('Hello world');
|
||||
|
||||
fixture.componentInstance.injector =
|
||||
Injector.create({providers: [{provide: templateToken, useValue: 'there'}]});
|
||||
detectChangesAndExpectText('Hello there');
|
||||
}));
|
||||
|
||||
it('should override providers from parent component using custom injector', waitForAsync(() => {
|
||||
const template = `<ng-template #tpl><inject-value></inject-value></ng-template>` +
|
||||
`<ng-container *ngTemplateOutlet="tpl; injector: injector"></ng-container>`;
|
||||
fixture = createTestComponent(template, [{provide: templateToken, useValue: 'parent'}]);
|
||||
fixture.componentInstance.injector =
|
||||
Injector.create({providers: [{provide: templateToken, useValue: 'world'}]});
|
||||
detectChangesAndExpectText('Hello world');
|
||||
}));
|
||||
});
|
||||
|
||||
const templateToken = new InjectionToken<string>('templateToken');
|
||||
|
||||
@Injectable()
|
||||
class DestroyedSpyService {
|
||||
destroyed = false;
|
||||
|
|
@ -290,6 +329,15 @@ class TestComponent {
|
|||
currentTplRef!: TemplateRef<any>;
|
||||
context: any = {foo: 'bar'};
|
||||
value = 'bar';
|
||||
injector: Injector|null = null;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'inject-value',
|
||||
template: 'Hello {{tokenValue}}',
|
||||
})
|
||||
class InjectValueComponent {
|
||||
constructor(@Inject(templateToken) public tokenValue: string) {}
|
||||
}
|
||||
|
||||
@Component({
|
||||
|
|
@ -305,8 +353,9 @@ class MultiContextComponent {
|
|||
context2: {name: string}|undefined;
|
||||
}
|
||||
|
||||
function createTestComponent(template: string): ComponentFixture<TestComponent> {
|
||||
return TestBed.overrideComponent(TestComponent, {set: {template: template}})
|
||||
function createTestComponent(
|
||||
template: string, providers: Provider[] = []): ComponentFixture<TestComponent> {
|
||||
return TestBed.overrideComponent(TestComponent, {set: {template: template, providers}})
|
||||
.configureTestingModule({schemas: [NO_ERRORS_SCHEMA]})
|
||||
.createComponent(TestComponent);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Injector} from '../di/injector';
|
||||
import {assertLContainer} from '../render3/assert';
|
||||
import {createLView, renderView} from '../render3/instructions/shared';
|
||||
import {TContainerNode, TNode, TNodeType} from '../render3/interfaces/node';
|
||||
|
|
@ -55,9 +56,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 +79,11 @@ 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, null, injector || null);
|
||||
|
||||
const declarationLContainer = this._declarationLView[this._declarationTContainer.index];
|
||||
ngDevMode && assertLContainer(declarationLContainer);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -134,7 +134,7 @@ export function renderComponent<T>(
|
|||
const rootTView = createTView(TViewType.Root, null, null, 1, 0, null, null, null, null, null);
|
||||
const rootView: LView = createLView(
|
||||
null, rootTView, rootContext, rootFlags, null, null, rendererFactory, renderer, null,
|
||||
opts.injector || null);
|
||||
opts.injector || null, null);
|
||||
|
||||
enterView(rootView);
|
||||
let component: T;
|
||||
|
|
@ -200,7 +200,7 @@ export function createRootComponentView(
|
|||
const componentView = createLView(
|
||||
rootView, getOrCreateTComponentView(def), null,
|
||||
def.onPush ? LViewFlags.Dirty : LViewFlags.CheckAlways, rootView[index], tNode,
|
||||
rendererFactory, viewRenderer, sanitizer || null, null);
|
||||
rendererFactory, viewRenderer, sanitizer || null, null, null);
|
||||
|
||||
if (tView.firstCreatePass) {
|
||||
diPublicInInjector(getOrCreateNodeInjectorForNode(tNode, rootView), tView, def.type);
|
||||
|
|
|
|||
|
|
@ -69,24 +69,28 @@ function getNamespace(elementName: string): string|null {
|
|||
return name === 'svg' ? SVG_NAMESPACE : (name === 'math' ? MATH_ML_NAMESPACE : null);
|
||||
}
|
||||
|
||||
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);
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
class ChainedInjector implements Injector {
|
||||
constructor(private injector: Injector, private parentInjector: Injector) {}
|
||||
|
||||
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;
|
||||
}
|
||||
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);
|
||||
|
||||
return moduleInjector.get(token, notFoundValue, 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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -125,11 +129,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);
|
||||
|
|
@ -150,7 +154,7 @@ export class ComponentFactory<T> extends viewEngine_ComponentFactory<T> {
|
|||
const rootTView = createTView(TViewType.Root, null, null, 1, 0, null, null, null, null, null);
|
||||
const rootLView = createLView(
|
||||
null, rootTView, rootContext, rootFlags, null, null, rendererFactory, hostRenderer,
|
||||
sanitizer, rootViewInjector);
|
||||
sanitizer, rootViewInjector, null);
|
||||
|
||||
// rootView is the parent when bootstrapping
|
||||
// TODO(misko): it looks like we are entering view here but we don't really need to as
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ import {DirectiveDef} from './interfaces/definition';
|
|||
import {isFactory, NO_PARENT_INJECTOR, NodeInjectorFactory, NodeInjectorOffset, RelativeInjectorLocation, RelativeInjectorLocationFlags} from './interfaces/injector';
|
||||
import {AttributeMarker, TContainerNode, TDirectiveHostNode, TElementContainerNode, TElementNode, TNode, TNodeProviderIndexes, TNodeType} from './interfaces/node';
|
||||
import {isComponentDef, isComponentHost} from './interfaces/type_checks';
|
||||
import {DECLARATION_COMPONENT_VIEW, DECLARATION_VIEW, INJECTOR, LView, T_HOST, TData, TVIEW, TView, TViewType} from './interfaces/view';
|
||||
import {DECLARATION_COMPONENT_VIEW, DECLARATION_VIEW, EMBEDDED_VIEW_INJECTOR, FLAGS, INJECTOR, LView, LViewFlags, T_HOST, TData, TVIEW, TView, TViewType} from './interfaces/view';
|
||||
import {assertTNodeType} from './node_assert';
|
||||
import {enterDI, getCurrentTNode, getLView, leaveDI} from './state';
|
||||
import {isNameOnlyAttributeMarker} from './util/attrs_utils';
|
||||
|
|
@ -96,6 +96,9 @@ const BLOOM_BUCKET_BITS = 5;
|
|||
/** Counter used to generate unique IDs for directives. */
|
||||
let nextNgElementId = 0;
|
||||
|
||||
/** Value used when something wasn't found by an injector. */
|
||||
const NOT_FOUND = {};
|
||||
|
||||
/**
|
||||
* Registers this directive as present in its node's injector by flipping the directive's
|
||||
* corresponding bit in the injector's bloom filter.
|
||||
|
|
@ -222,21 +225,8 @@ export function getParentInjectorLocation(tNode: TNode, lView: LView): RelativeI
|
|||
// `LView` hierarchy and look for it. If we walk of the top, that means that there is no parent
|
||||
// `NodeInjector`.
|
||||
while (lViewCursor !== null) {
|
||||
// First determine the `parentTNode` location. The parent pointer differs based on `TView.type`.
|
||||
const tView = lViewCursor[TVIEW];
|
||||
const tViewType = tView.type;
|
||||
if (tViewType === TViewType.Embedded) {
|
||||
ngDevMode &&
|
||||
assertDefined(tView.declTNode, 'Embedded TNodes should have declaration parents.');
|
||||
parentTNode = tView.declTNode;
|
||||
} else if (tViewType === TViewType.Component) {
|
||||
// Components don't have `TView.declTNode` because each instance of component could be
|
||||
// inserted in different location, hence `TView.declTNode` is meaningless.
|
||||
parentTNode = lViewCursor[T_HOST];
|
||||
} else {
|
||||
ngDevMode && assertEqual(tView.type, TViewType.Root, 'Root type expected');
|
||||
parentTNode = null;
|
||||
}
|
||||
parentTNode = getTNodeFromLView(lViewCursor);
|
||||
|
||||
if (parentTNode === null) {
|
||||
// If we have no parent, than we are done.
|
||||
return NO_PARENT_INJECTOR;
|
||||
|
|
@ -406,100 +396,126 @@ export function getOrCreateInjectable<T>(
|
|||
tNode: TDirectiveHostNode|null, lView: LView, token: ProviderToken<T>,
|
||||
flags: InjectFlags = InjectFlags.Default, notFoundValue?: any): T|null {
|
||||
if (tNode !== null) {
|
||||
const bloomHash = bloomHashBitOrFactory(token);
|
||||
// If the ID stored here is a function, this is a special object like ElementRef or TemplateRef
|
||||
// so just call the factory function to create it.
|
||||
if (typeof bloomHash === 'function') {
|
||||
if (!enterDI(lView, tNode, flags)) {
|
||||
// Failed to enter DI, try module injector instead. If a token is injected with the @Host
|
||||
// flag, the module injector is not searched for that token in Ivy.
|
||||
return (flags & InjectFlags.Host) ?
|
||||
notFoundValueOrThrow<T>(notFoundValue, token, flags) :
|
||||
lookupTokenUsingModuleInjector<T>(lView, token, flags, notFoundValue);
|
||||
// If the view or any of its ancestors have an embedded
|
||||
// view injector, we have to look it up there first.
|
||||
if (lView[FLAGS] & LViewFlags.HasEmbeddedViewInjector) {
|
||||
const embeddedInjectorValue =
|
||||
lookupTokenUsingEmbeddedInjector(tNode, lView, token, flags, NOT_FOUND);
|
||||
if (embeddedInjectorValue !== NOT_FOUND) {
|
||||
return embeddedInjectorValue;
|
||||
}
|
||||
try {
|
||||
const value = bloomHash(flags);
|
||||
if (value == null && !(flags & InjectFlags.Optional)) {
|
||||
throwProviderNotFoundError(token);
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
} finally {
|
||||
leaveDI();
|
||||
}
|
||||
|
||||
// Otherwise try the node injector.
|
||||
const value = lookupTokenUsingNodeInjector(tNode, lView, token, flags, NOT_FOUND);
|
||||
if (value !== NOT_FOUND) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
// Finally, fall back to the module injector.
|
||||
return lookupTokenUsingModuleInjector<T>(lView, token, flags, notFoundValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value associated to the given token from the node injector.
|
||||
*
|
||||
* @param tNode The Node where the search for the injector should start
|
||||
* @param lView The `LView` that contains the `tNode`
|
||||
* @param token The token to look for
|
||||
* @param flags Injection flags
|
||||
* @param notFoundValue The value to return when the injection flags is `InjectFlags.Optional`
|
||||
* @returns the value from the injector, `null` when not found, or `notFoundValue` if provided
|
||||
*/
|
||||
function lookupTokenUsingNodeInjector<T>(
|
||||
tNode: TDirectiveHostNode, lView: LView, token: ProviderToken<T>, flags: InjectFlags,
|
||||
notFoundValue?: any) {
|
||||
const bloomHash = bloomHashBitOrFactory(token);
|
||||
// If the ID stored here is a function, this is a special object like ElementRef or TemplateRef
|
||||
// so just call the factory function to create it.
|
||||
if (typeof bloomHash === 'function') {
|
||||
if (!enterDI(lView, tNode, flags)) {
|
||||
// Failed to enter DI, try module injector instead. If a token is injected with the @Host
|
||||
// flag, the module injector is not searched for that token in Ivy.
|
||||
return (flags & InjectFlags.Host) ?
|
||||
notFoundValueOrThrow<T>(notFoundValue, token, flags) :
|
||||
lookupTokenUsingModuleInjector<T>(lView, token, flags, notFoundValue);
|
||||
}
|
||||
try {
|
||||
const value = bloomHash(flags);
|
||||
if (value == null && !(flags & InjectFlags.Optional)) {
|
||||
throwProviderNotFoundError(token);
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
} else if (typeof bloomHash === 'number') {
|
||||
// A reference to the previous injector TView that was found while climbing the element
|
||||
// injector tree. This is used to know if viewProviders can be accessed on the current
|
||||
// injector.
|
||||
let previousTView: TView|null = null;
|
||||
let injectorIndex = getInjectorIndex(tNode, lView);
|
||||
let parentLocation: RelativeInjectorLocation = NO_PARENT_INJECTOR;
|
||||
let hostTElementNode: TNode|null =
|
||||
flags & InjectFlags.Host ? lView[DECLARATION_COMPONENT_VIEW][T_HOST] : null;
|
||||
} finally {
|
||||
leaveDI();
|
||||
}
|
||||
} else if (typeof bloomHash === 'number') {
|
||||
// A reference to the previous injector TView that was found while climbing the element
|
||||
// injector tree. This is used to know if viewProviders can be accessed on the current
|
||||
// injector.
|
||||
let previousTView: TView|null = null;
|
||||
let injectorIndex = getInjectorIndex(tNode, lView);
|
||||
let parentLocation: RelativeInjectorLocation = NO_PARENT_INJECTOR;
|
||||
let hostTElementNode: TNode|null =
|
||||
flags & InjectFlags.Host ? lView[DECLARATION_COMPONENT_VIEW][T_HOST] : null;
|
||||
|
||||
// If we should skip this injector, or if there is no injector on this node, start by
|
||||
// searching the parent injector.
|
||||
if (injectorIndex === -1 || flags & InjectFlags.SkipSelf) {
|
||||
parentLocation = injectorIndex === -1 ? getParentInjectorLocation(tNode, lView) :
|
||||
lView[injectorIndex + NodeInjectorOffset.PARENT];
|
||||
// If we should skip this injector, or if there is no injector on this node, start by
|
||||
// searching the parent injector.
|
||||
if (injectorIndex === -1 || flags & InjectFlags.SkipSelf) {
|
||||
parentLocation = injectorIndex === -1 ? getParentInjectorLocation(tNode, lView) :
|
||||
lView[injectorIndex + NodeInjectorOffset.PARENT];
|
||||
|
||||
if (parentLocation === NO_PARENT_INJECTOR || !shouldSearchParent(flags, false)) {
|
||||
injectorIndex = -1;
|
||||
} else {
|
||||
previousTView = lView[TVIEW];
|
||||
injectorIndex = getParentInjectorIndex(parentLocation);
|
||||
lView = getParentInjectorView(parentLocation, lView);
|
||||
if (parentLocation === NO_PARENT_INJECTOR || !shouldSearchParent(flags, false)) {
|
||||
injectorIndex = -1;
|
||||
} else {
|
||||
previousTView = lView[TVIEW];
|
||||
injectorIndex = getParentInjectorIndex(parentLocation);
|
||||
lView = getParentInjectorView(parentLocation, lView);
|
||||
}
|
||||
}
|
||||
|
||||
// Traverse up the injector tree until we find a potential match or until we know there
|
||||
// *isn't* a match.
|
||||
while (injectorIndex !== -1) {
|
||||
ngDevMode && assertNodeInjector(lView, injectorIndex);
|
||||
|
||||
// Check the current injector. If it matches, see if it contains token.
|
||||
const tView = lView[TVIEW];
|
||||
ngDevMode &&
|
||||
assertTNodeForLView(tView.data[injectorIndex + NodeInjectorOffset.TNODE] as TNode, lView);
|
||||
if (bloomHasToken(bloomHash, injectorIndex, tView.data)) {
|
||||
// At this point, we have an injector which *may* contain the token, so we step through
|
||||
// the providers and directives associated with the injector's corresponding node to get
|
||||
// the instance.
|
||||
const instance: T|null = searchTokensOnInjector<T>(
|
||||
injectorIndex, lView, token, previousTView, flags, hostTElementNode);
|
||||
if (instance !== NOT_FOUND) {
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
||||
// Traverse up the injector tree until we find a potential match or until we know there
|
||||
// *isn't* a match.
|
||||
while (injectorIndex !== -1) {
|
||||
ngDevMode && assertNodeInjector(lView, injectorIndex);
|
||||
|
||||
// Check the current injector. If it matches, see if it contains token.
|
||||
const tView = lView[TVIEW];
|
||||
ngDevMode &&
|
||||
assertTNodeForLView(
|
||||
tView.data[injectorIndex + NodeInjectorOffset.TNODE] as TNode, lView);
|
||||
if (bloomHasToken(bloomHash, injectorIndex, tView.data)) {
|
||||
// At this point, we have an injector which *may* contain the token, so we step through
|
||||
// the providers and directives associated with the injector's corresponding node to get
|
||||
// the instance.
|
||||
const instance: T|null = searchTokensOnInjector<T>(
|
||||
injectorIndex, lView, token, previousTView, flags, hostTElementNode);
|
||||
if (instance !== NOT_FOUND) {
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
parentLocation = lView[injectorIndex + NodeInjectorOffset.PARENT];
|
||||
if (parentLocation !== NO_PARENT_INJECTOR &&
|
||||
shouldSearchParent(
|
||||
flags,
|
||||
lView[TVIEW].data[injectorIndex + NodeInjectorOffset.TNODE] === hostTElementNode) &&
|
||||
bloomHasToken(bloomHash, injectorIndex, lView)) {
|
||||
// The def wasn't found anywhere on this node, so it was a false positive.
|
||||
// Traverse up the tree and continue searching.
|
||||
previousTView = tView;
|
||||
injectorIndex = getParentInjectorIndex(parentLocation);
|
||||
lView = getParentInjectorView(parentLocation, lView);
|
||||
} else {
|
||||
// If we should not search parent OR If the ancestor bloom filter value does not have the
|
||||
// bit corresponding to the directive we can give up on traversing up to find the specific
|
||||
// injector.
|
||||
injectorIndex = -1;
|
||||
}
|
||||
parentLocation = lView[injectorIndex + NodeInjectorOffset.PARENT];
|
||||
if (parentLocation !== NO_PARENT_INJECTOR &&
|
||||
shouldSearchParent(
|
||||
flags,
|
||||
lView[TVIEW].data[injectorIndex + NodeInjectorOffset.TNODE] === hostTElementNode) &&
|
||||
bloomHasToken(bloomHash, injectorIndex, lView)) {
|
||||
// The def wasn't found anywhere on this node, so it was a false positive.
|
||||
// Traverse up the tree and continue searching.
|
||||
previousTView = tView;
|
||||
injectorIndex = getParentInjectorIndex(parentLocation);
|
||||
lView = getParentInjectorView(parentLocation, lView);
|
||||
} else {
|
||||
// If we should not search parent OR If the ancestor bloom filter value does not have the
|
||||
// bit corresponding to the directive we can give up on traversing up to find the specific
|
||||
// injector.
|
||||
injectorIndex = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return lookupTokenUsingModuleInjector<T>(lView, token, flags, notFoundValue);
|
||||
}
|
||||
|
||||
const NOT_FOUND = {};
|
||||
|
||||
export function createNodeInjector(): Injector {
|
||||
return new NodeInjector(getCurrentTNode()! as TDirectiveHostNode, getLView()) as any;
|
||||
return notFoundValue;
|
||||
}
|
||||
|
||||
function searchTokensOnInjector<T>(
|
||||
|
|
@ -693,6 +709,11 @@ export class NodeInjector implements Injector {
|
|||
}
|
||||
}
|
||||
|
||||
/** Creates a `NodeInjector` for the current node. */
|
||||
export function createNodeInjector(): Injector {
|
||||
return new NodeInjector(getCurrentTNode()! as TDirectiveHostNode, getLView()) as any;
|
||||
}
|
||||
|
||||
/**
|
||||
* @codeGenApi
|
||||
*/
|
||||
|
|
@ -736,3 +757,83 @@ function getFactoryOf<T>(type: Type<any>): ((type?: Type<T>) => T | null)|null {
|
|||
}
|
||||
return getFactoryDef<T>(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a value from the closest embedded or node injector.
|
||||
*
|
||||
* @param tNode The Node where the search for the injector should start
|
||||
* @param lView The `LView` that contains the `tNode`
|
||||
* @param token The token to look for
|
||||
* @param flags Injection flags
|
||||
* @param notFoundValue The value to return when the injection flags is `InjectFlags.Optional`
|
||||
* @returns the value from the injector, `null` when not found, or `notFoundValue` if provided
|
||||
*/
|
||||
function lookupTokenUsingEmbeddedInjector<T>(
|
||||
tNode: TDirectiveHostNode, lView: LView, token: ProviderToken<T>, flags: InjectFlags,
|
||||
notFoundValue?: any) {
|
||||
let currentTNode: TDirectiveHostNode|null = tNode;
|
||||
let currentLView: LView|null = lView;
|
||||
|
||||
// When an LView with an embedded view injector is inserted, it'll likely be interlaced with
|
||||
// nodes who may have injectors (e.g. node injector -> embedded view injector -> node injector).
|
||||
// Since the bloom filters for the node injectors have already been constructed and we don't
|
||||
// have a way of extracting the records from an injector, the only way to maintain the correct
|
||||
// hierarchy when resolving the value is to walk it node-by-node while attempting to resolve
|
||||
// the token at each level.
|
||||
while (currentTNode !== null && currentLView !== null &&
|
||||
(currentLView[FLAGS] & LViewFlags.HasEmbeddedViewInjector) &&
|
||||
!(currentLView[FLAGS] & LViewFlags.IsRoot)) {
|
||||
ngDevMode && assertTNodeForLView(currentTNode, currentLView);
|
||||
|
||||
// Note that this lookup on the node injector is using the `Self` flag, because
|
||||
// we don't want the node injector to look at any parent injectors since we
|
||||
// may hit the embedded view injector first.
|
||||
const nodeInjectorValue = lookupTokenUsingNodeInjector(
|
||||
currentTNode, currentLView, token, flags | InjectFlags.Self, NOT_FOUND);
|
||||
if (nodeInjectorValue !== NOT_FOUND) {
|
||||
return nodeInjectorValue;
|
||||
}
|
||||
|
||||
// Has an explicit type due to a TS bug: https://github.com/microsoft/TypeScript/issues/33191
|
||||
let parentTNode: TElementNode|TContainerNode|null = currentTNode.parent;
|
||||
|
||||
// `TNode.parent` includes the parent within the current view only. If it doesn't exist,
|
||||
// it means that we've hit the view boundary and we need to go up to the next view.
|
||||
if (!parentTNode) {
|
||||
// Before we go to the next LView, check if the token exists on the current embedded injector.
|
||||
const embeddedViewInjector = currentLView[EMBEDDED_VIEW_INJECTOR];
|
||||
if (embeddedViewInjector) {
|
||||
const embeddedViewInjectorValue = embeddedViewInjector.get(token, NOT_FOUND as T, flags);
|
||||
if (embeddedViewInjectorValue !== NOT_FOUND) {
|
||||
return embeddedViewInjectorValue;
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise keep going up the tree.
|
||||
parentTNode = getTNodeFromLView(currentLView);
|
||||
currentLView = currentLView[DECLARATION_VIEW];
|
||||
}
|
||||
|
||||
currentTNode = parentTNode;
|
||||
}
|
||||
|
||||
return notFoundValue;
|
||||
}
|
||||
|
||||
/** Gets the TNode associated with an LView inside of the declaration view. */
|
||||
function getTNodeFromLView(lView: LView): TElementNode|TElementContainerNode|null {
|
||||
const tView = lView[TVIEW];
|
||||
const tViewType = tView.type;
|
||||
|
||||
// The parent pointer differs based on `TView.type`.
|
||||
if (tViewType === TViewType.Embedded) {
|
||||
ngDevMode && assertDefined(tView.declTNode, 'Embedded TNodes should have declaration parents.');
|
||||
return tView.declTNode as TElementContainerNode;
|
||||
} else if (tViewType === TViewType.Component) {
|
||||
// Components don't have `TView.declTNode` because each instance of component could be
|
||||
// inserted in different location, hence `TView.declTNode` is meaningless.
|
||||
return lView[T_HOST] as TElementNode;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ import {isProceduralRenderer, Renderer3, RendererFactory3} from '../interfaces/r
|
|||
import {RComment, RElement, RNode, RText} from '../interfaces/renderer_dom';
|
||||
import {SanitizerFn} from '../interfaces/sanitization';
|
||||
import {isComponentDef, isComponentHost, isContentQueryHost, isRootView} from '../interfaces/type_checks';
|
||||
import {CHILD_HEAD, CHILD_TAIL, CLEANUP, CONTEXT, DECLARATION_COMPONENT_VIEW, DECLARATION_VIEW, FLAGS, HEADER_OFFSET, HOST, HostBindingOpCodes, ID, InitPhaseState, INJECTOR, LView, LViewFlags, NEXT, PARENT, RENDERER, RENDERER_FACTORY, RootContext, RootContextFlags, SANITIZER, T_HOST, TData, TRANSPLANTED_VIEWS_TO_REFRESH, TVIEW, TView, TViewType} from '../interfaces/view';
|
||||
import {CHILD_HEAD, CHILD_TAIL, CLEANUP, CONTEXT, DECLARATION_COMPONENT_VIEW, DECLARATION_VIEW, EMBEDDED_VIEW_INJECTOR, FLAGS, HEADER_OFFSET, HOST, HostBindingOpCodes, ID, InitPhaseState, INJECTOR, LView, LViewFlags, NEXT, PARENT, RENDERER, RENDERER_FACTORY, RootContext, RootContextFlags, SANITIZER, T_HOST, TData, TRANSPLANTED_VIEWS_TO_REFRESH, TVIEW, TView, TViewType} from '../interfaces/view';
|
||||
import {assertPureTNodeType, assertTNodeType} from '../node_assert';
|
||||
import {updateTextNode} from '../node_manipulation';
|
||||
import {isInlineTemplate, isNodeMatchingSelectorList} from '../node_selector_matcher';
|
||||
|
|
@ -126,11 +126,16 @@ function renderChildComponents(hostLView: LView, components: number[]): void {
|
|||
export function createLView<T>(
|
||||
parentLView: LView|null, tView: TView, context: T|null, flags: LViewFlags, host: RElement|null,
|
||||
tHostNode: TNode|null, rendererFactory: RendererFactory3|null, renderer: Renderer3|null,
|
||||
sanitizer: Sanitizer|null, injector: Injector|null): LView {
|
||||
sanitizer: Sanitizer|null, injector: Injector|null,
|
||||
embeddedViewInjector: Injector|null): LView {
|
||||
const lView =
|
||||
ngDevMode ? cloneToLViewFromTViewBlueprint(tView) : tView.blueprint.slice() as LView;
|
||||
lView[HOST] = host;
|
||||
lView[FLAGS] = flags | LViewFlags.CreationMode | LViewFlags.Attached | LViewFlags.FirstLViewPass;
|
||||
if (embeddedViewInjector !== null ||
|
||||
(parentLView && (parentLView[FLAGS] & LViewFlags.HasEmbeddedViewInjector))) {
|
||||
lView[FLAGS] |= LViewFlags.HasEmbeddedViewInjector;
|
||||
}
|
||||
resetPreOrderHookFlags(lView);
|
||||
ngDevMode && tView.declTNode && parentLView && assertTNodeForLView(tView.declTNode, parentLView);
|
||||
lView[PARENT] = lView[DECLARATION_VIEW] = parentLView;
|
||||
|
|
@ -143,6 +148,7 @@ export function createLView<T>(
|
|||
lView[INJECTOR as any] = injector || parentLView && parentLView[INJECTOR] || null;
|
||||
lView[T_HOST] = tHostNode;
|
||||
lView[ID] = getUniqueLViewId();
|
||||
lView[EMBEDDED_VIEW_INJECTOR as any] = embeddedViewInjector;
|
||||
ngDevMode &&
|
||||
assertEqual(
|
||||
tView.type == TViewType.Embedded ? parentLView !== null : true, true,
|
||||
|
|
@ -1499,7 +1505,7 @@ function addComponentLogic<T>(lView: LView, hostTNode: TElementNode, def: Compon
|
|||
createLView(
|
||||
lView, tView, null, def.onPush ? LViewFlags.Dirty : LViewFlags.CheckAlways, native,
|
||||
hostTNode as TElementNode, rendererFactory, rendererFactory.createRenderer(native, def),
|
||||
null, null));
|
||||
null, null, null));
|
||||
|
||||
// Component view will always be created before any injected LContainers,
|
||||
// so this is a regular element, wrap it with the component view
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ export const DECLARATION_LCONTAINER = 17;
|
|||
export const PREORDER_HOOK_FLAGS = 18;
|
||||
export const QUERIES = 19;
|
||||
export const ID = 20;
|
||||
export const EMBEDDED_VIEW_INJECTOR = 21;
|
||||
/**
|
||||
* Size of LView's header. Necessary to adjust for it when setting slots.
|
||||
*
|
||||
|
|
@ -56,7 +57,7 @@ export const ID = 20;
|
|||
* instruction index into `LView` index. All other indexes should be in the `LView` index space and
|
||||
* there should be no need to refer to `HEADER_OFFSET` anywhere else.
|
||||
*/
|
||||
export const HEADER_OFFSET = 21;
|
||||
export const HEADER_OFFSET = 22;
|
||||
|
||||
|
||||
// This interface replaces the real LView interface if it is an arg or a
|
||||
|
|
@ -331,6 +332,12 @@ export interface LView extends Array<any> {
|
|||
|
||||
/** Unique ID of the view. Used for `__ngContext__` lookups in the `LView` registry. */
|
||||
[ID]: number;
|
||||
|
||||
/**
|
||||
* Optional injector assigned to embedded views that takes
|
||||
* precedence over the element and module injectors.
|
||||
*/
|
||||
readonly[EMBEDDED_VIEW_INJECTOR]: Injector|null;
|
||||
}
|
||||
|
||||
/** Flags associated with an LView (saved in LView[FLAGS]) */
|
||||
|
|
@ -396,12 +403,15 @@ export const enum LViewFlags {
|
|||
*/
|
||||
RefreshTransplantedView = 0b0010000000000,
|
||||
|
||||
/** Indicates that the view **or any of its ancestors** have an embedded view injector. */
|
||||
HasEmbeddedViewInjector = 0b0100000000000,
|
||||
|
||||
/**
|
||||
* Index of the current init phase on last 21 bits
|
||||
*/
|
||||
IndexWithinInitPhaseIncrementer = 0b0100000000000,
|
||||
IndexWithinInitPhaseShift = 11,
|
||||
IndexWithinInitPhaseReset = 0b0011111111111,
|
||||
IndexWithinInitPhaseIncrementer = 0b1000000000000,
|
||||
IndexWithinInitPhaseShift = 12,
|
||||
IndexWithinInitPhaseReset = 0b001111111111111,
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -3546,4 +3546,823 @@ describe('di', () => {
|
|||
TestBed.configureTestingModule({declarations: [App]});
|
||||
expect(() => TestBed.createComponent(App)).toThrowError(/NullInjectorError/);
|
||||
});
|
||||
|
||||
describe('injector when creating embedded view', () => {
|
||||
const token = new InjectionToken<string>('greeting');
|
||||
|
||||
@Directive({selector: 'menu-trigger'})
|
||||
class MenuTrigger {
|
||||
@Input('triggerFor') menu!: TemplateRef<unknown>;
|
||||
|
||||
constructor(private viewContainerRef: ViewContainerRef) {}
|
||||
|
||||
open(injector: Injector|undefined) {
|
||||
this.viewContainerRef.createEmbeddedView(this.menu, undefined, {injector});
|
||||
}
|
||||
}
|
||||
|
||||
it('should be able to provide an injection token through a custom injector', () => {
|
||||
@Directive({selector: 'menu'})
|
||||
class Menu {
|
||||
constructor(@Inject(token) public tokenValue: string) {}
|
||||
}
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<menu-trigger [triggerFor]="menuTemplate"></menu-trigger>
|
||||
<ng-template #menuTemplate>
|
||||
<menu></menu>
|
||||
</ng-template>
|
||||
`
|
||||
})
|
||||
class App {
|
||||
@ViewChild(MenuTrigger) trigger!: MenuTrigger;
|
||||
@ViewChild(Menu) menu!: Menu;
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({declarations: [App, MenuTrigger, Menu]});
|
||||
const injector = Injector.create({providers: [{provide: token, useValue: 'hello'}]});
|
||||
const fixture = TestBed.createComponent(App);
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.componentInstance.trigger.open(injector);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(fixture.componentInstance.menu.tokenValue).toBe('hello');
|
||||
});
|
||||
|
||||
it('should be able to provide an injection token to a nested template through a custom injector',
|
||||
() => {
|
||||
@Directive({selector: 'menu'})
|
||||
class Menu {
|
||||
constructor(@Inject(token) public tokenValue: string) {}
|
||||
}
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<menu-trigger #outerTrigger [triggerFor]="outerTemplate"></menu-trigger>
|
||||
<ng-template #outerTemplate>
|
||||
<menu></menu>
|
||||
|
||||
<menu-trigger #innerTrigger [triggerFor]="innerTemplate"></menu-trigger>
|
||||
<ng-template #innerTemplate>
|
||||
<menu #innerMenu></menu>
|
||||
</ng-template>
|
||||
</ng-template>
|
||||
`
|
||||
})
|
||||
class App {
|
||||
@ViewChild('outerTrigger', {read: MenuTrigger}) outerTrigger!: MenuTrigger;
|
||||
@ViewChild('innerTrigger', {read: MenuTrigger}) innerTrigger!: MenuTrigger;
|
||||
@ViewChild('innerMenu', {read: Menu}) innerMenu!: Menu;
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({declarations: [App, MenuTrigger, Menu]});
|
||||
const fixture = TestBed.createComponent(App);
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.componentInstance.outerTrigger.open(
|
||||
Injector.create({providers: [{provide: token, useValue: 'hello'}]}));
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.componentInstance.innerTrigger.open(undefined);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(fixture.componentInstance.innerMenu.tokenValue).toBe('hello');
|
||||
});
|
||||
|
||||
it('should be able to resolve a token from a custom grandparent injector if the token is not provided in the parent',
|
||||
() => {
|
||||
@Directive({selector: 'menu'})
|
||||
class Menu {
|
||||
constructor(@Inject(token) public tokenValue: string) {}
|
||||
}
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<menu-trigger #grandparentTrigger [triggerFor]="grandparentTemplate"></menu-trigger>
|
||||
<ng-template #grandparentTemplate>
|
||||
<menu></menu>
|
||||
|
||||
<menu-trigger #parentTrigger [triggerFor]="parentTemplate"></menu-trigger>
|
||||
<ng-template #parentTemplate>
|
||||
<menu></menu>
|
||||
|
||||
<menu-trigger #childTrigger [triggerFor]="childTemplate"></menu-trigger>
|
||||
<ng-template #childTemplate>
|
||||
<menu #childMenu></menu>
|
||||
</ng-template>
|
||||
</ng-template>
|
||||
</ng-template>
|
||||
`
|
||||
})
|
||||
class App {
|
||||
@ViewChild('grandparentTrigger', {read: MenuTrigger}) grandparentTrigger!: MenuTrigger;
|
||||
@ViewChild('parentTrigger', {read: MenuTrigger}) parentTrigger!: MenuTrigger;
|
||||
@ViewChild('childTrigger', {read: MenuTrigger}) childTrigger!: MenuTrigger;
|
||||
@ViewChild('childMenu', {read: Menu}) childMenu!: Menu;
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({declarations: [App, MenuTrigger, Menu]});
|
||||
const fixture = TestBed.createComponent(App);
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.componentInstance.grandparentTrigger.open(
|
||||
Injector.create({providers: [{provide: token, useValue: 'hello'}]}));
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.componentInstance.parentTrigger.open(Injector.create({providers: []}));
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.componentInstance.childTrigger.open(undefined);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(fixture.componentInstance.childMenu.tokenValue).toBe('hello');
|
||||
});
|
||||
|
||||
it('should resolve value from node injector if it is lower than embedded view injector', () => {
|
||||
@Directive({selector: 'menu'})
|
||||
class Menu {
|
||||
constructor(@Inject(token) public tokenValue: string) {}
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'wrapper',
|
||||
providers: [{provide: token, useValue: 'hello from wrapper'}],
|
||||
template: `
|
||||
<menu-trigger [triggerFor]="menuTemplate"></menu-trigger>
|
||||
<ng-template #menuTemplate>
|
||||
<menu></menu>
|
||||
</ng-template>
|
||||
`
|
||||
})
|
||||
class Wrapper {
|
||||
@ViewChild(MenuTrigger) trigger!: MenuTrigger;
|
||||
@ViewChild(Menu) menu!: Menu;
|
||||
}
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<menu-trigger [triggerFor]="menuTemplate"></menu-trigger>
|
||||
<ng-template #menuTemplate>
|
||||
<wrapper></wrapper>
|
||||
</ng-template>
|
||||
`
|
||||
})
|
||||
class App {
|
||||
@ViewChild(MenuTrigger) trigger!: MenuTrigger;
|
||||
@ViewChild(Wrapper) wrapper!: Wrapper;
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({declarations: [App, MenuTrigger, Menu, Wrapper]});
|
||||
const fixture = TestBed.createComponent(App);
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.componentInstance.trigger.open(
|
||||
Injector.create({providers: [{provide: token, useValue: 'hello from injector'}]}));
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.componentInstance.wrapper.trigger.open(undefined);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(fixture.componentInstance.wrapper.menu.tokenValue).toBe('hello from wrapper');
|
||||
});
|
||||
|
||||
it('should be able to inject a value provided at the module level', () => {
|
||||
@Directive({selector: 'menu'})
|
||||
class Menu {
|
||||
constructor(@Inject(token) public tokenValue: string) {}
|
||||
}
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<menu-trigger [triggerFor]="menuTemplate"></menu-trigger>
|
||||
<ng-template #menuTemplate>
|
||||
<menu></menu>
|
||||
</ng-template>
|
||||
`
|
||||
})
|
||||
class App {
|
||||
@ViewChild(MenuTrigger) trigger!: MenuTrigger;
|
||||
@ViewChild(Menu) menu!: Menu;
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
declarations: [App, MenuTrigger, Menu],
|
||||
exports: [App, MenuTrigger, Menu],
|
||||
providers: [{provide: token, useValue: 'hello'}]
|
||||
})
|
||||
class Module {
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({imports: [Module]});
|
||||
const injector = Injector.create({providers: []});
|
||||
const fixture = TestBed.createComponent(App);
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.componentInstance.trigger.open(injector);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(fixture.componentInstance.menu.tokenValue).toBe('hello');
|
||||
});
|
||||
|
||||
it('should have value from custom injector take precedence over module injector', () => {
|
||||
@Directive({selector: 'menu'})
|
||||
class Menu {
|
||||
constructor(@Inject(token) public tokenValue: string) {}
|
||||
}
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<menu-trigger [triggerFor]="menuTemplate"></menu-trigger>
|
||||
<ng-template #menuTemplate>
|
||||
<menu></menu>
|
||||
</ng-template>
|
||||
`
|
||||
})
|
||||
class App {
|
||||
@ViewChild(MenuTrigger) trigger!: MenuTrigger;
|
||||
@ViewChild(Menu) menu!: Menu;
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
declarations: [App, MenuTrigger, Menu],
|
||||
exports: [App, MenuTrigger, Menu],
|
||||
providers: [{provide: token, useValue: 'hello from module'}]
|
||||
})
|
||||
class Module {
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({imports: [Module]});
|
||||
const injector =
|
||||
Injector.create({providers: [{provide: token, useValue: 'hello from injector'}]});
|
||||
const fixture = TestBed.createComponent(App);
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.componentInstance.trigger.open(injector);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(fixture.componentInstance.menu.tokenValue).toBe('hello from injector');
|
||||
});
|
||||
|
||||
it('should have value from custom injector take precedence over parent injector', () => {
|
||||
@Directive({selector: 'menu'})
|
||||
class Menu {
|
||||
constructor(@Inject(token) public tokenValue: string) {}
|
||||
}
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<menu-trigger [triggerFor]="menuTemplate"></menu-trigger>
|
||||
<ng-template #menuTemplate>
|
||||
<menu></menu>
|
||||
</ng-template>
|
||||
`,
|
||||
providers: [{provide: token, useValue: 'hello from parent'}]
|
||||
})
|
||||
class App {
|
||||
@ViewChild(MenuTrigger) trigger!: MenuTrigger;
|
||||
@ViewChild(Menu) menu!: Menu;
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
declarations: [App, MenuTrigger, Menu],
|
||||
exports: [App, MenuTrigger, Menu],
|
||||
})
|
||||
class Module {
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({imports: [Module]});
|
||||
const injector =
|
||||
Injector.create({providers: [{provide: token, useValue: 'hello from injector'}]});
|
||||
const fixture = TestBed.createComponent(App);
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.componentInstance.trigger.open(injector);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(fixture.componentInstance.menu.tokenValue).toBe('hello from injector');
|
||||
});
|
||||
|
||||
it('should be able to inject built-in tokens when a custom injector is provided', () => {
|
||||
@Directive({selector: 'menu'})
|
||||
class Menu {
|
||||
constructor(public elementRef: ElementRef, public changeDetectorRef: ChangeDetectorRef) {}
|
||||
}
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<menu-trigger [triggerFor]="menuTemplate"></menu-trigger>
|
||||
<ng-template #menuTemplate>
|
||||
<menu></menu>
|
||||
</ng-template>
|
||||
`
|
||||
})
|
||||
class App {
|
||||
@ViewChild(MenuTrigger) trigger!: MenuTrigger;
|
||||
@ViewChild(Menu) menu!: Menu;
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({declarations: [App, MenuTrigger, Menu]});
|
||||
const injector = Injector.create({providers: []});
|
||||
const fixture = TestBed.createComponent(App);
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.componentInstance.trigger.open(injector);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(fixture.componentInstance.menu.elementRef.nativeElement)
|
||||
.toBe(fixture.nativeElement.querySelector('menu'));
|
||||
expect(fixture.componentInstance.menu.changeDetectorRef).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should have value from parent component injector take precedence over module injector',
|
||||
() => {
|
||||
@Directive({selector: 'menu'})
|
||||
class Menu {
|
||||
constructor(@Inject(token) public tokenValue: string) {}
|
||||
}
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<menu-trigger [triggerFor]="menuTemplate"></menu-trigger>
|
||||
<ng-template #menuTemplate>
|
||||
<menu></menu>
|
||||
</ng-template>
|
||||
`,
|
||||
providers: [{provide: token, useValue: 'hello from parent'}]
|
||||
})
|
||||
class App {
|
||||
@ViewChild(MenuTrigger) trigger!: MenuTrigger;
|
||||
@ViewChild(Menu) menu!: Menu;
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
declarations: [App, MenuTrigger, Menu],
|
||||
exports: [App, MenuTrigger, Menu],
|
||||
providers: [{provide: token, useValue: 'hello from module'}]
|
||||
})
|
||||
class Module {
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({imports: [Module]});
|
||||
const injector = Injector.create({providers: []});
|
||||
const fixture = TestBed.createComponent(App);
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.componentInstance.trigger.open(injector);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(fixture.componentInstance.menu.tokenValue).toBe('hello from parent');
|
||||
});
|
||||
|
||||
it('should be able to inject an injectable with dependencies', () => {
|
||||
@Injectable()
|
||||
class Greeter {
|
||||
constructor(@Inject(token) private tokenValue: string) {}
|
||||
|
||||
greet() {
|
||||
return `hello from ${this.tokenValue}`;
|
||||
}
|
||||
}
|
||||
|
||||
@Directive({selector: 'menu'})
|
||||
class Menu {
|
||||
constructor(public greeter: Greeter) {}
|
||||
}
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<menu-trigger [triggerFor]="menuTemplate"></menu-trigger>
|
||||
<ng-template #menuTemplate>
|
||||
<menu></menu>
|
||||
</ng-template>
|
||||
`
|
||||
})
|
||||
class App {
|
||||
@ViewChild(MenuTrigger) trigger!: MenuTrigger;
|
||||
@ViewChild(Menu) menu!: Menu;
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
declarations: [App, MenuTrigger, Menu],
|
||||
exports: [App, MenuTrigger, Menu],
|
||||
providers: [{provide: token, useValue: 'module'}]
|
||||
})
|
||||
class Module {
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({imports: [Module]});
|
||||
const injector = Injector.create({
|
||||
providers: [
|
||||
{provide: Greeter, useClass: Greeter},
|
||||
{provide: token, useValue: 'injector'},
|
||||
]
|
||||
});
|
||||
const fixture = TestBed.createComponent(App);
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.componentInstance.trigger.open(injector);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(fixture.componentInstance.menu.greeter.greet()).toBe('hello from injector');
|
||||
});
|
||||
|
||||
it('should be able to inject a value from a grandparent component when a custom injector is provided',
|
||||
() => {
|
||||
@Directive({selector: 'menu'})
|
||||
class Menu {
|
||||
constructor(@Inject(token) public tokenValue: string) {}
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'parent',
|
||||
template: `
|
||||
<menu-trigger [triggerFor]="menuTemplate"></menu-trigger>
|
||||
<ng-template #menuTemplate>
|
||||
<menu></menu>
|
||||
</ng-template>
|
||||
`
|
||||
})
|
||||
class Parent {
|
||||
@ViewChild(MenuTrigger) trigger!: MenuTrigger;
|
||||
@ViewChild(Menu) menu!: Menu;
|
||||
}
|
||||
|
||||
@Component({
|
||||
template: '<parent></parent>',
|
||||
providers: [{provide: token, useValue: 'hello from grandparent'}]
|
||||
})
|
||||
class GrandParent {
|
||||
@ViewChild(Parent) parent!: Parent;
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({declarations: [GrandParent, Parent, MenuTrigger, Menu]});
|
||||
const injector = Injector.create({providers: []});
|
||||
const fixture = TestBed.createComponent(GrandParent);
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.componentInstance.parent.trigger.open(injector);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(fixture.componentInstance.parent.menu.tokenValue).toBe('hello from grandparent');
|
||||
});
|
||||
|
||||
it('should be able to use a custom injector when created through TemplateRef', () => {
|
||||
let injectedValue: string|undefined;
|
||||
|
||||
@Directive({selector: 'menu'})
|
||||
class Menu {
|
||||
constructor(@Inject(token) tokenValue: string) {
|
||||
injectedValue = tokenValue;
|
||||
}
|
||||
}
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<ng-template>
|
||||
<menu></menu>
|
||||
</ng-template>
|
||||
`
|
||||
})
|
||||
class App {
|
||||
@ViewChild(TemplateRef) template!: TemplateRef<unknown>;
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
declarations: [App, Menu],
|
||||
exports: [App, Menu],
|
||||
providers: [{provide: token, useValue: 'hello from module'}]
|
||||
})
|
||||
class Module {
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({imports: [Module]});
|
||||
const injector =
|
||||
Injector.create({providers: [{provide: token, useValue: 'hello from injector'}]});
|
||||
const fixture = TestBed.createComponent(App);
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.componentInstance.template.createEmbeddedView({}, injector);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(injectedValue).toBe('hello from injector');
|
||||
});
|
||||
|
||||
it('should use a custom injector when the view is created outside of the declaration view',
|
||||
() => {
|
||||
const declarerToken = new InjectionToken<string>('declarerToken');
|
||||
const creatorToken = new InjectionToken<string>('creatorToken');
|
||||
|
||||
@Directive({selector: 'menu'})
|
||||
class Menu {
|
||||
constructor(
|
||||
@Inject(token) public tokenValue: string,
|
||||
@Optional() @Inject(declarerToken) public declarerTokenValue: string,
|
||||
@Optional() @Inject(creatorToken) public creatorTokenValue: string) {}
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'declarer',
|
||||
template: '<ng-template><menu></menu></ng-template>',
|
||||
providers: [{provide: declarerToken, useValue: 'hello from declarer'}]
|
||||
})
|
||||
class Declarer {
|
||||
@ViewChild(Menu) menu!: Menu;
|
||||
@ViewChild(TemplateRef) template!: TemplateRef<unknown>;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'creator',
|
||||
template: '<menu-trigger></menu-trigger>',
|
||||
providers: [{provide: creatorToken, useValue: 'hello from creator'}]
|
||||
})
|
||||
class Creator {
|
||||
@ViewChild(MenuTrigger) trigger!: MenuTrigger;
|
||||
}
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<declarer></declarer>
|
||||
<creator></creator>
|
||||
`
|
||||
})
|
||||
class App {
|
||||
@ViewChild(Declarer) declarer!: Declarer;
|
||||
@ViewChild(Creator) creator!: Creator;
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule(
|
||||
{declarations: [App, MenuTrigger, Menu, Declarer, Creator]});
|
||||
const injector = Injector.create({providers: [{provide: token, useValue: 'hello'}]});
|
||||
const fixture = TestBed.createComponent(App);
|
||||
fixture.detectChanges();
|
||||
const {declarer, creator} = fixture.componentInstance;
|
||||
|
||||
creator.trigger.menu = declarer.template;
|
||||
creator.trigger.open(injector);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(declarer.menu.tokenValue).toBe('hello');
|
||||
expect(declarer.menu.declarerTokenValue).toBe('hello from declarer');
|
||||
expect(declarer.menu.creatorTokenValue).toBeNull();
|
||||
});
|
||||
|
||||
it('should give precedence to value provided lower in the tree over custom injector', () => {
|
||||
@Directive({selector: 'menu'})
|
||||
class Menu {
|
||||
constructor(@Inject(token) public tokenValue: string) {}
|
||||
}
|
||||
|
||||
@Directive({
|
||||
selector: '[provide-token]',
|
||||
providers: [{provide: token, useValue: 'hello from directive'}]
|
||||
})
|
||||
class ProvideToken {
|
||||
}
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<menu-trigger [triggerFor]="menuTemplate"></menu-trigger>
|
||||
<ng-template #menuTemplate>
|
||||
<section>
|
||||
<div provide-token>
|
||||
<menu></menu>
|
||||
</div>
|
||||
</section>
|
||||
</ng-template>
|
||||
`,
|
||||
providers: [{provide: token, useValue: 'hello from parent'}]
|
||||
})
|
||||
class App {
|
||||
@ViewChild(MenuTrigger) trigger!: MenuTrigger;
|
||||
@ViewChild(Menu) menu!: Menu;
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
declarations: [App, MenuTrigger, Menu, ProvideToken],
|
||||
exports: [App, MenuTrigger, Menu, ProvideToken],
|
||||
})
|
||||
class Module {
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({imports: [Module]});
|
||||
const injector =
|
||||
Injector.create({providers: [{provide: token, useValue: 'hello from injector'}]});
|
||||
const fixture = TestBed.createComponent(App);
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.componentInstance.trigger.open(injector);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(fixture.componentInstance.menu.tokenValue).toBe('hello from directive');
|
||||
});
|
||||
|
||||
it('should give precedence to value provided in custom injector over one provided higher',
|
||||
() => {
|
||||
@Directive({selector: 'menu'})
|
||||
class Menu {
|
||||
constructor(@Inject(token) public tokenValue: string) {}
|
||||
}
|
||||
|
||||
@Directive({
|
||||
selector: '[provide-token]',
|
||||
providers: [{provide: token, useValue: 'hello from directive'}]
|
||||
})
|
||||
class ProvideToken {
|
||||
}
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<menu-trigger [triggerFor]="menuTemplate"></menu-trigger>
|
||||
<div provide-token>
|
||||
<ng-template #menuTemplate>
|
||||
<menu></menu>
|
||||
</ng-template>
|
||||
</div>
|
||||
`,
|
||||
providers: [{provide: token, useValue: 'hello from parent'}]
|
||||
})
|
||||
class App {
|
||||
@ViewChild(MenuTrigger) trigger!: MenuTrigger;
|
||||
@ViewChild(Menu) menu!: Menu;
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
declarations: [App, MenuTrigger, Menu, ProvideToken],
|
||||
exports: [App, MenuTrigger, Menu, ProvideToken],
|
||||
})
|
||||
class Module {
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({imports: [Module]});
|
||||
const injector =
|
||||
Injector.create({providers: [{provide: token, useValue: 'hello from injector'}]});
|
||||
const fixture = TestBed.createComponent(App);
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.componentInstance.trigger.open(injector);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(fixture.componentInstance.menu.tokenValue).toBe('hello from injector');
|
||||
});
|
||||
|
||||
it('should give precedence to value provided lower in the tree over custom injector when crossing view boundaries',
|
||||
() => {
|
||||
@Directive({selector: 'menu'})
|
||||
class Menu {
|
||||
constructor(@Inject(token) public tokenValue: string) {}
|
||||
}
|
||||
|
||||
@Directive({
|
||||
selector: '[provide-token]',
|
||||
providers: [{provide: token, useValue: 'hello from directive'}]
|
||||
})
|
||||
class ProvideToken {
|
||||
}
|
||||
|
||||
@Component({selector: 'wrapper', template: `<div><menu></menu></div>`})
|
||||
class Wrapper {
|
||||
@ViewChild(Menu) menu!: Menu;
|
||||
}
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<menu-trigger [triggerFor]="menuTemplate"></menu-trigger>
|
||||
<ng-template #menuTemplate>
|
||||
<section provide-token>
|
||||
<wrapper></wrapper>
|
||||
</section>
|
||||
</ng-template>
|
||||
`,
|
||||
providers: [{provide: token, useValue: 'hello from parent'}]
|
||||
})
|
||||
class App {
|
||||
@ViewChild(MenuTrigger) trigger!: MenuTrigger;
|
||||
@ViewChild(Wrapper) wrapper!: Wrapper;
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
declarations: [App, MenuTrigger, Menu, ProvideToken, Wrapper],
|
||||
exports: [App, MenuTrigger, Menu, ProvideToken, Wrapper],
|
||||
})
|
||||
class Module {
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({imports: [Module]});
|
||||
const injector =
|
||||
Injector.create({providers: [{provide: token, useValue: 'hello from injector'}]});
|
||||
const fixture = TestBed.createComponent(App);
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.componentInstance.trigger.open(injector);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(fixture.componentInstance.wrapper.menu.tokenValue).toBe('hello from directive');
|
||||
});
|
||||
|
||||
it('should give precedence to value provided in custom injector over one provided higher when crossing view boundaries',
|
||||
() => {
|
||||
@Directive({selector: 'menu'})
|
||||
class Menu {
|
||||
constructor(@Inject(token) public tokenValue: string) {}
|
||||
}
|
||||
|
||||
@Directive({
|
||||
selector: '[provide-token]',
|
||||
providers: [{provide: token, useValue: 'hello from directive'}]
|
||||
})
|
||||
class ProvideToken {
|
||||
}
|
||||
|
||||
@Component({selector: 'wrapper', template: `<div><menu></menu></div>`})
|
||||
class Wrapper {
|
||||
@ViewChild(Menu) menu!: Menu;
|
||||
}
|
||||
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<menu-trigger [triggerFor]="menuTemplate"></menu-trigger>
|
||||
<div provide-token>
|
||||
<ng-template #menuTemplate>
|
||||
<wrapper></wrapper>
|
||||
</ng-template>
|
||||
</div>
|
||||
`,
|
||||
providers: [{provide: token, useValue: 'hello from parent'}]
|
||||
})
|
||||
class App {
|
||||
@ViewChild(MenuTrigger) trigger!: MenuTrigger;
|
||||
@ViewChild(Wrapper) wrapper!: Wrapper;
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
declarations: [App, MenuTrigger, Menu, ProvideToken, Wrapper],
|
||||
exports: [App, MenuTrigger, Menu, ProvideToken, Wrapper],
|
||||
})
|
||||
class Module {
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({imports: [Module]});
|
||||
const injector =
|
||||
Injector.create({providers: [{provide: token, useValue: 'hello from injector'}]});
|
||||
const fixture = TestBed.createComponent(App);
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.componentInstance.trigger.open(injector);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(fixture.componentInstance.wrapper.menu.tokenValue).toBe('hello from injector');
|
||||
});
|
||||
|
||||
it('should not resolve value at insertion location', () => {
|
||||
@Directive({selector: 'menu'})
|
||||
class Menu {
|
||||
constructor(@Inject(token) public tokenValue: string) {}
|
||||
}
|
||||
|
||||
@Directive({
|
||||
selector: '[provide-token]',
|
||||
providers: [{provide: token, useValue: 'hello from directive'}]
|
||||
})
|
||||
class ProvideToken {
|
||||
}
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<div provide-token>
|
||||
<menu-trigger [triggerFor]="menuTemplate"></menu-trigger>
|
||||
</div>
|
||||
|
||||
<ng-template #menuTemplate>
|
||||
<menu></menu>
|
||||
</ng-template>
|
||||
`,
|
||||
providers: [{provide: token, useValue: 'hello from parent'}]
|
||||
})
|
||||
class App {
|
||||
@ViewChild(MenuTrigger) trigger!: MenuTrigger;
|
||||
@ViewChild(Menu) menu!: Menu;
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
declarations: [App, MenuTrigger, Menu, ProvideToken],
|
||||
exports: [App, MenuTrigger, Menu, ProvideToken],
|
||||
})
|
||||
class Module {
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({imports: [Module]});
|
||||
// Provide an empty injector so we hit the new code path.
|
||||
const injector = Injector.create({providers: []});
|
||||
const fixture = TestBed.createComponent(App);
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.componentInstance.trigger.open(injector);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(fixture.componentInstance.menu.tokenValue).toBe('hello from parent');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -923,6 +923,9 @@
|
|||
{
|
||||
"name": "getTNode"
|
||||
},
|
||||
{
|
||||
"name": "getTNodeFromLView"
|
||||
},
|
||||
{
|
||||
"name": "getTView"
|
||||
},
|
||||
|
|
@ -1088,6 +1091,9 @@
|
|||
{
|
||||
"name": "lookupTokenUsingModuleInjector"
|
||||
},
|
||||
{
|
||||
"name": "lookupTokenUsingNodeInjector"
|
||||
},
|
||||
{
|
||||
"name": "makeAnimationEvent"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -206,6 +206,9 @@
|
|||
{
|
||||
"name": "getSimpleChangesStore"
|
||||
},
|
||||
{
|
||||
"name": "getTNodeFromLView"
|
||||
},
|
||||
{
|
||||
"name": "getTView"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -992,6 +992,9 @@
|
|||
{
|
||||
"name": "getTNode"
|
||||
},
|
||||
{
|
||||
"name": "getTNodeFromLView"
|
||||
},
|
||||
{
|
||||
"name": "getTStylingRangeNext"
|
||||
},
|
||||
|
|
@ -1211,6 +1214,9 @@
|
|||
{
|
||||
"name": "lookupTokenUsingModuleInjector"
|
||||
},
|
||||
{
|
||||
"name": "lookupTokenUsingNodeInjector"
|
||||
},
|
||||
{
|
||||
"name": "makeParamDecorator"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -956,6 +956,9 @@
|
|||
{
|
||||
"name": "getTNode"
|
||||
},
|
||||
{
|
||||
"name": "getTNodeFromLView"
|
||||
},
|
||||
{
|
||||
"name": "getTStylingRangeNext"
|
||||
},
|
||||
|
|
@ -1169,6 +1172,9 @@
|
|||
{
|
||||
"name": "lookupTokenUsingModuleInjector"
|
||||
},
|
||||
{
|
||||
"name": "lookupTokenUsingNodeInjector"
|
||||
},
|
||||
{
|
||||
"name": "makeParamDecorator"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -161,6 +161,9 @@
|
|||
{
|
||||
"name": "getSimpleChangesStore"
|
||||
},
|
||||
{
|
||||
"name": "getTNodeFromLView"
|
||||
},
|
||||
{
|
||||
"name": "includeViewProviders"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1316,6 +1316,9 @@
|
|||
{
|
||||
"name": "getTNode"
|
||||
},
|
||||
{
|
||||
"name": "getTNodeFromLView"
|
||||
},
|
||||
{
|
||||
"name": "getTQuery"
|
||||
},
|
||||
|
|
@ -1526,6 +1529,9 @@
|
|||
{
|
||||
"name": "lookupTokenUsingModuleInjector"
|
||||
},
|
||||
{
|
||||
"name": "lookupTokenUsingNodeInjector"
|
||||
},
|
||||
{
|
||||
"name": "makeParamDecorator"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -479,6 +479,9 @@
|
|||
{
|
||||
"name": "getTNode"
|
||||
},
|
||||
{
|
||||
"name": "getTNodeFromLView"
|
||||
},
|
||||
{
|
||||
"name": "getTStylingRangeNext"
|
||||
},
|
||||
|
|
@ -614,6 +617,9 @@
|
|||
{
|
||||
"name": "lookupTokenUsingModuleInjector"
|
||||
},
|
||||
{
|
||||
"name": "lookupTokenUsingNodeInjector"
|
||||
},
|
||||
{
|
||||
"name": "makeParamDecorator"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -236,7 +236,7 @@ describe('di', () => {
|
|||
it('should handle initial undefined state', () => {
|
||||
const contentView = createLView(
|
||||
null, createTView(TViewType.Component, null, null, 1, 0, null, null, null, null, null),
|
||||
{}, LViewFlags.CheckAlways, null, null, {} as any, {} as any, null, null);
|
||||
{}, LViewFlags.CheckAlways, null, null, {} as any, {} as any, null, null, null);
|
||||
enterView(contentView);
|
||||
try {
|
||||
const parentTNode = getOrCreateTNode(contentView[TVIEW], 0, TNodeType.Element, null, null);
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ export function enterViewWithOneDiv() {
|
|||
const tNode = tView.firstChild = createTNode(tView, null!, TNodeType.Element, 0, 'div', null);
|
||||
const lView = createLView(
|
||||
null, tView, null, LViewFlags.CheckAlways, null, null, domRendererFactory3, renderer, null,
|
||||
null);
|
||||
null, null);
|
||||
lView[HEADER_OFFSET] = div;
|
||||
tView.data[HEADER_OFFSET] = tNode;
|
||||
enterView(lView);
|
||||
|
|
|
|||
|
|
@ -287,3 +287,17 @@ ng_benchmark(
|
|||
name = "render_stringify",
|
||||
bundle = ":render_stringify_lib",
|
||||
)
|
||||
|
||||
app_bundle(
|
||||
name = "embedded_view_injector_lib",
|
||||
entry_point = ":embedded_view_injector/index.ts",
|
||||
external = ["perf_hooks"],
|
||||
deps = [
|
||||
":perf_lib",
|
||||
],
|
||||
)
|
||||
|
||||
ng_benchmark(
|
||||
name = "embedded_view_injector",
|
||||
bundle = ":embedded_view_injector_lib",
|
||||
)
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ function testTemplate(rf: RenderFlags, ctx: any) {
|
|||
|
||||
const rootLView = createLView(
|
||||
null, createTView(TViewType.Root, null, null, 0, 0, null, null, null, null, null), {},
|
||||
LViewFlags.IsRoot, null, null, null, null, null, null);
|
||||
LViewFlags.IsRoot, null, null, null, null, null, null, null);
|
||||
|
||||
const viewTNode = createTNode(null!, null, TNodeType.Element, -1, null, null);
|
||||
const embeddedTView = createTView(
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ function testTemplate(rf: RenderFlags, ctx: any) {
|
|||
|
||||
const rootLView = createLView(
|
||||
null, createTView(TViewType.Root, null, null, 0, 0, null, null, null, null, null), {},
|
||||
LViewFlags.IsRoot, null, null, null, null, null, null);
|
||||
LViewFlags.IsRoot, null, null, null, null, null, null, null);
|
||||
|
||||
const viewTNode = createTNode(null!, null, TNodeType.Element, -1, null, null);
|
||||
const embeddedTView = createTView(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,89 @@
|
|||
/*!
|
||||
* @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 '@angular/core';
|
||||
import {RenderFlags, ɵɵadvance, ɵɵdefineComponent, ɵɵelement, ɵɵelementEnd, ɵɵelementStart, ɵɵproperty, ɵɵreference, ɵɵtemplate, ɵɵtemplateRefExtractor} from '@angular/core/src/render3';
|
||||
|
||||
import {createInnerComponent} from './inner_component';
|
||||
import {createRenderTemplateDirective} from './render_template_directive';
|
||||
|
||||
/**
|
||||
* Creates the root component of the benchmark. The goal is to add a few more layers of elements
|
||||
* between the root and the `ng-template` that renders out the `inner-comp`.
|
||||
* The template corresponds to:
|
||||
*
|
||||
* <div>
|
||||
* <div>
|
||||
* <div>
|
||||
* <div>
|
||||
* <div [renderTemplate]="template"></div>
|
||||
* <div>
|
||||
* <ng-template #template>
|
||||
* <div>
|
||||
* <div>
|
||||
* <div>
|
||||
* <div>
|
||||
* <div>
|
||||
* <inner-comp></inner-comp>
|
||||
* </div>
|
||||
* </div>
|
||||
* </div>
|
||||
* </div>
|
||||
* </div>
|
||||
* </ng-template>
|
||||
* </div>
|
||||
* </div>
|
||||
* </div>
|
||||
* </div>
|
||||
* </div>
|
||||
*/
|
||||
export function createAppComponent(injector: Injector|undefined) {
|
||||
const RenderTemplate = createRenderTemplateDirective(injector);
|
||||
const InnerComp = createInnerComponent(RenderTemplate);
|
||||
|
||||
function App_ng_template_6_Template(rf: RenderFlags, ctx: any) {
|
||||
if (rf & 1) {
|
||||
ɵɵelementStart(0, 'div')(1, 'div')(2, 'div')(3, 'div')(4, 'div');
|
||||
ɵɵelement(5, 'inner-comp');
|
||||
ɵɵelementEnd()()()()();
|
||||
}
|
||||
}
|
||||
|
||||
return class App {
|
||||
static ɵfac() {
|
||||
return new App();
|
||||
}
|
||||
|
||||
static ɵcmp = ɵɵdefineComponent({
|
||||
type: App,
|
||||
selectors: [['app']],
|
||||
decls: 8,
|
||||
vars: 1,
|
||||
consts: [[3, 'renderTemplate'], ['template', '']],
|
||||
template:
|
||||
function App_Template(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
ɵɵelementStart(0, 'div')(1, 'div')(2, 'div')(3, 'div');
|
||||
ɵɵelement(4, 'div', 0);
|
||||
ɵɵelementStart(5, 'div');
|
||||
ɵɵtemplate(
|
||||
6, App_ng_template_6_Template, 6, 0, 'ng-template', null, 1,
|
||||
ɵɵtemplateRefExtractor);
|
||||
ɵɵelementEnd()()()()();
|
||||
}
|
||||
if (rf & 2) {
|
||||
const _r0 = ɵɵreference(7);
|
||||
ɵɵadvance(4);
|
||||
ɵɵproperty('renderTemplate', _r0);
|
||||
}
|
||||
},
|
||||
directives: [RenderTemplate, InnerComp],
|
||||
encapsulation: 2
|
||||
}) as never;
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
/**
|
||||
* @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, ɵɵelement} from '@angular/core';
|
||||
|
||||
import {RenderFlags} from '../../../../src/render3/interfaces/definition';
|
||||
import {createBenchmark} from '../micro_bench';
|
||||
import {setupTestHarness} from '../setup';
|
||||
|
||||
import {createAppComponent} from './app_component';
|
||||
|
||||
|
||||
function template(rf: RenderFlags, ctx: any) {
|
||||
if (rf & 1) {
|
||||
ɵɵelement(0, 'app');
|
||||
}
|
||||
}
|
||||
|
||||
// App where no injector is provided when creating the embedded views.
|
||||
const noInjectorApp = createAppComponent(undefined);
|
||||
|
||||
// App where an empty injector is provided when creating the embedded views. We provide an
|
||||
// empty injector so that the entire view hierarchy has to be traversed during DI.
|
||||
const withInjectorApp = createAppComponent(Injector.create({providers: []}));
|
||||
|
||||
const noInjectorHarness = setupTestHarness(template, 1, 0, 1, {}, null, [noInjectorApp.ɵcmp]);
|
||||
const withInjectorHarness = setupTestHarness(template, 1, 0, 1, {}, null, [withInjectorApp.ɵcmp]);
|
||||
|
||||
const benchmark = createBenchmark('embedded_view_injector');
|
||||
const noEmbeddedInjectorTime = benchmark('no injector');
|
||||
const withEmbeddedInjectorTime = benchmark('with embedded view injector');
|
||||
|
||||
while (noEmbeddedInjectorTime()) {
|
||||
noInjectorHarness.createEmbeddedLView();
|
||||
noInjectorHarness.detectChanges();
|
||||
}
|
||||
|
||||
while (withEmbeddedInjectorTime()) {
|
||||
withInjectorHarness.createEmbeddedLView();
|
||||
withInjectorHarness.detectChanges();
|
||||
}
|
||||
|
||||
benchmark.report();
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
/*!
|
||||
* @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 {InjectFlags, InjectionToken, ɵɵdefineComponent, ɵɵtext} from '@angular/core';
|
||||
import {RenderFlags, ɵɵdirectiveInject, ɵɵtextInterpolate1} from '@angular/core/src/render3';
|
||||
|
||||
const token = new InjectionToken<string>('token');
|
||||
|
||||
/**
|
||||
* Leaf component that tries to inject a token.
|
||||
* Template corresponds to `Hello {{tokenValue}}`
|
||||
*/
|
||||
export class InjectorComp {
|
||||
static ɵcmp = ɵɵdefineComponent({
|
||||
type: InjectorComp,
|
||||
selectors: [['injector-comp']],
|
||||
decls: 1,
|
||||
vars: 1,
|
||||
encapsulation: 2,
|
||||
template:
|
||||
function InjectorComp_Template(rf: RenderFlags, ctx: any) {
|
||||
if (rf & 1) {
|
||||
ɵɵtext(0);
|
||||
}
|
||||
if (rf & 2) {
|
||||
ɵɵtextInterpolate1('Hello ', ctx.tokenValue, '');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
static ɵfac() {
|
||||
return new InjectorComp(ɵɵdirectiveInject(token, InjectFlags.Optional));
|
||||
}
|
||||
|
||||
constructor(public tokenValue: string) {}
|
||||
}
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
/*!
|
||||
* @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 {Type, ɵɵadvance, ɵɵdefineComponent, ɵɵelement, ɵɵelementEnd, ɵɵelementStart, ɵɵproperty, ɵɵreference, ɵɵtemplate, ɵɵtemplateRefExtractor} from '@angular/core';
|
||||
import {RenderFlags} from '@angular/core/src/render3';
|
||||
|
||||
import {InjectorComp} from './injector_component';
|
||||
|
||||
/**
|
||||
* Creates a component that will be rendered inside the main app that adds a few layers of elements
|
||||
* between the root and where the template with the injector component will be rendered.
|
||||
* Template corresponds to:
|
||||
*
|
||||
*
|
||||
* <div>
|
||||
* <div>
|
||||
* <div>
|
||||
* <div>
|
||||
* <div [renderTemplate]="template"></div>
|
||||
* <div>
|
||||
* <ng-template #template>
|
||||
* <div>
|
||||
* <div>
|
||||
* <div>
|
||||
* <div>
|
||||
* <div>
|
||||
* <injector-comp></injector-comp>
|
||||
* </div>
|
||||
* </div>
|
||||
* </div>
|
||||
* </div>
|
||||
* </div>
|
||||
* </ng-template>
|
||||
* </div>
|
||||
* </div>
|
||||
* </div>
|
||||
* </div>
|
||||
* </div>
|
||||
*/
|
||||
export function createInnerComponent(renderTemplateDirective: Type<{}>) {
|
||||
function InnerComp_ng_template_6_Template(rf: RenderFlags, ctx: any) {
|
||||
if (rf & 1) {
|
||||
ɵɵelementStart(0, 'div')(1, 'div')(2, 'div')(3, 'div')(4, 'div');
|
||||
ɵɵelement(5, 'injector-comp');
|
||||
ɵɵelementEnd()()()()();
|
||||
}
|
||||
}
|
||||
|
||||
return class InnerComp {
|
||||
static ɵfac() {
|
||||
return new InnerComp();
|
||||
}
|
||||
|
||||
static ɵcmp = ɵɵdefineComponent({
|
||||
type: InnerComp,
|
||||
selectors: [['inner-comp']],
|
||||
decls: 8,
|
||||
vars: 1,
|
||||
consts: [[3, 'renderTemplate'], ['template', '']],
|
||||
template:
|
||||
function InnerComp_Template(rf, ctx: any) {
|
||||
if (rf & 1) {
|
||||
ɵɵelementStart(0, 'div')(1, 'div')(2, 'div')(3, 'div');
|
||||
ɵɵelement(4, 'div', 0);
|
||||
ɵɵelementStart(5, 'div');
|
||||
ɵɵtemplate(
|
||||
6, InnerComp_ng_template_6_Template, 6, 0, 'ng-template', null, 1,
|
||||
ɵɵtemplateRefExtractor);
|
||||
ɵɵelementEnd()()()()();
|
||||
}
|
||||
if (rf & 2) {
|
||||
const _r0 = ɵɵreference(7);
|
||||
ɵɵadvance(4);
|
||||
ɵɵproperty('renderTemplate', _r0);
|
||||
}
|
||||
},
|
||||
directives: [renderTemplateDirective, InjectorComp],
|
||||
encapsulation: 2
|
||||
});
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
/*!
|
||||
* @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, TemplateRef, ViewContainerRef, ɵɵdefineDirective, ɵɵdirectiveInject} from '@angular/core';
|
||||
import {injectViewContainerRef} from '@angular/core/src/linker/view_container_ref';
|
||||
|
||||
|
||||
class ViewContainerRefToken {
|
||||
/**
|
||||
* @internal
|
||||
* @nocollapse
|
||||
*/
|
||||
static __NG_ELEMENT_ID__(): ViewContainerRef {
|
||||
return injectViewContainerRef();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a helper directive that renders out a template
|
||||
* reference that is passed in as an input.
|
||||
*/
|
||||
export function createRenderTemplateDirective(injector: Injector|undefined) {
|
||||
return class RenderTemplate {
|
||||
static ɵfac() {
|
||||
return new RenderTemplate(ɵɵdirectiveInject(ViewContainerRefToken as any));
|
||||
}
|
||||
|
||||
static ɵdir = ɵɵdefineDirective({
|
||||
type: RenderTemplate,
|
||||
selectors: [['', 'renderTemplate', '']],
|
||||
inputs: {template: ['renderTemplate', 'template']}
|
||||
});
|
||||
|
||||
constructor(public viewContainerRef: ViewContainerRef) {}
|
||||
|
||||
set template(template: TemplateRef<any>) {
|
||||
this.viewContainerRef.createEmbeddedView(template, undefined, {injector});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -67,7 +67,7 @@ function testTemplate(rf: RenderFlags, ctx: any) {
|
|||
|
||||
const rootLView = createLView(
|
||||
null, createTView(TViewType.Root, null, null, 0, 0, null, null, null, null, null), {},
|
||||
LViewFlags.IsRoot, null, null, null, null, null, null);
|
||||
LViewFlags.IsRoot, null, null, null, null, null, null, null);
|
||||
|
||||
const viewTNode = createTNode(null!, null, TNodeType.Element, -1, null, null);
|
||||
const embeddedTView = createTView(
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
*/
|
||||
import {injectTemplateRef} from '@angular/core/src/linker/template_ref';
|
||||
import {injectViewContainerRef} from '@angular/core/src/linker/view_container_ref';
|
||||
|
||||
import {TemplateRef, ViewContainerRef} from '../../../../src/linker';
|
||||
import {ɵɵdefineDirective, ɵɵdirectiveInject, ɵɵtemplate} from '../../../../src/render3/index';
|
||||
import {createLView, createTNode, createTView} from '../../../../src/render3/instructions/shared';
|
||||
|
|
@ -63,7 +64,7 @@ function testTemplate(rf: RenderFlags, ctx: any) {
|
|||
|
||||
const rootLView = createLView(
|
||||
null, createTView(TViewType.Root, null, null, 0, 0, null, null, null, null, null), {},
|
||||
LViewFlags.IsRoot, null, null, null, null, null, null);
|
||||
LViewFlags.IsRoot, null, null, null, null, null, null, null);
|
||||
|
||||
const viewTNode = createTNode(null!, null, TNodeType.Element, -1, null, null);
|
||||
const embeddedTView = createTView(
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import {RComment, RElement, RNode, RText} from '@angular/core/src/render3/interfaces/renderer_dom';
|
||||
|
||||
import {ProceduralRenderer3, Renderer3, RendererFactory3, RendererStyleFlags3} from '../../../src/render3/interfaces/renderer';
|
||||
|
||||
export class MicroBenchmarkRenderNode implements RNode, RComment, RText {
|
||||
|
|
@ -49,7 +50,7 @@ export class MicroBenchmarkRenderer implements ProceduralRenderer3 {
|
|||
return null;
|
||||
}
|
||||
nextSibling(node: RNode): RNode|null {
|
||||
throw new Error('Method not implemented.');
|
||||
return null;
|
||||
}
|
||||
setAttribute(el: RElement, name: string, value: string, namespace?: string|null|undefined): void {
|
||||
if (name === 'class' && isOurNode(el)) {
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ export function createAndRenderLView(
|
|||
parentLView: LView, tView: TView, hostTNode: TElementNode): LView {
|
||||
const embeddedLView = createLView(
|
||||
parentLView, tView, {}, LViewFlags.CheckAlways, null, hostTNode, rendererFactory, renderer,
|
||||
null, null);
|
||||
null, null, null);
|
||||
renderView(tView, embeddedLView, null);
|
||||
return embeddedLView;
|
||||
}
|
||||
|
|
@ -55,7 +55,7 @@ export function setupTestHarness(
|
|||
const hostNode = renderer.createElement('div');
|
||||
const hostLView = createLView(
|
||||
null, hostTView, {}, LViewFlags.CheckAlways | LViewFlags.IsRoot, hostNode, null,
|
||||
rendererFactory, renderer, null, null);
|
||||
rendererFactory, renderer, null, null, null);
|
||||
const mockRCommentNode = renderer.createComment('');
|
||||
const lContainer =
|
||||
createLContainer(mockRCommentNode, hostLView, mockRCommentNode, tContainerNode);
|
||||
|
|
@ -71,7 +71,7 @@ export function setupTestHarness(
|
|||
function createEmbeddedLView(): LView {
|
||||
const embeddedLView = createLView(
|
||||
hostLView, embeddedTView, embeddedViewContext, LViewFlags.CheckAlways, null, viewTNode,
|
||||
rendererFactory, renderer, null, null);
|
||||
rendererFactory, renderer, null, null, null);
|
||||
renderView(embeddedTView, embeddedLView, embeddedViewContext);
|
||||
return embeddedLView;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ function testTemplate(rf: RenderFlags, ctx: any) {
|
|||
|
||||
const rootLView = createLView(
|
||||
null, createTView(TViewType.Root, null, null, 0, 0, null, null, null, null, null), {},
|
||||
LViewFlags.IsRoot, null, null, null, null, null, null);
|
||||
LViewFlags.IsRoot, null, null, null, null, null, null, null);
|
||||
|
||||
const viewTNode = createTNode(null!, null, TNodeType.Element, -1, null, null);
|
||||
const embeddedTView = createTView(
|
||||
|
|
|
|||
|
|
@ -292,7 +292,7 @@ export function renderTemplate<T>(
|
|||
const tView = createTView(TViewType.Root, null, null, 1, 0, null, null, null, null, null);
|
||||
const hostLView = createLView(
|
||||
null, tView, {}, LViewFlags.CheckAlways | LViewFlags.IsRoot, null, null,
|
||||
providedRendererFactory, renderer, null, null);
|
||||
providedRendererFactory, renderer, null, null, null);
|
||||
enterView(hostLView);
|
||||
|
||||
const def = ɵɵdefineComponent({
|
||||
|
|
@ -310,7 +310,7 @@ export function renderTemplate<T>(
|
|||
hostLView[hostTNode.index] = hostNode;
|
||||
componentView = createLView(
|
||||
hostLView, componentTView, context, LViewFlags.CheckAlways, hostNode, hostTNode,
|
||||
providedRendererFactory, renderer, sanitizer || null, null);
|
||||
providedRendererFactory, renderer, sanitizer || null, null, null);
|
||||
}
|
||||
renderComponentOrTemplate(componentView[TVIEW], componentView, templateFn, context);
|
||||
return componentView;
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ export class ViewFixture {
|
|||
const hostTView = createTView(TViewType.Root, null, null, 1, 0, null, null, null, null, null);
|
||||
const hostLView = createLView(
|
||||
null, hostTView, {}, LViewFlags.CheckAlways | LViewFlags.IsRoot, null, null,
|
||||
domRendererFactory3, hostRenderer, null, null);
|
||||
domRendererFactory3, hostRenderer, null, null, null);
|
||||
|
||||
|
||||
this.tView = createTView(
|
||||
|
|
@ -58,7 +58,7 @@ export class ViewFixture {
|
|||
createTNode(hostTView, null, TNodeType.Element, 0, 'host-element', null) as TElementNode;
|
||||
this.lView = createLView(
|
||||
hostLView, this.tView, context || {}, LViewFlags.CheckAlways, this.host, hostTNode,
|
||||
domRendererFactory3, hostRenderer, null, null);
|
||||
domRendererFactory3, hostRenderer, null, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
Loading…
Reference in a new issue