From e7ef02722d0d39d0825cf64d4e15d964d8704b3a Mon Sep 17 00:00:00 2001 From: Marc Laval Date: Wed, 11 Apr 2018 14:15:30 +0200 Subject: [PATCH] fix(ivy): local directives and pipes should be applied to TemplateRef (#23312) PR Close #23312 --- packages/core/src/render3/di.ts | 12 ++- packages/core/src/render3/instructions.ts | 13 ++- .../test/render3/view_container_ref_spec.ts | 88 ++++++++++++++++++- 3 files changed, 99 insertions(+), 14 deletions(-) diff --git a/packages/core/src/render3/di.ts b/packages/core/src/render3/di.ts index 99d83eb6e8..4fb335c385 100644 --- a/packages/core/src/render3/di.ts +++ b/packages/core/src/render3/di.ts @@ -20,7 +20,7 @@ import {Type} from '../type'; import {assertGreaterThan, assertLessThan, assertNotNull} from './assert'; import {addToViewTree, assertPreviousIsParent, createLContainer, createLNodeObject, getDirectiveInstance, getPreviousOrParentNode, getRenderer, isComponent, renderEmbeddedTemplate, resolveDirective} from './instructions'; -import {ComponentTemplate, DirectiveDef} from './interfaces/definition'; +import {ComponentTemplate, DirectiveDef, DirectiveDefList, PipeDefList} from './interfaces/definition'; import {LInjector} from './interfaces/injector'; import {LContainerNode, LElementNode, LNode, LNodeType, LViewNode, TNodeFlags} from './interfaces/node'; import {QueryReadType} from './interfaces/query'; @@ -701,8 +701,10 @@ class ViewContainerRef implements viewEngine_ViewContainerRef { export function getOrCreateTemplateRef(di: LInjector): viewEngine_TemplateRef { ngDevMode && assertNodeType(di.node, LNodeType.Container); const data = (di.node as LContainerNode).data; + const tView = di.node.view.tView; return di.templateRef || (di.templateRef = new TemplateRef( - getOrCreateElementRef(di), data.template !, getRenderer())); + getOrCreateElementRef(di), data.template !, getRenderer(), + tView.directiveRegistry, tView.pipeRegistry)); } class TemplateRef implements viewEngine_TemplateRef { @@ -711,13 +713,15 @@ class TemplateRef implements viewEngine_TemplateRef { constructor( elementRef: viewEngine_ElementRef, template: ComponentTemplate, - private _renderer: Renderer3) { + private _renderer: Renderer3, private _directives: DirectiveDefList|null, + private _pipes: PipeDefList|null) { this.elementRef = elementRef; this._template = template; } createEmbeddedView(context: T): viewEngine_EmbeddedViewRef { - const viewNode = renderEmbeddedTemplate(null, this._template, context, this._renderer); + const viewNode = renderEmbeddedTemplate( + null, this._template, context, this._renderer, this._directives, this._pipes); return addDestroyable(new EmbeddedViewRef(viewNode, this._template, context)); } } diff --git a/packages/core/src/render3/instructions.ts b/packages/core/src/render3/instructions.ts index 09f3328836..38844d7069 100644 --- a/packages/core/src/render3/instructions.ts +++ b/packages/core/src/render3/instructions.ts @@ -19,7 +19,7 @@ import {LContainerNode, LElementNode, LNode, LNodeType, TNodeFlags, LProjectionN import {assertNodeType} from './node_assert'; import {appendChild, insertChild, insertView, appendProjectedNode, removeView, canInsertNativeNode, createTextNode} from './node_manipulation'; import {isNodeMatchingSelectorList, matchingSelectorIndex} from './node_selector_matcher'; -import {ComponentDef, ComponentTemplate, ComponentType, DirectiveDef, DirectiveDefList, DirectiveDefListOrFactory, DirectiveType, PipeDef, PipeDefListOrFactory, RenderFlags} from './interfaces/definition'; +import {ComponentDef, ComponentTemplate, ComponentType, DirectiveDef, DirectiveDefList, DirectiveDefListOrFactory, DirectiveType, PipeDef, PipeDefList, PipeDefListOrFactory, RenderFlags} from './interfaces/definition'; import {RElement, RText, Renderer3, RendererFactory3, ProceduralRenderer3, ObjectOrientedRenderer3, RendererStyleFlags3, isProceduralRenderer} from './interfaces/renderer'; import {isDifferent, stringify} from './util'; import {executeHooks, queueLifecycleHooks, queueInitHooks, executeInitHooks} from './hooks'; @@ -450,8 +450,8 @@ export function renderTemplate( } export function renderEmbeddedTemplate( - viewNode: LViewNode | null, template: ComponentTemplate, context: T, - renderer: Renderer3): LViewNode { + viewNode: LViewNode | null, template: ComponentTemplate, context: T, renderer: Renderer3, + directives?: DirectiveDefList | null, pipes?: PipeDefList | null): LViewNode { const _isParent = isParent; const _previousOrParentNode = previousOrParentNode; let oldView: LView; @@ -460,11 +460,7 @@ export function renderEmbeddedTemplate( previousOrParentNode = null !; let rf: RenderFlags = RenderFlags.Update; if (viewNode == null) { - // TODO: revisit setting currentView when re-writing view containers - const directives = currentView && currentView.tView.directiveRegistry; - const pipes = currentView && currentView.tView.pipeRegistry; - - const tView = getOrCreateTView(template, directives, pipes); + const tView = getOrCreateTView(template, directives || null, pipes || null); const lView = createLView(-1, renderer, tView, template, context, LViewFlags.CheckAlways); viewNode = createLNode(null, LNodeType.View, null, lView); @@ -1474,6 +1470,7 @@ function refreshDynamicChildren() { const container = current as LContainer; for (let i = 0; i < container.views.length; i++) { const view = container.views[i]; + // The directives and pipes are not needed here as an existing view is only being refreshed. renderEmbeddedTemplate(view, view.data.template !, view.data.context !, renderer); } } diff --git a/packages/core/test/render3/view_container_ref_spec.ts b/packages/core/test/render3/view_container_ref_spec.ts index a79c7ba365..dda5e60885 100644 --- a/packages/core/test/render3/view_container_ref_spec.ts +++ b/packages/core/test/render3/view_container_ref_spec.ts @@ -6,11 +6,12 @@ * found in the LICENSE file at https://angular.io/license */ -import {Component, Directive, TemplateRef, ViewContainerRef} from '../../src/core'; +import {Component, Directive, Pipe, PipeTransform, TemplateRef, ViewContainerRef} from '../../src/core'; import {getOrCreateNodeInjectorForNode, getOrCreateTemplateRef} from '../../src/render3/di'; -import {defineComponent, defineDirective, injectTemplateRef, injectViewContainerRef} from '../../src/render3/index'; +import {defineComponent, defineDirective, definePipe, injectTemplateRef, injectViewContainerRef} from '../../src/render3/index'; import {bind, container, containerRefreshEnd, containerRefreshStart, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, interpolation1, load, loadDirective, projection, projectionDef, text, textBinding} from '../../src/render3/instructions'; import {RenderFlags} from '../../src/render3/interfaces/definition'; +import {pipe, pipeBind1} from '../../src/render3/pipe'; import {ComponentFixture, TemplateFixture} from './render_util'; @@ -334,6 +335,89 @@ describe('ViewContainerRef', () => { fixture.update(); expect(fixture.html).toEqual('before|AAB|after'); }); + + it('should apply directives and pipes of the host view to the TemplateRef', () => { + @Component({selector: 'child', template: `{{name}}`}) + class Child { + name: string; + + static ngComponentDef = defineComponent({ + type: Child, + selectors: [['child']], + factory: () => new Child(), + template: (rf: RenderFlags, cmp: Child) => { + if (rf & RenderFlags.Create) { + text(0); + } + if (rf & RenderFlags.Update) { + textBinding(0, interpolation1('', cmp.name, '')); + } + }, + inputs: {name: 'name'} + }); + } + + @Pipe({name: 'starPipe'}) + class StarPipe implements PipeTransform { + transform(value: any) { return `**${value}**`; } + + static ngPipeDef = definePipe({ + name: 'starPipe', + type: StarPipe, + factory: function StarPipe_Factory() { return new StarPipe(); }, + }); + } + + @Component({ + template: ` + + + + + + ` + }) + class SomeComponent { + static ngComponentDef = defineComponent({ + type: SomeComponent, + selectors: [['some-comp']], + factory: () => new SomeComponent(), + template: (rf: RenderFlags, cmp: SomeComponent) => { + if (rf & RenderFlags.Create) { + container(0, (rf: RenderFlags, ctx: any) => { + if (rf & RenderFlags.Create) { + elementStart(0, 'child'); + elementEnd(); + pipe(1, 'starPipe'); + } + if (rf & RenderFlags.Create) { + elementProperty(0, 'name', bind(pipeBind1(1, 'C'))); + } + }); + pipe(1, 'starPipe'); + elementStart(2, 'child', ['vcref', '']); + elementEnd(); + elementStart(3, 'child'); + elementEnd(); + } + if (rf & RenderFlags.Update) { + const tplRef = getOrCreateTemplateRef(getOrCreateNodeInjectorForNode(load(0))); + elementProperty(2, 'tplRef', bind(tplRef)); + elementProperty(2, 'name', bind(pipeBind1(1, 'A'))); + elementProperty(3, 'name', bind(pipeBind1(1, 'B'))); + } + }, + directives: [Child, DirectiveWithVCRef], + pipes: [StarPipe] + }); + } + + const fixture = new ComponentFixture(SomeComponent); + directiveInstance !.vcref.createEmbeddedView(directiveInstance !.tplRef, fixture.component); + fixture.update(); + expect(fixture.html) + .toEqual('**A****C****B**'); + }); }); describe('detach', () => {