Skip to content

Commit

Permalink
fix(common): properly get root nodes from embedded views with <ng-con…
Browse files Browse the repository at this point in the history
…tent>

This commit fixes 2 separate issues related to root nodes retrieval from
embedded views with `<ng-content>`:

1) we did not account for the case where there were no projectable nodes
for a given `<ng-content>`;

2) we did not account for the case where projectable nodes for a given
`<ng-content>` were represented as an array of native nodes (happens in
the case of dynamically created components with projectable nodes);

Fixes angular#35967
  • Loading branch information
pkozlowski-opensource committed Mar 21, 2020
1 parent 528e25a commit 5aac6fc
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 7 deletions.
2 changes: 1 addition & 1 deletion packages/core/src/render3/component_ref.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ export class ComponentFactory<T> extends viewEngine_ComponentFactory<T> {
}
}

tElementNode = getTNode(rootLView[TVIEW], 0) as TElementNode;
tElementNode = getTNode(rootTView, 0) as TElementNode;

if (projectableNodes) {
// projectable nodes can be passed as array of arrays or an array of iterables (ngUpgrade
Expand Down
18 changes: 13 additions & 5 deletions packages/core/src/render3/view_ref.ts
Original file line number Diff line number Diff line change
Expand Up @@ -335,11 +335,19 @@ function collectNativeNodes(
} else if (tNodeType === TNodeType.Projection) {
const componentView = lView[DECLARATION_COMPONENT_VIEW];
const componentHost = componentView[T_HOST] as TElementNode;
const parentView = getLViewParent(componentView);
let firstProjectedNode: TNode|null =
(componentHost.projection as(TNode | null)[])[tNode.projection as number];
if (firstProjectedNode !== null && parentView !== null) {
collectNativeNodes(parentView[TVIEW], parentView, firstProjectedNode, result, true);
const projectionSlots = componentHost.projection;
const slotIdx = tNode.projection as number;

if (projectionSlots !== null && projectionSlots.length > slotIdx) {
const nodesInSlot = projectionSlots[slotIdx];
if (Array.isArray(nodesInSlot)) {
result.push(...nodesInSlot);
} else {
const parentView = getLViewParent(componentView);
if (parentView !== null) {
collectNativeNodes(parentView[TVIEW], parentView, nodesInSlot, result, true);
}
}
}
}
tNode = isProjection ? tNode.projectionNext : tNode.next;
Expand Down
47 changes: 46 additions & 1 deletion packages/core/test/acceptance/template_ref_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/

import {Component, TemplateRef, ViewChild, ViewContainerRef} from '@angular/core';
import {Component, ComponentFactoryResolver, Injector, NgModule, TemplateRef, ViewChild, ViewContainerRef} from '@angular/core';
import {TestBed} from '@angular/core/testing';
import {expect} from '@angular/platform-browser/testing/src/matchers';
import {ivyEnabled, onlyInIvy} from '@angular/private/testing';
Expand Down Expand Up @@ -182,5 +182,50 @@ describe('TemplateRef', () => {
expect(rootNodes.length).toBe(7);
}
});

it('should return an empty array for an embedded view with projection and no projectable nodes',
() => {
const rootNodes =
getRootNodes(`<ng-template #templateRef><ng-content></ng-content></ng-template>`);
// VE will, incorrectly, return an additional comment node in this case
expect(rootNodes.length).toBe(ivyEnabled ? 0 : 1);
});

it('should return projectable nodes provided to a dynamically created component', () => {

@Component({
selector: 'dynamic',
template: '<ng-template #templateRef><ng-content></ng-content></ng-template>'
})
class DynamicCmp {
@ViewChild('templateRef', {static: true})
templateRef !: TemplateRef<any>;
}

@NgModule({
declarations: [DynamicCmp],
entryComponents: [DynamicCmp],
})
class WithDynamicCmpModule {
}

@Component({selector: 'test', template: ''})
class TestCmp {
constructor(public cfr: ComponentFactoryResolver) {}
}

TestBed.configureTestingModule({
declarations: [TestCmp],
imports: [WithDynamicCmpModule],
});
const fixture = TestBed.createComponent(TestCmp);
const dynamicCmptFactory = fixture.componentInstance.cfr.resolveComponentFactory(DynamicCmp);

const cmptRef =
dynamicCmptFactory.create(Injector.NULL, [[document.createTextNode('textNode')]]);
const viewRef = cmptRef.instance.templateRef.createEmbeddedView({});
// VE will, incorrectly, return an additional comment node in this case
expect(viewRef.rootNodes.length).toBe(ivyEnabled ? 1 : 2);
});
});
});

0 comments on commit 5aac6fc

Please sign in to comment.