fix(core): properly get root nodes from embedded views with <ng-content> (#36051)
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 #35967 PR Close #36051
This commit is contained in:

committed by
Alex Rickabaugh

parent
fc6c3ae97d
commit
e30e1325f3
@ -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';
|
||||
@ -180,5 +180,97 @@ 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 an empty array for an embedded view with multiple projections and no projectable nodes',
|
||||
() => {
|
||||
const rootNodes = getRootNodes(
|
||||
`<ng-template #templateRef><ng-content></ng-content><ng-content select="foo"></ng-content></ng-template>`);
|
||||
// VE will, incorrectly, return an additional comment node in this case
|
||||
expect(rootNodes.length).toBe(ivyEnabled ? 0 : 1);
|
||||
});
|
||||
|
||||
describe('projectable nodes provided to a dynamically created component', () => {
|
||||
@Component({selector: 'dynamic', 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) {}
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [TestCmp],
|
||||
imports: [WithDynamicCmpModule],
|
||||
});
|
||||
});
|
||||
|
||||
it('should return projectable nodes when provided', () => {
|
||||
TestBed.overrideTemplate(
|
||||
DynamicCmp, `<ng-template #templateRef><ng-content></ng-content></ng-template>`);
|
||||
|
||||
const fixture = TestBed.createComponent(TestCmp);
|
||||
const dynamicCmptFactory =
|
||||
fixture.componentInstance.cfr.resolveComponentFactory(DynamicCmp);
|
||||
|
||||
// Number of projectable nodes matches the number of slots - all nodes should be returned
|
||||
const projectableNodes = [[document.createTextNode('textNode')]];
|
||||
const cmptRef = dynamicCmptFactory.create(Injector.NULL, projectableNodes);
|
||||
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);
|
||||
});
|
||||
|
||||
it('should return an empty collection when no projectable nodes were provided', () => {
|
||||
TestBed.overrideTemplate(
|
||||
DynamicCmp, `<ng-template #templateRef><ng-content></ng-content></ng-template>`);
|
||||
|
||||
const fixture = TestBed.createComponent(TestCmp);
|
||||
const dynamicCmptFactory =
|
||||
fixture.componentInstance.cfr.resolveComponentFactory(DynamicCmp);
|
||||
|
||||
// There are slots but projectable nodes were not provided - nothing should be returned
|
||||
const cmptRef = dynamicCmptFactory.create(Injector.NULL, []);
|
||||
const viewRef = cmptRef.instance.templateRef.createEmbeddedView({});
|
||||
|
||||
// VE will, incorrectly, return an additional comment node in this case
|
||||
expect(viewRef.rootNodes.length).toBe(ivyEnabled ? 0 : 1);
|
||||
});
|
||||
|
||||
it('should return an empty collection when projectable nodes were provided but there are no slots',
|
||||
() => {
|
||||
TestBed.overrideTemplate(DynamicCmp, `<ng-template #templateRef></ng-template>`);
|
||||
|
||||
const fixture = TestBed.createComponent(TestCmp);
|
||||
const dynamicCmptFactory =
|
||||
fixture.componentInstance.cfr.resolveComponentFactory(DynamicCmp);
|
||||
|
||||
// There are no slots but projectable were provided - nothing should be returned
|
||||
const projectableNodes = [[document.createTextNode('textNode')]];
|
||||
const cmptRef = dynamicCmptFactory.create(Injector.NULL, projectableNodes);
|
||||
const viewRef = cmptRef.instance.templateRef.createEmbeddedView({});
|
||||
|
||||
// VE will, incorrectly, return an additional comment node in this case
|
||||
expect(viewRef.rootNodes.length).toBe(ivyEnabled ? 0 : 1);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
Reference in New Issue
Block a user