Skip to content

Commit

Permalink
feat(ivy): support projection of ViewContainerRef (angular#23272)
Browse files Browse the repository at this point in the history
PR Close angular#23272
  • Loading branch information
marclaval authored and IgorMinar committed Apr 9, 2018
1 parent 37c1634 commit bb3f0e5
Show file tree
Hide file tree
Showing 2 changed files with 205 additions and 18 deletions.
3 changes: 3 additions & 0 deletions packages/core/src/render3/node_manipulation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
220 changes: 202 additions & 18 deletions packages/core/test/render3/view_container_ref_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,39 +6,39 @@
* 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', () => {
describe('API', () => {
let directiveInstance: DirectiveWithVCRef|null;
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<{}>;

beforeEach(() => { directiveInstance = null; });
constructor(public vcref: ViewContainerRef) {}
}

describe('API', () => {
function embeddedTemplate(ctx: any, cm: boolean) {
if (cm) {
text(0);
}
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);
}
Expand Down Expand Up @@ -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>');
});
});
});

0 comments on commit bb3f0e5

Please sign in to comment.