fix(ivy): implement rootNodes getter on ViewRef (#27095)
PR Close #27095
This commit is contained in:
parent
1c9e526a83
commit
ce5242462b
|
@ -120,31 +120,29 @@ describe('insert/remove', () => {
|
||||||
expect(cmpRef.instance.testToken).toBeNull();
|
expect(cmpRef.instance.testToken).toBeNull();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
fixmeIvy('can not pass projectable nodes') &&
|
it('should render projectable nodes, if supplied', async(() => {
|
||||||
it('should render projectable nodes, if supplied', async(() => {
|
const template = `<ng-template>projected foo</ng-template>${TEST_CMP_TEMPLATE}`;
|
||||||
const template = `<ng-template>projected foo</ng-template>${TEST_CMP_TEMPLATE}`;
|
TestBed.overrideComponent(TestComponent, {set: {template: template}})
|
||||||
TestBed.overrideComponent(TestComponent, {set: {template: template}})
|
.configureTestingModule({schemas: [NO_ERRORS_SCHEMA]});
|
||||||
.configureTestingModule({schemas: [NO_ERRORS_SCHEMA]});
|
|
||||||
|
|
||||||
TestBed
|
TestBed.overrideComponent(InjectedComponent, {set: {template: `<ng-content></ng-content>`}})
|
||||||
.overrideComponent(InjectedComponent, {set: {template: `<ng-content></ng-content>`}})
|
.configureTestingModule({schemas: [NO_ERRORS_SCHEMA]});
|
||||||
.configureTestingModule({schemas: [NO_ERRORS_SCHEMA]});
|
|
||||||
|
|
||||||
let fixture = TestBed.createComponent(TestComponent);
|
let fixture = TestBed.createComponent(TestComponent);
|
||||||
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
expect(fixture.nativeElement).toHaveText('');
|
expect(fixture.nativeElement).toHaveText('');
|
||||||
|
|
||||||
fixture.componentInstance.currentComponent = InjectedComponent;
|
fixture.componentInstance.currentComponent = InjectedComponent;
|
||||||
fixture.componentInstance.projectables =
|
fixture.componentInstance.projectables =
|
||||||
[fixture.componentInstance.vcRef
|
[fixture.componentInstance.vcRef
|
||||||
.createEmbeddedView(fixture.componentInstance.tplRefs.first)
|
.createEmbeddedView(fixture.componentInstance.tplRefs.first)
|
||||||
.rootNodes];
|
.rootNodes];
|
||||||
|
|
||||||
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
expect(fixture.nativeElement).toHaveText('projected foo');
|
expect(fixture.nativeElement).toHaveText('projected foo');
|
||||||
}));
|
}));
|
||||||
|
|
||||||
fixmeIvy('Runtime compiler is not loaded') &&
|
fixmeIvy('Runtime compiler is not loaded') &&
|
||||||
it('should resolve components from other modules, if supplied', async(() => {
|
it('should resolve components from other modules, if supplied', async(() => {
|
||||||
|
|
|
@ -12,10 +12,12 @@ import {ViewContainerRef as viewEngine_ViewContainerRef} from '../linker/view_co
|
||||||
import {EmbeddedViewRef as viewEngine_EmbeddedViewRef, InternalViewRef as viewEngine_InternalViewRef} from '../linker/view_ref';
|
import {EmbeddedViewRef as viewEngine_EmbeddedViewRef, InternalViewRef as viewEngine_InternalViewRef} from '../linker/view_ref';
|
||||||
|
|
||||||
import {checkNoChanges, checkNoChangesInRootView, detectChanges, detectChangesInRootView, markViewDirty, storeCleanupFn, viewAttached} from './instructions';
|
import {checkNoChanges, checkNoChangesInRootView, detectChanges, detectChangesInRootView, markViewDirty, storeCleanupFn, viewAttached} from './instructions';
|
||||||
import {TViewNode} from './interfaces/node';
|
import {TNode, TNodeType, TViewNode} from './interfaces/node';
|
||||||
import {FLAGS, LViewData, LViewFlags, PARENT} from './interfaces/view';
|
import {FLAGS, HOST, HOST_NODE, LViewData, LViewFlags, PARENT} from './interfaces/view';
|
||||||
import {destroyLView} from './node_manipulation';
|
import {destroyLView} from './node_manipulation';
|
||||||
import {getRendererFactory} from './state';
|
import {getRendererFactory} from './state';
|
||||||
|
import {getNativeByTNode} from './util';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Needed due to tsickle downleveling where multiple `implements` with classes creates
|
// Needed due to tsickle downleveling where multiple `implements` with classes creates
|
||||||
|
@ -38,8 +40,13 @@ export class ViewRef<T> implements viewEngine_EmbeddedViewRef<T>, viewEngine_Int
|
||||||
*/
|
*/
|
||||||
_tViewNode: TViewNode|null = null;
|
_tViewNode: TViewNode|null = null;
|
||||||
|
|
||||||
// TODO(issue/24571): remove '!'.
|
get rootNodes(): any[] {
|
||||||
rootNodes !: any[];
|
if (this._view[HOST] == null) {
|
||||||
|
const tView = this._view[HOST_NODE] as TViewNode;
|
||||||
|
return collectNativeNodes(this._view, tView, []);
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
constructor(_view: LViewData, private _context: T|null, private _componentIndex: number) {
|
constructor(_view: LViewData, private _context: T|null, private _componentIndex: number) {
|
||||||
this._view = _view;
|
this._view = _view;
|
||||||
|
@ -269,3 +276,17 @@ export class RootViewRef<T> extends ViewRef<T> {
|
||||||
|
|
||||||
checkNoChanges(): void { checkNoChangesInRootView(this._view); }
|
checkNoChanges(): void { checkNoChangesInRootView(this._view); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function collectNativeNodes(lView: LViewData, parentTNode: TNode, result: any[]): any[] {
|
||||||
|
let tNodeChild = parentTNode.child;
|
||||||
|
|
||||||
|
while (tNodeChild) {
|
||||||
|
result.push(getNativeByTNode(tNodeChild, lView));
|
||||||
|
if (tNodeChild.type === TNodeType.ElementContainer) {
|
||||||
|
collectNativeNodes(lView, tNodeChild, result);
|
||||||
|
}
|
||||||
|
tNodeChild = tNodeChild.next;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
|
@ -371,6 +371,9 @@
|
||||||
{
|
{
|
||||||
"name": "cleanUpView"
|
"name": "cleanUpView"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "collectNativeNodes"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "componentRefresh"
|
"name": "componentRefresh"
|
||||||
},
|
},
|
||||||
|
|
|
@ -611,6 +611,9 @@
|
||||||
{
|
{
|
||||||
"name": "cleanUpView"
|
"name": "cleanUpView"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "collectNativeNodes"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "compileNgModuleFactory"
|
"name": "compileNgModuleFactory"
|
||||||
},
|
},
|
||||||
|
|
|
@ -428,6 +428,9 @@
|
||||||
{
|
{
|
||||||
"name": "cleanUpView"
|
"name": "cleanUpView"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "collectNativeNodes"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "componentRefresh"
|
"name": "componentRefresh"
|
||||||
},
|
},
|
||||||
|
|
|
@ -1280,6 +1280,9 @@
|
||||||
{
|
{
|
||||||
"name": "cleanUpView"
|
"name": "cleanUpView"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "collectNativeNodes"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "compileNgModuleFactory"
|
"name": "compileNgModuleFactory"
|
||||||
},
|
},
|
||||||
|
|
|
@ -7,7 +7,6 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {NgForOfContext} from '@angular/common';
|
import {NgForOfContext} from '@angular/common';
|
||||||
import {ElementRef, TemplateRef} from '@angular/core';
|
|
||||||
|
|
||||||
import {AttributeMarker, defineComponent, templateRefExtractor} from '../../src/render3/index';
|
import {AttributeMarker, defineComponent, templateRefExtractor} from '../../src/render3/index';
|
||||||
import {bind, template, elementEnd, elementProperty, elementStart, interpolation1, interpolation2, interpolation3, interpolationV, listener, load, nextContext, text, textBinding, elementContainerStart, elementContainerEnd, reference} from '../../src/render3/instructions';
|
import {bind, template, elementEnd, elementProperty, elementStart, interpolation1, interpolation2, interpolation3, interpolationV, listener, load, nextContext, text, textBinding, elementContainerStart, elementContainerEnd, reference} from '../../src/render3/instructions';
|
||||||
|
|
|
@ -0,0 +1,178 @@
|
||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright Google Inc. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style license that can be
|
||||||
|
* found in the LICENSE file at https://angular.io/license
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {TemplateRef} from '@angular/core';
|
||||||
|
|
||||||
|
import {ComponentFixture, createComponent, getDirectiveOnNode} from './render_util';
|
||||||
|
import {bind, directiveInject, element, elementContainerStart, elementContainerEnd, elementProperty, template, text} from '../../src/render3/instructions';
|
||||||
|
import {RenderFlags, defineDirective, AttributeMarker} from '../../src/render3/index';
|
||||||
|
|
||||||
|
import {NgIf} from './common_with_def';
|
||||||
|
|
||||||
|
describe('TemplateRef', () => {
|
||||||
|
|
||||||
|
describe('rootNodes', () => {
|
||||||
|
|
||||||
|
class DirectiveWithTplRef {
|
||||||
|
static ngDirectiveDef = defineDirective({
|
||||||
|
type: DirectiveWithTplRef,
|
||||||
|
selectors: [['', 'tplRef', '']],
|
||||||
|
factory: () => new DirectiveWithTplRef(directiveInject(TemplateRef as any))
|
||||||
|
});
|
||||||
|
|
||||||
|
// injecting a ViewContainerRef to create a dynamic container in which embedded views will be
|
||||||
|
// created
|
||||||
|
constructor(public tplRef: TemplateRef<{}>) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
it('should return root render nodes for an embedded view instance', () => {
|
||||||
|
let directiveWithTplRef: DirectiveWithTplRef;
|
||||||
|
|
||||||
|
function embeddedTemplate(rf: RenderFlags, ctx: any) {
|
||||||
|
if (rf & RenderFlags.Create) {
|
||||||
|
element(0, 'div');
|
||||||
|
text(1, 'some text');
|
||||||
|
element(2, 'span');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
<ng-template tplRef>
|
||||||
|
<div></div>
|
||||||
|
some text
|
||||||
|
<span></span>
|
||||||
|
</ng-template>
|
||||||
|
*/
|
||||||
|
const AppComponent = createComponent('app-cmp', function(rf: RenderFlags, ctx: any) {
|
||||||
|
if (rf & RenderFlags.Create) {
|
||||||
|
template(0, embeddedTemplate, 3, 0, null, ['tplRef', '']);
|
||||||
|
directiveWithTplRef = getDirectiveOnNode(0, 0);
|
||||||
|
}
|
||||||
|
}, 1, 0, [DirectiveWithTplRef]);
|
||||||
|
|
||||||
|
|
||||||
|
const fixture = new ComponentFixture(AppComponent);
|
||||||
|
expect(directiveWithTplRef !).toBeDefined();
|
||||||
|
|
||||||
|
const viewRef = directiveWithTplRef !.tplRef.createEmbeddedView({});
|
||||||
|
expect(viewRef.rootNodes.length).toBe(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is different as compared to the view engine implementation which returns a comment node
|
||||||
|
* in this case:
|
||||||
|
* https://stackblitz.com/edit/angular-uiqry6?file=src/app/app.component.ts
|
||||||
|
*
|
||||||
|
* Returning a comment node for a template ref with no nodes is wrong and should be fixed in
|
||||||
|
* ivy.
|
||||||
|
*/
|
||||||
|
it('should return an empty array for embedded view with no nodes', () => {
|
||||||
|
let directiveWithTplRef: DirectiveWithTplRef;
|
||||||
|
|
||||||
|
/*
|
||||||
|
<ng-template tplRef></ng-template>
|
||||||
|
*/
|
||||||
|
const AppComponent = createComponent('app-cmp', function(rf: RenderFlags, ctx: any) {
|
||||||
|
if (rf & RenderFlags.Create) {
|
||||||
|
template(0, () => {}, 0, 0, null, ['tplRef', '']);
|
||||||
|
directiveWithTplRef = getDirectiveOnNode(0, 0);
|
||||||
|
}
|
||||||
|
}, 1, 0, [DirectiveWithTplRef]);
|
||||||
|
|
||||||
|
|
||||||
|
const fixture = new ComponentFixture(AppComponent);
|
||||||
|
expect(directiveWithTplRef !).toBeDefined();
|
||||||
|
|
||||||
|
const viewRef = directiveWithTplRef !.tplRef.createEmbeddedView({});
|
||||||
|
expect(viewRef.rootNodes.length).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is somehow surprising but the current view engine don't descend into containers when
|
||||||
|
* getting root nodes of an embedded view:
|
||||||
|
* https://stackblitz.com/edit/angular-z8zev7?file=src/app/app.component.ts
|
||||||
|
*/
|
||||||
|
it('should not descend into containers when retrieving root nodes', () => {
|
||||||
|
let directiveWithTplRef: DirectiveWithTplRef;
|
||||||
|
|
||||||
|
function ngIfTemplate(rf: RenderFlags, ctx: any) {
|
||||||
|
if (rf & RenderFlags.Create) {
|
||||||
|
text(0, 'text');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function embeddedTemplate(rf: RenderFlags, ctx: any) {
|
||||||
|
if (rf & RenderFlags.Create) {
|
||||||
|
template(0, ngIfTemplate, 1, 0, null, [AttributeMarker.SelectOnly, 'ngIf']);
|
||||||
|
}
|
||||||
|
if (rf & RenderFlags.Update) {
|
||||||
|
elementProperty(0, 'ngIf', bind(ctx.showing));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
<ng-template tplRef><ng-template [ngIf]="true">text</ng-template></ng-template>
|
||||||
|
*/
|
||||||
|
const AppComponent = createComponent('app-cmp', function(rf: RenderFlags, ctx: any) {
|
||||||
|
if (rf & RenderFlags.Create) {
|
||||||
|
template(0, embeddedTemplate, 1, 1, null, ['tplRef', '']);
|
||||||
|
directiveWithTplRef = getDirectiveOnNode(0, 0);
|
||||||
|
}
|
||||||
|
}, 1, 0, [DirectiveWithTplRef, NgIf]);
|
||||||
|
|
||||||
|
|
||||||
|
const fixture = new ComponentFixture(AppComponent);
|
||||||
|
expect(directiveWithTplRef !).toBeDefined();
|
||||||
|
|
||||||
|
const viewRef = directiveWithTplRef !.tplRef.createEmbeddedView({});
|
||||||
|
|
||||||
|
// assert that we've got a comment node (only!) corresponding to <ng-template [ngIf]="true">
|
||||||
|
expect(viewRef.rootNodes.length).toBe(1);
|
||||||
|
expect(viewRef.rootNodes[0].nodeType).toBe(8);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contrary to containers (<ng-template>) we _do_ descend into element containers
|
||||||
|
* (<ng-container):
|
||||||
|
* https://stackblitz.com/edit/angular-yovmmp?file=src/app/app.component.ts
|
||||||
|
*/
|
||||||
|
it('should descend into element containers when retrieving root nodes', () => {
|
||||||
|
let directiveWithTplRef: DirectiveWithTplRef;
|
||||||
|
|
||||||
|
function embeddedTemplate(rf: RenderFlags, ctx: any) {
|
||||||
|
if (rf & RenderFlags.Create) {
|
||||||
|
elementContainerStart(0);
|
||||||
|
{ text(1, 'text'); }
|
||||||
|
elementContainerEnd();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
<ng-template tplRef><ng-container>text</ng-container></ng-template>
|
||||||
|
*/
|
||||||
|
const AppComponent = createComponent('app-cmp', function(rf: RenderFlags, ctx: any) {
|
||||||
|
if (rf & RenderFlags.Create) {
|
||||||
|
template(0, embeddedTemplate, 2, 0, null, ['tplRef', '']);
|
||||||
|
directiveWithTplRef = getDirectiveOnNode(0, 0);
|
||||||
|
}
|
||||||
|
}, 1, 0, [DirectiveWithTplRef]);
|
||||||
|
|
||||||
|
|
||||||
|
const fixture = new ComponentFixture(AppComponent);
|
||||||
|
expect(directiveWithTplRef !).toBeDefined();
|
||||||
|
|
||||||
|
const viewRef = directiveWithTplRef !.tplRef.createEmbeddedView({});
|
||||||
|
|
||||||
|
expect(viewRef.rootNodes.length).toBe(2);
|
||||||
|
expect(viewRef.rootNodes[0].nodeType)
|
||||||
|
.toBe(8); // a comment node (only!) corresponding to <ng-container>
|
||||||
|
expect(viewRef.rootNodes[1].nodeType).toBe(3); // a text node
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in New Issue