fix(ivy): local directives and pipes should be applied to TemplateRef (#23312)
PR Close #23312
This commit is contained in:
@ -20,7 +20,7 @@ import {Type} from '../type';
|
|||||||
|
|
||||||
import {assertGreaterThan, assertLessThan, assertNotNull} from './assert';
|
import {assertGreaterThan, assertLessThan, assertNotNull} from './assert';
|
||||||
import {addToViewTree, assertPreviousIsParent, createLContainer, createLNodeObject, getDirectiveInstance, getPreviousOrParentNode, getRenderer, isComponent, renderEmbeddedTemplate, resolveDirective} from './instructions';
|
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 {LInjector} from './interfaces/injector';
|
||||||
import {LContainerNode, LElementNode, LNode, LNodeType, LViewNode, TNodeFlags} from './interfaces/node';
|
import {LContainerNode, LElementNode, LNode, LNodeType, LViewNode, TNodeFlags} from './interfaces/node';
|
||||||
import {QueryReadType} from './interfaces/query';
|
import {QueryReadType} from './interfaces/query';
|
||||||
@ -701,8 +701,10 @@ class ViewContainerRef implements viewEngine_ViewContainerRef {
|
|||||||
export function getOrCreateTemplateRef<T>(di: LInjector): viewEngine_TemplateRef<T> {
|
export function getOrCreateTemplateRef<T>(di: LInjector): viewEngine_TemplateRef<T> {
|
||||||
ngDevMode && assertNodeType(di.node, LNodeType.Container);
|
ngDevMode && assertNodeType(di.node, LNodeType.Container);
|
||||||
const data = (di.node as LContainerNode).data;
|
const data = (di.node as LContainerNode).data;
|
||||||
|
const tView = di.node.view.tView;
|
||||||
return di.templateRef || (di.templateRef = new TemplateRef<any>(
|
return di.templateRef || (di.templateRef = new TemplateRef<any>(
|
||||||
getOrCreateElementRef(di), data.template !, getRenderer()));
|
getOrCreateElementRef(di), data.template !, getRenderer(),
|
||||||
|
tView.directiveRegistry, tView.pipeRegistry));
|
||||||
}
|
}
|
||||||
|
|
||||||
class TemplateRef<T> implements viewEngine_TemplateRef<T> {
|
class TemplateRef<T> implements viewEngine_TemplateRef<T> {
|
||||||
@ -711,13 +713,15 @@ class TemplateRef<T> implements viewEngine_TemplateRef<T> {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
elementRef: viewEngine_ElementRef, template: ComponentTemplate<T>,
|
elementRef: viewEngine_ElementRef, template: ComponentTemplate<T>,
|
||||||
private _renderer: Renderer3) {
|
private _renderer: Renderer3, private _directives: DirectiveDefList|null,
|
||||||
|
private _pipes: PipeDefList|null) {
|
||||||
this.elementRef = elementRef;
|
this.elementRef = elementRef;
|
||||||
this._template = template;
|
this._template = template;
|
||||||
}
|
}
|
||||||
|
|
||||||
createEmbeddedView(context: T): viewEngine_EmbeddedViewRef<T> {
|
createEmbeddedView(context: T): viewEngine_EmbeddedViewRef<T> {
|
||||||
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));
|
return addDestroyable(new EmbeddedViewRef(viewNode, this._template, context));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,7 @@ import {LContainerNode, LElementNode, LNode, LNodeType, TNodeFlags, LProjectionN
|
|||||||
import {assertNodeType} from './node_assert';
|
import {assertNodeType} from './node_assert';
|
||||||
import {appendChild, insertChild, insertView, appendProjectedNode, removeView, canInsertNativeNode, createTextNode} from './node_manipulation';
|
import {appendChild, insertChild, insertView, appendProjectedNode, removeView, canInsertNativeNode, createTextNode} from './node_manipulation';
|
||||||
import {isNodeMatchingSelectorList, matchingSelectorIndex} from './node_selector_matcher';
|
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 {RElement, RText, Renderer3, RendererFactory3, ProceduralRenderer3, ObjectOrientedRenderer3, RendererStyleFlags3, isProceduralRenderer} from './interfaces/renderer';
|
||||||
import {isDifferent, stringify} from './util';
|
import {isDifferent, stringify} from './util';
|
||||||
import {executeHooks, queueLifecycleHooks, queueInitHooks, executeInitHooks} from './hooks';
|
import {executeHooks, queueLifecycleHooks, queueInitHooks, executeInitHooks} from './hooks';
|
||||||
@ -450,8 +450,8 @@ export function renderTemplate<T>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function renderEmbeddedTemplate<T>(
|
export function renderEmbeddedTemplate<T>(
|
||||||
viewNode: LViewNode | null, template: ComponentTemplate<T>, context: T,
|
viewNode: LViewNode | null, template: ComponentTemplate<T>, context: T, renderer: Renderer3,
|
||||||
renderer: Renderer3): LViewNode {
|
directives?: DirectiveDefList | null, pipes?: PipeDefList | null): LViewNode {
|
||||||
const _isParent = isParent;
|
const _isParent = isParent;
|
||||||
const _previousOrParentNode = previousOrParentNode;
|
const _previousOrParentNode = previousOrParentNode;
|
||||||
let oldView: LView;
|
let oldView: LView;
|
||||||
@ -460,11 +460,7 @@ export function renderEmbeddedTemplate<T>(
|
|||||||
previousOrParentNode = null !;
|
previousOrParentNode = null !;
|
||||||
let rf: RenderFlags = RenderFlags.Update;
|
let rf: RenderFlags = RenderFlags.Update;
|
||||||
if (viewNode == null) {
|
if (viewNode == null) {
|
||||||
// TODO: revisit setting currentView when re-writing view containers
|
const tView = getOrCreateTView(template, directives || null, pipes || null);
|
||||||
const directives = currentView && currentView.tView.directiveRegistry;
|
|
||||||
const pipes = currentView && currentView.tView.pipeRegistry;
|
|
||||||
|
|
||||||
const tView = getOrCreateTView(template, directives, pipes);
|
|
||||||
const lView = createLView(-1, renderer, tView, template, context, LViewFlags.CheckAlways);
|
const lView = createLView(-1, renderer, tView, template, context, LViewFlags.CheckAlways);
|
||||||
|
|
||||||
viewNode = createLNode(null, LNodeType.View, null, lView);
|
viewNode = createLNode(null, LNodeType.View, null, lView);
|
||||||
@ -1474,6 +1470,7 @@ function refreshDynamicChildren() {
|
|||||||
const container = current as LContainer;
|
const container = current as LContainer;
|
||||||
for (let i = 0; i < container.views.length; i++) {
|
for (let i = 0; i < container.views.length; i++) {
|
||||||
const view = container.views[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);
|
renderEmbeddedTemplate(view, view.data.template !, view.data.context !, renderer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,11 +6,12 @@
|
|||||||
* found in the LICENSE file at https://angular.io/license
|
* 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 {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 {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 {RenderFlags} from '../../src/render3/interfaces/definition';
|
||||||
|
import {pipe, pipeBind1} from '../../src/render3/pipe';
|
||||||
|
|
||||||
import {ComponentFixture, TemplateFixture} from './render_util';
|
import {ComponentFixture, TemplateFixture} from './render_util';
|
||||||
|
|
||||||
@ -334,6 +335,89 @@ describe('ViewContainerRef', () => {
|
|||||||
fixture.update();
|
fixture.update();
|
||||||
expect(fixture.html).toEqual('before|AAB|after');
|
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: `
|
||||||
|
<ng-template #foo>
|
||||||
|
<child [name]="'C' | starPipe"></child>
|
||||||
|
</ng-template>
|
||||||
|
<child vcref [tplRef]="foo" [name]="'A' | starPipe"></child>
|
||||||
|
<child [name]="'B' | starPipe"></child>
|
||||||
|
`
|
||||||
|
})
|
||||||
|
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('<child vcref="">**A**</child><child>**C**</child><child>**B**</child>');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('detach', () => {
|
describe('detach', () => {
|
||||||
|
Reference in New Issue
Block a user