refactor(ivy): clean projection support (#23287)

PR Close #23287
This commit is contained in:
Marc Laval 2018-04-10 17:37:11 +02:00 committed by Igor Minar
parent 8555a3a3cd
commit c973830d9a
3 changed files with 114 additions and 106 deletions

View File

@ -1710,12 +1710,14 @@ export function projection(
} }
if (canInsertNativeNode(currentParent, currentView)) { if (canInsertNativeNode(currentParent, currentView)) {
ngDevMode && assertNodeType(currentParent, LNodeType.Element);
// process each node in the list of projected nodes: // process each node in the list of projected nodes:
let nodeToProject: LNode|null = node.data.head; let nodeToProject: LNode|null = node.data.head;
const lastNodeToProject = node.data.tail; const lastNodeToProject = node.data.tail;
while (nodeToProject) { while (nodeToProject) {
appendProjectedNode( appendProjectedNode(
nodeToProject as LTextNode | LElementNode | LContainerNode, currentParent, currentView); nodeToProject as LTextNode | LElementNode | LContainerNode, currentParent as LElementNode,
currentView);
nodeToProject = nodeToProject === lastNodeToProject ? null : nodeToProject.pNextOrParent; nodeToProject = nodeToProject === lastNodeToProject ? null : nodeToProject.pNextOrParent;
} }
} }

View File

@ -473,24 +473,24 @@ export function insertChild(node: LNode, currentView: LView): void {
* @param currentView Current LView * @param currentView Current LView
*/ */
export function appendProjectedNode( export function appendProjectedNode(
node: LElementNode | LTextNode | LContainerNode, currentParent: LViewNode | LElementNode, node: LElementNode | LTextNode | LContainerNode, currentParent: LElementNode,
currentView: LView): void { currentView: LView): void {
if (node.type !== LNodeType.Container) { if (node.type !== LNodeType.Container) {
appendChild(currentParent, (node as LElementNode | LTextNode).native, currentView); appendChild(currentParent, (node as LElementNode | LTextNode).native, currentView);
} else if (canInsertNativeNode(currentParent, currentView)) { } else {
// The node we are adding is a Container and we are adding it to Element which // The node we are adding is a Container and we are adding it to Element which
// is not a component (no more re-projection). // is not a component (no more re-projection).
// Alternatively a container is projected at the root of a component's template // Alternatively a container is projected at the root of a component's template
// and can't be re-projected (as not content of any component). // and can't be re-projected (as not content of any component).
// Assignee the final projection location in those cases. // Assignee the final projection location in those cases.
const lContainer = (node as LContainerNode).data; const lContainer = (node as LContainerNode).data;
lContainer.renderParent = currentParent as LElementNode; lContainer.renderParent = currentParent;
const views = lContainer.views; const views = lContainer.views;
for (let i = 0; i < views.length; i++) { for (let i = 0; i < views.length; i++) {
addRemoveViewFromContainer(node as LContainerNode, views[i], true, null); addRemoveViewFromContainer(node as LContainerNode, views[i], true, null);
} }
} }
if (node.dynamicLContainerNode) { if (node.dynamicLContainerNode) {
node.dynamicLContainerNode.data.renderParent = currentParent as LElementNode; node.dynamicLContainerNode.data.renderParent = currentParent;
} }
} }

View File

@ -28,6 +28,8 @@ describe('ViewContainerRef', () => {
tplRef: TemplateRef<{}>; tplRef: TemplateRef<{}>;
// injecting a ViewContainerRef to create a dynamic container in which embedded views will be
// created
constructor(public vcref: ViewContainerRef) {} constructor(public vcref: ViewContainerRef) {}
} }
@ -521,119 +523,123 @@ describe('ViewContainerRef', () => {
.toEqual('<child><div><header vcref="">blah</header><span>bar</span></div></child>'); .toEqual('<child><div><header vcref="">blah</header><span>bar</span></div></child>');
}); });
@Component({ describe('with select', () => {
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({ @Component({
selector: 'parent', selector: 'child-with-selector',
template: ` template: `
<ng-template #foo> <first><ng-content select="header"></ng-content></first>
<span>{{name}}</span> <second><ng-content></ng-content></second>`
</ng-template>
<child-with-selector><footer vcref [tplRef]="foo" [name]="name">blah</footer></child-with-selector>`
}) })
class Parent { class ChildWithSelector {
name: string = 'bar';
static ngComponentDef = defineComponent({ static ngComponentDef = defineComponent({
type: Parent, type: ChildWithSelector,
selectors: [['parent']], selectors: [['child-with-selector']],
factory: () => new Parent(), factory: () => new ChildWithSelector(),
template: (cmp: Parent, cm: boolean) => { template: (cmp: ChildWithSelector, cm: boolean) => {
if (cm) { if (cm) {
container(0, embeddedTemplate); projectionDef(0, [[['header']]], ['header']);
elementStart(1, 'child-with-selector'); elementStart(1, 'first');
elementStart(2, 'footer', ['vcref', '']); { projection(2, 0, 1); }
text(3, 'blah');
elementEnd(); elementEnd();
elementStart(3, 'second');
{ projection(4, 0); }
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); it('should project the ViewContainerRef content along its host, when the host matches a selector',
expect(fixture.html) () => {
.toEqual( @Component({
'<child-with-selector><first></first><second><footer vcref="">blah</footer></second></child-with-selector>'); 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]
});
}
directiveInstance !.vcref.createEmbeddedView(directiveInstance !.tplRef, fixture.component); const fixture = new ComponentFixture(Parent);
fixture.update(); expect(fixture.html)
expect(fixture.html) .toEqual(
.toEqual( '<child-with-selector><first><header vcref="">blah</header></first><second></second></child-with-selector>');
'<child-with-selector><first></first><second><footer vcref="">blah</footer><span>bar</span></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>');
});
}); });
}); });
}); });