diff --git a/packages/common/test/directives/ng_component_outlet_spec.ts b/packages/common/test/directives/ng_component_outlet_spec.ts
index 4b59d8b4d1..dcc3193dd0 100644
--- a/packages/common/test/directives/ng_component_outlet_spec.ts
+++ b/packages/common/test/directives/ng_component_outlet_spec.ts
@@ -120,31 +120,29 @@ describe('insert/remove', () => {
expect(cmpRef.instance.testToken).toBeNull();
}));
- fixmeIvy('can not pass projectable nodes') &&
- it('should render projectable nodes, if supplied', async(() => {
- const template = `projected foo${TEST_CMP_TEMPLATE}`;
- TestBed.overrideComponent(TestComponent, {set: {template: template}})
- .configureTestingModule({schemas: [NO_ERRORS_SCHEMA]});
+ it('should render projectable nodes, if supplied', async(() => {
+ const template = `projected foo${TEST_CMP_TEMPLATE}`;
+ TestBed.overrideComponent(TestComponent, {set: {template: template}})
+ .configureTestingModule({schemas: [NO_ERRORS_SCHEMA]});
- TestBed
- .overrideComponent(InjectedComponent, {set: {template: ``}})
- .configureTestingModule({schemas: [NO_ERRORS_SCHEMA]});
+ TestBed.overrideComponent(InjectedComponent, {set: {template: ``}})
+ .configureTestingModule({schemas: [NO_ERRORS_SCHEMA]});
- let fixture = TestBed.createComponent(TestComponent);
+ let fixture = TestBed.createComponent(TestComponent);
- fixture.detectChanges();
- expect(fixture.nativeElement).toHaveText('');
+ fixture.detectChanges();
+ expect(fixture.nativeElement).toHaveText('');
- fixture.componentInstance.currentComponent = InjectedComponent;
- fixture.componentInstance.projectables =
- [fixture.componentInstance.vcRef
- .createEmbeddedView(fixture.componentInstance.tplRefs.first)
- .rootNodes];
+ fixture.componentInstance.currentComponent = InjectedComponent;
+ fixture.componentInstance.projectables =
+ [fixture.componentInstance.vcRef
+ .createEmbeddedView(fixture.componentInstance.tplRefs.first)
+ .rootNodes];
- fixture.detectChanges();
- expect(fixture.nativeElement).toHaveText('projected foo');
- }));
+ fixture.detectChanges();
+ expect(fixture.nativeElement).toHaveText('projected foo');
+ }));
fixmeIvy('Runtime compiler is not loaded') &&
it('should resolve components from other modules, if supplied', async(() => {
diff --git a/packages/core/src/render3/view_ref.ts b/packages/core/src/render3/view_ref.ts
index e47b32fbcf..ec1066dab0 100644
--- a/packages/core/src/render3/view_ref.ts
+++ b/packages/core/src/render3/view_ref.ts
@@ -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 {checkNoChanges, checkNoChangesInRootView, detectChanges, detectChangesInRootView, markViewDirty, storeCleanupFn, viewAttached} from './instructions';
-import {TViewNode} from './interfaces/node';
-import {FLAGS, LViewData, LViewFlags, PARENT} from './interfaces/view';
+import {TNode, TNodeType, TViewNode} from './interfaces/node';
+import {FLAGS, HOST, HOST_NODE, LViewData, LViewFlags, PARENT} from './interfaces/view';
import {destroyLView} from './node_manipulation';
import {getRendererFactory} from './state';
+import {getNativeByTNode} from './util';
+
// Needed due to tsickle downleveling where multiple `implements` with classes creates
@@ -38,8 +40,13 @@ export class ViewRef implements viewEngine_EmbeddedViewRef, viewEngine_Int
*/
_tViewNode: TViewNode|null = null;
- // TODO(issue/24571): remove '!'.
- rootNodes !: any[];
+ get 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) {
this._view = _view;
@@ -269,3 +276,17 @@ export class RootViewRef extends ViewRef {
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;
+}
\ No newline at end of file
diff --git a/packages/core/test/bundling/animation_world/bundle.golden_symbols.json b/packages/core/test/bundling/animation_world/bundle.golden_symbols.json
index ac48a8cfd4..01ee1e9f0a 100644
--- a/packages/core/test/bundling/animation_world/bundle.golden_symbols.json
+++ b/packages/core/test/bundling/animation_world/bundle.golden_symbols.json
@@ -371,6 +371,9 @@
{
"name": "cleanUpView"
},
+ {
+ "name": "collectNativeNodes"
+ },
{
"name": "componentRefresh"
},
diff --git a/packages/core/test/bundling/hello_world_r2/bundle.golden_symbols.json b/packages/core/test/bundling/hello_world_r2/bundle.golden_symbols.json
index 79e0992226..d5bba26b9a 100644
--- a/packages/core/test/bundling/hello_world_r2/bundle.golden_symbols.json
+++ b/packages/core/test/bundling/hello_world_r2/bundle.golden_symbols.json
@@ -611,6 +611,9 @@
{
"name": "cleanUpView"
},
+ {
+ "name": "collectNativeNodes"
+ },
{
"name": "compileNgModuleFactory"
},
diff --git a/packages/core/test/bundling/todo/bundle.golden_symbols.json b/packages/core/test/bundling/todo/bundle.golden_symbols.json
index 8d17e05d2b..8857a5ca90 100644
--- a/packages/core/test/bundling/todo/bundle.golden_symbols.json
+++ b/packages/core/test/bundling/todo/bundle.golden_symbols.json
@@ -428,6 +428,9 @@
{
"name": "cleanUpView"
},
+ {
+ "name": "collectNativeNodes"
+ },
{
"name": "componentRefresh"
},
diff --git a/packages/core/test/bundling/todo_r2/bundle.golden_symbols.json b/packages/core/test/bundling/todo_r2/bundle.golden_symbols.json
index 26914e538d..e5630d71fc 100644
--- a/packages/core/test/bundling/todo_r2/bundle.golden_symbols.json
+++ b/packages/core/test/bundling/todo_r2/bundle.golden_symbols.json
@@ -1280,6 +1280,9 @@
{
"name": "cleanUpView"
},
+ {
+ "name": "collectNativeNodes"
+ },
{
"name": "compileNgModuleFactory"
},
diff --git a/packages/core/test/render3/common_integration_spec.ts b/packages/core/test/render3/common_integration_spec.ts
index 55a5c5495e..177cb16fd4 100644
--- a/packages/core/test/render3/common_integration_spec.ts
+++ b/packages/core/test/render3/common_integration_spec.ts
@@ -7,7 +7,6 @@
*/
import {NgForOfContext} from '@angular/common';
-import {ElementRef, TemplateRef} from '@angular/core';
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';
diff --git a/packages/core/test/render3/template_ref_spec.ts b/packages/core/test/render3/template_ref_spec.ts
new file mode 100644
index 0000000000..a2fe6883ae
--- /dev/null
+++ b/packages/core/test/render3/template_ref_spec.ts
@@ -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');
+ }
+ }
+
+ /*
+
+
+ some text
+
+
+ */
+ 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;
+
+ /*
+
+ */
+ 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));
+ }
+ }
+
+ /*
+ text
+ */
+ 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
+ expect(viewRef.rootNodes.length).toBe(1);
+ expect(viewRef.rootNodes[0].nodeType).toBe(8);
+ });
+
+
+ /**
+ * Contrary to containers () we _do_ descend into element containers
+ * ( {
+ let directiveWithTplRef: DirectiveWithTplRef;
+
+ function embeddedTemplate(rf: RenderFlags, ctx: any) {
+ if (rf & RenderFlags.Create) {
+ elementContainerStart(0);
+ { text(1, 'text'); }
+ elementContainerEnd();
+ }
+ }
+
+ /*
+ text
+ */
+ 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
+ expect(viewRef.rootNodes[1].nodeType).toBe(3); // a text node
+ });
+ });
+});
\ No newline at end of file