feat(ivy): support projection of ViewContainerRef (#23272)
PR Close #23272
This commit is contained in:
parent
37c1634276
commit
bb3f0e5ed2
@ -490,4 +490,7 @@ export function appendProjectedNode(
|
|||||||
addRemoveViewFromContainer(node as LContainerNode, views[i], true, null);
|
addRemoveViewFromContainer(node as LContainerNode, views[i], true, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (node.dynamicLContainerNode) {
|
||||||
|
node.dynamicLContainerNode.data.renderParent = currentParent as LElementNode;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,26 +6,18 @@
|
|||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {Directive, TemplateRef, ViewContainerRef} from '../../src/core';
|
import {Component, Directive, 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, injectTemplateRef, injectViewContainerRef} from '../../src/render3/index';
|
||||||
import {bind, container, containerRefreshEnd, containerRefreshStart, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, interpolation1, load, loadDirective, 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 {ComponentFixture, TemplateFixture} from './render_util';
|
import {ComponentFixture, TemplateFixture} from './render_util';
|
||||||
|
|
||||||
describe('ViewContainerRef', () => {
|
describe('ViewContainerRef', () => {
|
||||||
describe('API', () => {
|
|
||||||
let directiveInstance: DirectiveWithVCRef|null;
|
let directiveInstance: DirectiveWithVCRef|null;
|
||||||
|
|
||||||
beforeEach(() => { directiveInstance = null; });
|
beforeEach(() => { directiveInstance = null; });
|
||||||
|
|
||||||
function embeddedTemplate(ctx: any, cm: boolean) {
|
|
||||||
if (cm) {
|
|
||||||
text(0);
|
|
||||||
}
|
|
||||||
textBinding(0, ctx.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
class DirectiveWithVCRef {
|
class DirectiveWithVCRef {
|
||||||
static ngDirectiveDef = defineDirective({
|
static ngDirectiveDef = defineDirective({
|
||||||
type: DirectiveWithVCRef,
|
type: DirectiveWithVCRef,
|
||||||
@ -39,6 +31,14 @@ describe('ViewContainerRef', () => {
|
|||||||
constructor(public vcref: ViewContainerRef) {}
|
constructor(public vcref: ViewContainerRef) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
describe('API', () => {
|
||||||
|
function embeddedTemplate(ctx: any, cm: boolean) {
|
||||||
|
if (cm) {
|
||||||
|
text(0);
|
||||||
|
}
|
||||||
|
textBinding(0, ctx.name);
|
||||||
|
}
|
||||||
|
|
||||||
function createView(s: string, index?: number) {
|
function createView(s: string, index?: number) {
|
||||||
directiveInstance !.vcref.createEmbeddedView(directiveInstance !.tplRef, {name: s}, index);
|
directiveInstance !.vcref.createEmbeddedView(directiveInstance !.tplRef, {name: s}, index);
|
||||||
}
|
}
|
||||||
@ -452,4 +452,188 @@ describe('ViewContainerRef', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('projection', () => {
|
||||||
|
function embeddedTemplate(ctx: any, cm: boolean) {
|
||||||
|
if (cm) {
|
||||||
|
elementStart(0, 'span');
|
||||||
|
text(1);
|
||||||
|
elementEnd();
|
||||||
|
}
|
||||||
|
textBinding(1, ctx.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
it('should project the ViewContainerRef content along its host', () => {
|
||||||
|
@Component({selector: 'child', template: '<div><ng-content></ng-content></div>'})
|
||||||
|
class Child {
|
||||||
|
static ngComponentDef = defineComponent({
|
||||||
|
type: Child,
|
||||||
|
selectors: [['child']],
|
||||||
|
factory: () => new Child(),
|
||||||
|
template: (cmp: Child, cm: boolean) => {
|
||||||
|
if (cm) {
|
||||||
|
projectionDef(0);
|
||||||
|
elementStart(1, 'div');
|
||||||
|
{ projection(2, 0); }
|
||||||
|
elementEnd();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'parent',
|
||||||
|
template: `
|
||||||
|
<ng-template #foo>
|
||||||
|
<span>{{name}}</span>
|
||||||
|
</ng-template>
|
||||||
|
<child><header vcref [tplRef]="foo" [name]="name">blah</header></child>`
|
||||||
|
})
|
||||||
|
class Parent {
|
||||||
|
name: string = 'bar';
|
||||||
|
static ngComponentDef = defineComponent({
|
||||||
|
type: Parent,
|
||||||
|
selectors: [['parent']],
|
||||||
|
factory: () => new Parent(),
|
||||||
|
template: (cmp: Parent, cm: boolean) => {
|
||||||
|
if (cm) {
|
||||||
|
container(0, embeddedTemplate);
|
||||||
|
elementStart(1, 'child');
|
||||||
|
elementStart(2, 'header', ['vcref', '']);
|
||||||
|
text(3, 'blah');
|
||||||
|
elementEnd();
|
||||||
|
elementEnd();
|
||||||
|
}
|
||||||
|
const tplRef = getOrCreateTemplateRef(getOrCreateNodeInjectorForNode(load(0)));
|
||||||
|
elementProperty(2, 'tplRef', bind(tplRef));
|
||||||
|
elementProperty(2, 'name', bind(cmp.name));
|
||||||
|
},
|
||||||
|
directives: [Child, DirectiveWithVCRef]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const fixture = new ComponentFixture(Parent);
|
||||||
|
expect(fixture.html).toEqual('<child><div><header vcref="">blah</header></div></child>');
|
||||||
|
|
||||||
|
directiveInstance !.vcref.createEmbeddedView(directiveInstance !.tplRef, fixture.component);
|
||||||
|
fixture.update();
|
||||||
|
expect(fixture.html)
|
||||||
|
.toEqual('<child><div><header vcref="">blah</header><span>bar</span></div></child>');
|
||||||
|
});
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'child-with-selector',
|
||||||
|
template: `
|
||||||
|
<first><ng-content select="header"></ng-content></first>
|
||||||
|
<second><ng-content></ng-content></second>`
|
||||||
|
})
|
||||||
|
class ChildWithSelector {
|
||||||
|
static ngComponentDef = defineComponent({
|
||||||
|
type: ChildWithSelector,
|
||||||
|
selectors: [['child-with-selector']],
|
||||||
|
factory: () => new ChildWithSelector(),
|
||||||
|
template: (cmp: ChildWithSelector, cm: boolean) => {
|
||||||
|
if (cm) {
|
||||||
|
projectionDef(0, [[['header']]], ['header']);
|
||||||
|
elementStart(1, 'first');
|
||||||
|
{ projection(2, 0, 1); }
|
||||||
|
elementEnd();
|
||||||
|
elementStart(3, 'second');
|
||||||
|
{ projection(4, 0); }
|
||||||
|
elementEnd();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
it('should project the ViewContainerRef content along its host, when the host matches a selector',
|
||||||
|
() => {
|
||||||
|
@Component({
|
||||||
|
selector: 'parent',
|
||||||
|
template: `
|
||||||
|
<ng-template #foo>
|
||||||
|
<span>{{name}}</span>
|
||||||
|
</ng-template>
|
||||||
|
<child-with-selector><header vcref [tplRef]="foo" [name]="name">blah</header></child-with-selector>`
|
||||||
|
})
|
||||||
|
class Parent {
|
||||||
|
name: string = 'bar';
|
||||||
|
static ngComponentDef = defineComponent({
|
||||||
|
type: Parent,
|
||||||
|
selectors: [['parent']],
|
||||||
|
factory: () => new Parent(),
|
||||||
|
template: (cmp: Parent, cm: boolean) => {
|
||||||
|
if (cm) {
|
||||||
|
container(0, embeddedTemplate);
|
||||||
|
elementStart(1, 'child-with-selector');
|
||||||
|
elementStart(2, 'header', ['vcref', '']);
|
||||||
|
text(3, 'blah');
|
||||||
|
elementEnd();
|
||||||
|
elementEnd();
|
||||||
|
}
|
||||||
|
const tplRef = getOrCreateTemplateRef(getOrCreateNodeInjectorForNode(load(0)));
|
||||||
|
elementProperty(2, 'tplRef', bind(tplRef));
|
||||||
|
elementProperty(2, 'name', bind(cmp.name));
|
||||||
|
},
|
||||||
|
directives: [ChildWithSelector, DirectiveWithVCRef]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const fixture = new ComponentFixture(Parent);
|
||||||
|
expect(fixture.html)
|
||||||
|
.toEqual(
|
||||||
|
'<child-with-selector><first><header vcref="">blah</header></first><second></second></child-with-selector>');
|
||||||
|
|
||||||
|
directiveInstance !.vcref.createEmbeddedView(
|
||||||
|
directiveInstance !.tplRef, fixture.component);
|
||||||
|
fixture.update();
|
||||||
|
expect(fixture.html)
|
||||||
|
.toEqual(
|
||||||
|
'<child-with-selector><first><header vcref="">blah</header><span>bar</span></first><second></second></child-with-selector>');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not project the ViewContainerRef content, when the host does not match a selector', () => {
|
||||||
|
@Component({
|
||||||
|
selector: 'parent',
|
||||||
|
template: `
|
||||||
|
<ng-template #foo>
|
||||||
|
<span>{{name}}</span>
|
||||||
|
</ng-template>
|
||||||
|
<child-with-selector><footer vcref [tplRef]="foo" [name]="name">blah</footer></child-with-selector>`
|
||||||
|
})
|
||||||
|
class Parent {
|
||||||
|
name: string = 'bar';
|
||||||
|
static ngComponentDef = defineComponent({
|
||||||
|
type: Parent,
|
||||||
|
selectors: [['parent']],
|
||||||
|
factory: () => new Parent(),
|
||||||
|
template: (cmp: Parent, cm: boolean) => {
|
||||||
|
if (cm) {
|
||||||
|
container(0, embeddedTemplate);
|
||||||
|
elementStart(1, 'child-with-selector');
|
||||||
|
elementStart(2, 'footer', ['vcref', '']);
|
||||||
|
text(3, 'blah');
|
||||||
|
elementEnd();
|
||||||
|
elementEnd();
|
||||||
|
}
|
||||||
|
const tplRef = getOrCreateTemplateRef(getOrCreateNodeInjectorForNode(load(0)));
|
||||||
|
elementProperty(2, 'tplRef', bind(tplRef));
|
||||||
|
elementProperty(2, 'name', bind(cmp.name));
|
||||||
|
},
|
||||||
|
directives: [ChildWithSelector, DirectiveWithVCRef]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const fixture = new ComponentFixture(Parent);
|
||||||
|
expect(fixture.html)
|
||||||
|
.toEqual(
|
||||||
|
'<child-with-selector><first></first><second><footer vcref="">blah</footer></second></child-with-selector>');
|
||||||
|
|
||||||
|
directiveInstance !.vcref.createEmbeddedView(directiveInstance !.tplRef, fixture.component);
|
||||||
|
fixture.update();
|
||||||
|
expect(fixture.html)
|
||||||
|
.toEqual(
|
||||||
|
'<child-with-selector><first></first><second><footer vcref="">blah</footer><span>bar</span></second></child-with-selector>');
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user