fix(ivy): support projecting into dynamic views (#24752)

PR Close #24752
This commit is contained in:
Kara Erickson 2018-07-03 10:03:48 -07:00 committed by Miško Hevery
parent 49df4ef454
commit dc1f1295ee
2 changed files with 171 additions and 7 deletions

View File

@ -1963,12 +1963,9 @@ export function projectionDef(
// execute selector matching logic if and only if:
// - there are selectors defined
// - a node has a tag name / attributes that can be matched
if (selectors) {
const matchedIdx = matchingSelectorIndex(componentChild.tNode, selectors, textSelectors !);
distributedNodes[matchedIdx].push(componentChild);
} else {
distributedNodes[0].push(componentChild);
}
const bucketIndex =
selectors ? matchingSelectorIndex(componentChild.tNode, selectors, textSelectors !) : 0;
distributedNodes[bucketIndex].push(componentChild);
componentChild = getNextLNode(componentChild);
}
@ -2031,8 +2028,10 @@ export function projection(
const currentParent = getParentLNode(node);
const canInsert = canInsertNativeNode(currentParent, viewData);
let grandparent: LContainerNode;
const renderParent = currentParent.tNode.type === TNodeType.View ?
(getParentLNode(currentParent) as LContainerNode).data[RENDER_PARENT] ! :
(grandparent = getParentLNode(currentParent) as LContainerNode) &&
grandparent.data[RENDER_PARENT] ! :
currentParent as LElementNode;
for (let i = 0; i < nodesForSelector.length; i++) {

View File

@ -706,6 +706,171 @@ describe('content projection', () => {
expect(toHtml(parent)).toEqual('<child><div></div></child>');
});
it('should project into dynamic views (with createEmbeddedView)', () => {
class NgIf {
constructor(public vcr: ViewContainerRef, public template: TemplateRef<any>) {}
@Input()
set ngIf(value: boolean) {
value ? this.vcr.createEmbeddedView(this.template) : this.vcr.clear();
}
static ngDirectiveDef = defineDirective({
type: NgIf,
selectors: [['', 'ngIf', '']],
inputs: {'ngIf': 'ngIf'},
factory: () => new NgIf(injectViewContainerRef(), injectTemplateRef())
});
}
/**
* Before-
* <ng-template [ngIf]="showing">
* <ng-content></ng-content>
* </ng-template>
* -After
*/
const Child = createComponent('child', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
projectionDef(0);
text(1, 'Before-');
container(2, IfTemplate, '', [AttributeMarker.SelectOnly, 'ngIf']);
text(3, '-After');
}
if (rf & RenderFlags.Update) {
elementProperty(2, 'ngIf', bind(ctx.showing));
}
function IfTemplate(rf1: RenderFlags, ctx1: any) {
if (rf1 & RenderFlags.Create) {
projectionDef(0);
projection(1, 0);
}
}
}, [NgIf]);
let child: {showing: boolean};
/**
* <child>
* <div>A</div>
* Some text
* </child>
*/
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
elementStart(0, 'child');
{
elementStart(1, 'div');
{ text(2, 'A'); }
elementEnd();
text(3, 'Some text');
}
elementEnd();
// testing
child = loadDirective(0);
}
}, [Child]);
const fixture = new ComponentFixture(App);
child !.showing = true;
fixture.update();
expect(fixture.html).toEqual('<child>Before-<div>A</div>Some text-After</child>');
child !.showing = false;
fixture.update();
expect(fixture.html).toEqual('<child>Before--After</child>');
child !.showing = true;
fixture.update();
expect(fixture.html).toEqual('<child>Before-<div>A</div>Some text-After</child>');
});
it('should project into dynamic views (with insertion)', () => {
class NgIf {
constructor(public vcr: ViewContainerRef, public template: TemplateRef<any>) {}
@Input()
set ngIf(value: boolean) {
if (value) {
const viewRef = this.template.createEmbeddedView({});
this.vcr.insert(viewRef);
} else {
this.vcr.clear();
}
}
static ngDirectiveDef = defineDirective({
type: NgIf,
selectors: [['', 'ngIf', '']],
inputs: {'ngIf': 'ngIf'},
factory: () => new NgIf(injectViewContainerRef(), injectTemplateRef())
});
}
/**
* Before-
* <ng-template [ngIf]="showing">
* <ng-content></ng-content>
* </ng-template>
* -After
*/
const Child = createComponent('child', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
projectionDef(0);
text(1, 'Before-');
container(2, IfTemplate, '', [AttributeMarker.SelectOnly, 'ngIf']);
text(3, '-After');
}
if (rf & RenderFlags.Update) {
elementProperty(2, 'ngIf', bind(ctx.showing));
}
function IfTemplate(rf1: RenderFlags, ctx1: any) {
if (rf1 & RenderFlags.Create) {
projectionDef(0);
projection(1, 0);
}
}
}, [NgIf]);
let child: {showing: boolean};
/**
* <child>
* <div>A</div>
* Some text
* </child>
*/
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
elementStart(0, 'child');
{
elementStart(1, 'div');
{ text(2, 'A'); }
elementEnd();
text(3, 'Some text');
}
elementEnd();
// testing
child = loadDirective(0);
}
}, [Child]);
const fixture = new ComponentFixture(App);
child !.showing = true;
fixture.update();
expect(fixture.html).toEqual('<child>Before-<div>A</div>Some text-After</child>');
child !.showing = false;
fixture.update();
expect(fixture.html).toEqual('<child>Before--After</child>');
child !.showing = true;
fixture.update();
expect(fixture.html).toEqual('<child>Before-<div>A</div>Some text-After</child>');
});
it('should project nodes into the last ng-content', () => {
/**
* <div><ng-content></ng-content></div>