diff --git a/packages/core/src/linker/view_container_ref.ts b/packages/core/src/linker/view_container_ref.ts index cb188cecdf..bd2917fcec 100644 --- a/packages/core/src/linker/view_container_ref.ts +++ b/packages/core/src/linker/view_container_ref.ts @@ -41,6 +41,7 @@ export abstract class ViewContainerRef { abstract get injector(): Injector; + /** @deprecated No replacement */ abstract get parentInjector(): Injector; /** diff --git a/packages/core/src/render3/di.ts b/packages/core/src/render3/di.ts index 0ff99def26..9209484bc5 100644 --- a/packages/core/src/render3/di.ts +++ b/packages/core/src/render3/di.ts @@ -552,9 +552,11 @@ export const QUERY_READ_FROM_NODE = ngDevMode && assertNodeOfPossibleTypes(node, TNodeType.Container, TNodeType.Element); if (directiveIdx > -1) { return node.view[DIRECTIVES] ![directiveIdx]; - } else if (node.tNode.type === TNodeType.Element) { + } + if (node.tNode.type === TNodeType.Element) { return getOrCreateElementRef(injector); - } else if (node.tNode.type === TNodeType.Container) { + } + if (node.tNode.type === TNodeType.Container) { return getOrCreateTemplateRef(injector); } throw new Error('fail'); @@ -600,26 +602,55 @@ export function getOrCreateContainerRef(di: LInjector): viewEngine.ViewContainer addToViewTree(vcRefHost.view, hostTNode.index as number, lContainer); - di.viewContainerRef = new ViewContainerRef(lContainerNode); + di.viewContainerRef = new ViewContainerRef(lContainerNode, vcRefHost); } return di.viewContainerRef; } +class NodeInjector implements Injector { + constructor(private _lInjector: LInjector) {} + + get(token: any): any { + if (token === viewEngine.TemplateRef) { + return getOrCreateTemplateRef(this._lInjector); + } + if (token === viewEngine.ViewContainerRef) { + return getOrCreateContainerRef(this._lInjector); + } + if (token === viewEngine.ElementRef) { + return getOrCreateElementRef(this._lInjector); + } + if (token === viewEngine_ChangeDetectorRef) { + return getOrCreateChangeDetectorRef(this._lInjector, null); + } + + return getOrCreateInjectable(this._lInjector, token); + } +} + /** * A ref to a container that enables adding and removing views from that container * imperatively. */ class ViewContainerRef implements viewEngine.ViewContainerRef { private _viewRefs: viewEngine.ViewRef[] = []; - // TODO(issue/24571): remove '!'. - element !: viewEngine.ElementRef; - // TODO(issue/24571): remove '!'. - injector !: Injector; - // TODO(issue/24571): remove '!'. - parentInjector !: Injector; - constructor(private _lContainerNode: LContainerNode) {} + + constructor( + private _lContainerNode: LContainerNode, private _hostNode: LElementNode|LContainerNode) {} + + get element(): ElementRef { return new ElementRef(this._hostNode.native); } + + get injector(): Injector { + return new NodeInjector(getOrCreateNodeInjectorForNode(this._hostNode)); + } + + /** @deprecated No replacement */ + get parentInjector(): Injector { + const parentLInjector = getParentLNode(this._hostNode).nodeInjector; + return parentLInjector ? new NodeInjector(parentLInjector) : Injector.NULL; + } clear(): void { const lContainer = this._lContainerNode.data; @@ -651,7 +682,7 @@ class ViewContainerRef implements viewEngine.ViewContainerRef { ngModuleRef?: viewEngine.NgModuleRef|undefined): viewEngine.ComponentRef { const contextInjector = injector || this.parentInjector; if (!ngModuleRef && contextInjector) { - ngModuleRef = contextInjector.get(viewEngine.NgModuleRef); + ngModuleRef = contextInjector.get(viewEngine.NgModuleRef, null); } const componentRef = diff --git a/packages/core/src/render3/instructions.ts b/packages/core/src/render3/instructions.ts index 91140381aa..efeb0fc4b0 100644 --- a/packages/core/src/render3/instructions.ts +++ b/packages/core/src/render3/instructions.ts @@ -336,7 +336,7 @@ export function createLViewData( null, // directives null, // cleanupInstances context, // context - viewData && viewData[INJECTOR], // injector + viewData && viewData[INJECTOR] || null, // injector renderer, // renderer sanitizer || null, // sanitizer null, // tail diff --git a/packages/core/src/view/refs.ts b/packages/core/src/view/refs.ts index 72a44d6b5d..4aa2eb4afb 100644 --- a/packages/core/src/view/refs.ts +++ b/packages/core/src/view/refs.ts @@ -138,6 +138,7 @@ class ViewContainerRef_ implements ViewContainerData { get injector(): Injector { return new Injector_(this._view, this._elDef); } + /** @deprecated No replacement */ get parentInjector(): Injector { let view = this._view; let elDef = this._elDef.parent; diff --git a/packages/core/test/bundling/todo/bundle.golden_symbols.json b/packages/core/test/bundling/todo/bundle.golden_symbols.json index d3daec9e7a..da607a5ef7 100644 --- a/packages/core/test/bundling/todo/bundle.golden_symbols.json +++ b/packages/core/test/bundling/todo/bundle.golden_symbols.json @@ -44,6 +44,9 @@ { "name": "EMPTY_RENDERER_TYPE_ID" }, + { + "name": "ElementRef" + }, { "name": "ElementRef$1" }, @@ -98,6 +101,12 @@ { "name": "NgModuleRef" }, + { + "name": "NodeInjector" + }, + { + "name": "NullInjector" + }, { "name": "Optional" }, @@ -173,6 +182,9 @@ { "name": "ViewContainerRef$1" }, + { + "name": "ViewContainerRef$1" + }, { "name": "ViewEncapsulation$1" }, @@ -191,6 +203,9 @@ { "name": "_ROOT_DIRECTIVE_INDICES" }, + { + "name": "_THROW_IF_NOT_FOUND" + }, { "name": "__read" }, @@ -494,6 +509,9 @@ { "name": "getCleanup" }, + { + "name": "getClosestComponentAncestor" + }, { "name": "getCurrentSanitizer" }, @@ -515,12 +533,18 @@ { "name": "getNextLNode" }, + { + "name": "getOrCreateChangeDetectorRef" + }, { "name": "getOrCreateContainerRef" }, { "name": "getOrCreateElementRef" }, + { + "name": "getOrCreateHostChangeDetector" + }, { "name": "getOrCreateInjectable" }, @@ -617,6 +641,9 @@ { "name": "invertObject" }, + { + "name": "isComponent" + }, { "name": "isContextDirty" }, diff --git a/packages/core/test/render3/view_container_ref_spec.ts b/packages/core/test/render3/view_container_ref_spec.ts index e04905d608..c38c72ca39 100644 --- a/packages/core/test/render3/view_container_ref_spec.ts +++ b/packages/core/test/render3/view_container_ref_spec.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {Component, ComponentFactoryResolver, Directive, EmbeddedViewRef, NgModuleRef, Pipe, PipeTransform, RendererFactory2, TemplateRef, ViewContainerRef, createInjector, defineInjector, ɵAPP_ROOT as APP_ROOT, ɵNgModuleDef as NgModuleDef} from '../../src/core'; +import {Component, ComponentFactoryResolver, ElementRef, EmbeddedViewRef, NgModuleRef, Pipe, PipeTransform, RendererFactory2, TemplateRef, ViewContainerRef, createInjector, defineInjector, ɵAPP_ROOT as APP_ROOT, ɵNgModuleDef as NgModuleDef} from '../../src/core'; import {getOrCreateNodeInjectorForNode, getOrCreateTemplateRef} from '../../src/render3/di'; import {AttributeMarker, NgOnChangesFeature, defineComponent, defineDirective, definePipe, injectComponentFactoryResolver, injectTemplateRef, injectViewContainerRef} from '../../src/render3/index'; import {bind, container, containerRefreshEnd, containerRefreshStart, element, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, interpolation1, interpolation3, load, loadDirective, nextContext, projection, projectionDef, reserveSlots, text, textBinding} from '../../src/render3/instructions'; @@ -1035,6 +1035,66 @@ describe('ViewContainerRef', () => { '

12
34
'); }); }); + + describe('getters', () => { + it('should work on elements', () => { + function createTemplate() { + elementStart(0, 'header', ['vcref', '']); + elementEnd(); + elementStart(1, 'footer'); + elementEnd(); + } + + new TemplateFixture(createTemplate, undefined, [DirectiveWithVCRef]); + + expect(directiveInstance !.vcref.element.nativeElement.tagName.toLowerCase()) + .toEqual('header'); + expect( + directiveInstance !.vcref.injector.get(ElementRef).nativeElement.tagName.toLowerCase()) + .toEqual('header'); + expect(() => directiveInstance !.vcref.parentInjector.get(ElementRef)).toThrow(); + }); + + it('should work on components', () => { + const HeaderComponent = + createComponent('header-cmp', function(rf: RenderFlags, ctx: any) {}); + + function createTemplate() { + elementStart(0, 'header-cmp', ['vcref', '']); + elementEnd(); + elementStart(1, 'footer'); + elementEnd(); + } + + new TemplateFixture(createTemplate, undefined, [HeaderComponent, DirectiveWithVCRef]); + + expect(directiveInstance !.vcref.element.nativeElement.tagName.toLowerCase()) + .toEqual('header-cmp'); + expect( + directiveInstance !.vcref.injector.get(ElementRef).nativeElement.tagName.toLowerCase()) + .toEqual('header-cmp'); + expect(() => directiveInstance !.vcref.parentInjector.get(ElementRef)).toThrow(); + }); + + it('should work on containers', () => { + function createTemplate() { + container(0, embeddedTemplate, undefined, ['vcref', '']); + elementStart(1, 'footer'); + elementEnd(); + } + + function updateTemplate() { + containerRefreshStart(0); + containerRefreshEnd(); + } + + new TemplateFixture(createTemplate, updateTemplate, [DirectiveWithVCRef]); + expect(directiveInstance !.vcref.element.nativeElement.textContent).toEqual('container'); + expect(directiveInstance !.vcref.injector.get(ElementRef).nativeElement.textContent) + .toEqual('container'); + expect(() => directiveInstance !.vcref.parentInjector.get(ElementRef)).toThrow(); + }); + }); }); describe('projection', () => { diff --git a/tools/public_api_guard/core/core.d.ts b/tools/public_api_guard/core/core.d.ts index 84a8114039..307ca1ea21 100644 --- a/tools/public_api_guard/core/core.d.ts +++ b/tools/public_api_guard/core/core.d.ts @@ -922,7 +922,7 @@ export declare abstract class ViewContainerRef { abstract readonly element: ElementRef; abstract readonly injector: Injector; abstract readonly length: number; - abstract readonly parentInjector: Injector; + /** @deprecated */ abstract readonly parentInjector: Injector; abstract clear(): void; abstract createComponent(componentFactory: ComponentFactory, index?: number, injector?: Injector, projectableNodes?: any[][], ngModule?: NgModuleRef): ComponentRef; abstract createEmbeddedView(templateRef: TemplateRef, context?: C, index?: number): EmbeddedViewRef;