Skip to content

Commit

Permalink
fix(ivy): support ng-container inside another ng-container
Browse files Browse the repository at this point in the history
  • Loading branch information
pkozlowski-opensource committed Aug 7, 2018
1 parent 2505c07 commit 8de4940
Show file tree
Hide file tree
Showing 2 changed files with 117 additions and 2 deletions.
17 changes: 15 additions & 2 deletions packages/core/src/render3/node_manipulation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ function walkLNodeTree(
nextNode = head ? (componentHost.data as LViewData)[PARENT] ![head.index] : null;
} else {
// Otherwise look at the first child
nextNode = getChildLNode(node as LViewNode);
nextNode = getChildLNode(node as LViewNode | LElementContainerNode);
}

if (nextNode === null) {
Expand Down Expand Up @@ -532,6 +532,16 @@ function canInsertNativeChildOfElement(parent: LElementNode, currentView: LViewD
return false;
}

/**
* We might delay insertion of children for a given view if it is disconnected.
* This might happen for 2 main reason:
* - view is not inserted into any container (view was created but not iserted yet)
* - view is inserted into a container but the container itself is not inserted into the DOM
* (container might be part of projection or child of a view that is not inserted yet).
*
* In other words we can insert children of a given view this view was inserted into a container and
* the container itself has it render parent determined.
*/
function canInsertNativeChildOfView(parent: LViewNode): boolean {
ngDevMode && assertNodeType(parent, TNodeType.View);

Expand Down Expand Up @@ -635,7 +645,10 @@ export function appendChild(parent: LNode, child: RNode | null, currentView: LVi
nativeInsertBefore(renderer, renderParent !.native, child, beforeNode);
} else if (parent.tNode.type === TNodeType.ElementContainer) {
const beforeNode = parent.native;
const grandParent = getParentLNode(parent) as LElementNode | LViewNode;
let grandParent = getParentLNode(parent as LElementContainerNode);
while (grandParent.tNode.type === TNodeType.ElementContainer) {
grandParent = getParentLNode(grandParent as LElementContainerNode);
}
if (grandParent.tNode.type === TNodeType.View) {
const renderParent = getRenderParent(grandParent as LViewNode);
nativeInsertBefore(renderer, renderParent !.native, child, beforeNode);
Expand Down
102 changes: 102 additions & 0 deletions packages/core/test/render3/integration_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -651,6 +651,108 @@ describe('render3 integration test', () => {
expect(fixture.html).toEqual('<test-cmpt>component template</test-cmpt>');
});

it('should render inside another ng-container', () => {
/**
* <ng-container>
* <ng-container>
* <ng-container>
* content
* </ng-container>
* </ng-container>
* </ng-container>
*/
const TestCmpt = createComponent('test-cmpt', function(rf: RenderFlags) {
if (rf & RenderFlags.Create) {
elementContainerStart(0);
{
elementContainerStart(1);
{
elementContainerStart(2);
{ text(3, 'content'); }
elementContainerEnd();
}
elementContainerEnd();
}
elementContainerEnd();
}
});

function App() { element(0, 'test-cmpt'); }

const fixture = new TemplateFixture(App, () => {}, [TestCmpt]);
expect(fixture.html).toEqual('<test-cmpt>content</test-cmpt>');
});

it('should render inside another ng-container at the root of a delayed view', () => {

class TestDirective {
constructor(private _tplRef: TemplateRef<any>, private _vcRef: ViewContainerRef) {}

createAndInsert() { this._vcRef.insert(this._tplRef.createEmbeddedView({})); }

clear() { this._vcRef.clear(); }

static ngDirectiveDef = defineDirective({
type: TestDirective,
selectors: [['', 'testDirective', '']],
factory: () => new TestDirective(injectTemplateRef(), injectViewContainerRef()),
});
}

let testDirective: TestDirective;

function embeddedTemplate(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
elementContainerStart(0);
{
elementContainerStart(1);
{
elementContainerStart(2);
{ text(3, 'content'); }
elementContainerEnd();
}
elementContainerEnd();
}
elementContainerEnd();
}
}

`<ng-template testDirective>
<ng-container>
<ng-container>
<ng-container>
content
</ng-container>
</ng-container>
</ng-container>
</ng-template>`;
const TestCmpt = createComponent('test-cmpt', function(rf: RenderFlags) {
if (rf & RenderFlags.Create) {
container(0, embeddedTemplate, null, [AttributeMarker.SelectOnly, 'testDirective']);
}
if (rf & RenderFlags.Update) {
testDirective = loadDirective<TestDirective>(0);
}
}, [TestDirective]);

function App() { element(0, 'test-cmpt'); }

const fixture = new ComponentFixture(TestCmpt);
expect(fixture.html).toEqual('');

testDirective !.createAndInsert();
fixture.update();
expect(fixture.html).toEqual('content');

testDirective !.createAndInsert();
fixture.update();
expect(fixture.html).toEqual('contentcontent');

testDirective !.clear();
fixture.update();
expect(fixture.html).toEqual('');
});

it('should support directives and inject ElementRef', () => {

class Directive {
Expand Down

0 comments on commit 8de4940

Please sign in to comment.