feat(ivy): support projection of ViewContainerRef (#23272)

PR Close #23272
This commit is contained in:
Marc Laval 2018-04-09 17:35:50 +02:00 committed by Igor Minar
parent 37c1634276
commit bb3f0e5ed2
2 changed files with 206 additions and 19 deletions

View File

@ -490,4 +490,7 @@ export function appendProjectedNode(
addRemoveViewFromContainer(node as LContainerNode, views[i], true, null);
}
}
if (node.dynamicLContainerNode) {
node.dynamicLContainerNode.data.renderParent = currentParent as LElementNode;
}
}

View File

@ -6,19 +6,32 @@
* 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 {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';
describe('ViewContainerRef', () => {
let directiveInstance: DirectiveWithVCRef|null;
beforeEach(() => { directiveInstance = null; });
class DirectiveWithVCRef {
static ngDirectiveDef = defineDirective({
type: DirectiveWithVCRef,
selectors: [['', 'vcref', '']],
factory: () => directiveInstance = new DirectiveWithVCRef(injectViewContainerRef()),
inputs: {tplRef: 'tplRef'}
});
tplRef: TemplateRef<{}>;
constructor(public vcref: ViewContainerRef) {}
}
describe('API', () => {
let directiveInstance: DirectiveWithVCRef|null;
beforeEach(() => { directiveInstance = null; });
function embeddedTemplate(ctx: any, cm: boolean) {
if (cm) {
text(0);
@ -26,19 +39,6 @@ describe('ViewContainerRef', () => {
textBinding(0, ctx.name);
}
class DirectiveWithVCRef {
static ngDirectiveDef = defineDirective({
type: DirectiveWithVCRef,
selectors: [['', 'vcref', '']],
factory: () => directiveInstance = new DirectiveWithVCRef(injectViewContainerRef()),
inputs: {tplRef: 'tplRef'}
});
tplRef: TemplateRef<{}>;
constructor(public vcref: ViewContainerRef) {}
}
function createView(s: string, index?: number) {
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>');
});
});
});