Skip to content

Commit b127ba7

Browse files
authored
fix(renderer): set templateParent to comment and text nodes (#785)
* fix(renderer): set templateParent for newly create DetachedText nodes DetachedText is not inserted in the UI components tree, so we need to add it's parent manually. * fix(renderer): respect templateParent in parentNode() if one is set fixes #777, fixes #787 * test: add unit test for NgIfElse and NgIfThenElse * chore: target 'next' tag of tns-core-modules * fix(renderer): stop attaching comments to visual tree * refactor(renderer): create DetachedElements instead for comments and text nodes * refactor: move NgView, NgElement and similar to separate module
1 parent 81d5b40 commit b127ba7

17 files changed

+280
-164
lines changed

Diff for: nativescript-angular/animations/animation-driver.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { AnimationPlayer } from "@angular/animations";
2-
import { NgView } from "../element-registry";
32

3+
import { NgView } from "../element-types";
44
import { NativeScriptAnimationPlayer } from "./animation-player";
55
import { Keyframe } from "./utils";
66

Diff for: nativescript-angular/animations/animation-engine.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { ɵDomAnimationEngine as DomAnimationEngine } from "@angular/animations/browser";
22
import { AnimationEvent, AnimationPlayer } from "@angular/animations";
33

4-
import { NgView } from "../element-registry";
4+
import { NgView } from "../element-types";
55
import {
66
copyArray,
77
cssClasses,

Diff for: nativescript-angular/animations/animation-player.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import {
44
KeyframeAnimationInfo,
55
} from "tns-core-modules/ui/animation/keyframe-animation";
66

7-
import { NgView } from "../element-registry";
7+
import { NgView } from "../element-types";
88
import { Keyframe, getAnimationCurve, parseAnimationKeyframe } from "./utils";
99

1010
export class NativeScriptAnimationPlayer implements AnimationPlayer {

Diff for: nativescript-angular/animations/dom-utils.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
} from "@angular/animations";
88
import { unsetValue } from "tns-core-modules/ui/core/view";
99

10-
import { NgView } from "../element-registry";
10+
import { NgView } from "../element-types";
1111

1212
// overriden to use the default 'unsetValue'
1313
// instead of empty string ''

Diff for: nativescript-angular/directives/action-bar.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
import { Directive, Component, ElementRef, Optional, OnDestroy } from "@angular/core";
22
import { ActionItem, ActionBar, NavigationButton } from "tns-core-modules/ui/action-bar";
3-
import { isBlank } from "../lang-facade";
43
import { Page } from "tns-core-modules/ui/page";
54
import { View } from "tns-core-modules/ui/core/view";
6-
import { registerElement, ViewClassMeta, NgView } from "../element-registry";
5+
6+
import { isBlank } from "../lang-facade";
7+
import { registerElement } from "../element-registry";
8+
import { ViewClassMeta, NgView } from "../element-types";
79

810
const actionBarMeta: ViewClassMeta = {
911
skipAddToDom: true,
10-
insertChild: (parent: NgView, child: NgView, atIndex: number) => {
12+
insertChild: (parent: NgView, child: NgView) => {
1113
const bar = <ActionBar>(<any>parent);
1214
const childView = <any>child;
1315

@@ -17,8 +19,6 @@ const actionBarMeta: ViewClassMeta = {
1719
} else if (child instanceof ActionItem) {
1820
bar.actionItems.addItem(childView);
1921
childView.parent = bar;
20-
} else if (child.nodeName === "#comment") {
21-
bar._addView(childView, atIndex);
2222
} else if (child instanceof View) {
2323
bar.titleView = childView;
2424
}

Diff for: nativescript-angular/directives/list-view-comp.ts

+32-26
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,31 @@
11
import {
2+
AfterContentInit,
3+
ChangeDetectionStrategy,
4+
ChangeDetectorRef,
25
Component,
6+
ContentChild,
37
Directive,
4-
Input,
58
DoCheck,
6-
OnDestroy,
7-
AfterContentInit,
89
ElementRef,
9-
ViewContainerRef,
10-
TemplateRef,
11-
ContentChild,
1210
EmbeddedViewRef,
13-
IterableDiffers,
14-
IterableDiffer,
15-
ChangeDetectorRef,
1611
EventEmitter,
17-
ViewChild,
18-
Output,
1912
Host,
20-
ChangeDetectionStrategy
13+
Input,
14+
IterableDiffer,
15+
IterableDiffers,
16+
OnDestroy,
17+
Output,
18+
TemplateRef,
19+
ViewChild,
20+
ViewContainerRef,
2121
} from "@angular/core";
22-
import { isListLikeIterable } from "../collection-facade";
2322
import { ListView, ItemEventData } from "tns-core-modules/ui/list-view";
2423
import { View, KeyedTemplate } from "tns-core-modules/ui/core/view";
2524
import { ObservableArray } from "tns-core-modules/data/observable-array";
2625
import { LayoutBase } from "tns-core-modules/ui/layouts/layout-base";
26+
27+
import { CommentNode } from "../element-types";
28+
import { isListLikeIterable } from "../collection-facade";
2729
import { listViewLog, listViewError } from "../trace";
2830

2931
const NG_VIEW = "_ngViewRef";
@@ -212,23 +214,27 @@ export class ListViewComponent implements DoCheck, OnDestroy, AfterContentInit {
212214
}
213215

214216
function getSingleViewRecursive(nodes: Array<any>, nestLevel: number): View {
215-
const actualNodes = nodes.filter((n) => !!n && n.nodeName !== "#text");
217+
const actualNodes = nodes.filter(node => !(node instanceof CommentNode));
216218

217219
if (actualNodes.length === 0) {
218-
throw new Error("No suitable views found in list template! Nesting level: " + nestLevel);
220+
throw new Error(`No suitable views found in list template! ` +
221+
`Nesting level: ${nestLevel}`);
219222
} else if (actualNodes.length > 1) {
220-
throw new Error("More than one view found in list template! Nesting level: " + nestLevel);
221-
} else {
222-
if (actualNodes[0]) {
223-
let parentLayout = actualNodes[0].parent;
224-
if (parentLayout instanceof LayoutBase) {
225-
parentLayout.removeChild(actualNodes[0]);
226-
}
227-
return actualNodes[0];
228-
} else {
229-
return getSingleViewRecursive(actualNodes[0].children, nestLevel + 1);
230-
}
223+
throw new Error(`More than one view found in list template!` +
224+
`Nesting level: ${nestLevel}`);
225+
}
226+
227+
const rootLayout = actualNodes[0];
228+
if (!rootLayout) {
229+
return getSingleViewRecursive(rootLayout.children, nestLevel + 1);
230+
}
231+
232+
let parentLayout = rootLayout.parent;
233+
if (parentLayout instanceof LayoutBase) {
234+
parentLayout.removeChild(rootLayout);
231235
}
236+
237+
return rootLayout;
232238
}
233239

234240
export interface ComponentView {

Diff for: nativescript-angular/directives/tab-view.ts

+14-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
1-
import { ElementRef, Directive, Input, TemplateRef, ViewContainerRef, OnInit, AfterViewInit } from "@angular/core";
1+
import {
2+
AfterViewInit,
3+
Directive,
4+
ElementRef,
5+
Input,
6+
OnInit,
7+
TemplateRef,
8+
ViewContainerRef,
9+
} from "@angular/core";
210
import { TabView, TabViewItem } from "tns-core-modules/ui/tab-view";
11+
12+
import { CommentNode } from "../element-types";
313
import { convertToInt } from "../common/utils";
414
import { rendererLog } from "../trace";
515
import { isBlank } from "../lang-facade";
@@ -94,9 +104,9 @@ export class TabViewItemDirective implements OnInit {
94104
}
95105

96106
const viewRef = this.viewContainer.createEmbeddedView(this.templateRef);
97-
// Filter out text nodes, etc
98-
const realViews = viewRef.rootNodes.filter((node) =>
99-
node.nodeName && node.nodeName !== "#text");
107+
// Filter out text nodes and comments
108+
const realViews = viewRef.rootNodes.filter(node =>
109+
!(node instanceof CommentNode));
100110

101111
if (realViews.length > 0) {
102112
this.item.view = realViews[0];

Diff for: nativescript-angular/element-registry.ts

+4-35
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,10 @@
1-
import { View } from "tns-core-modules/ui/core/view";
1+
import { ViewClass, ViewClassMeta } from "./element-types";
22

33
export type ViewResolver = () => ViewClass;
4-
export type NgView = View & ViewExtensions;
5-
export interface ViewClassMeta {
6-
skipAddToDom?: boolean;
7-
insertChild?: (parent: NgView, child: NgView, atIndex: number) => void;
8-
removeChild?: (parent: NgView, child: NgView) => void;
9-
}
10-
11-
export interface ViewExtensions {
12-
nodeType: number;
13-
nodeName: string;
14-
templateParent: NgView;
15-
ngCssClasses: Map<string, boolean>;
16-
meta: ViewClassMeta;
17-
}
18-
19-
export interface ViewClass {
20-
new (): View;
21-
}
22-
23-
const defaultViewMeta: ViewClassMeta = {
24-
skipAddToDom: false,
25-
};
264

275
const elementMap = new Map<string, { resolver: ViewResolver, meta?: ViewClassMeta }>();
286
const camelCaseSplit = /([a-z0-9])([A-Z])/g;
7+
const defaultViewMeta: ViewClassMeta = { skipAddToDom: false };
298

309
export function registerElement(
3110
elementName: string,
@@ -48,6 +27,7 @@ export function getViewClass(elementName: string): ViewClass {
4827
if (!entry) {
4928
throw new TypeError(`No known component for element ${elementName}.`);
5029
}
30+
5131
try {
5232
return entry.resolver();
5333
} catch (e) {
@@ -56,12 +36,8 @@ export function getViewClass(elementName: string): ViewClass {
5636
}
5737

5838
export function getViewMeta(nodeName: string): ViewClassMeta {
59-
let meta = defaultViewMeta;
6039
const entry = elementMap.get(nodeName) || elementMap.get(nodeName.toLowerCase());
61-
if (entry && entry.meta) {
62-
meta = entry.meta;
63-
}
64-
return meta;
40+
return (entry && entry.meta) || defaultViewMeta;
6541
}
6642

6743
export function isKnownView(elementName: string): boolean {
@@ -110,10 +86,3 @@ registerElement("Span", () => require("tns-core-modules/text/span").Span);
11086

11187
registerElement("DetachedContainer", () => require("tns-core-modules/ui/proxy-view-container").ProxyViewContainer,
11288
{ skipAddToDom: true });
113-
114-
registerElement("DetachedText", () => require("ui/placeholder").Placeholder,
115-
{ skipAddToDom: true });
116-
117-
registerElement("Comment", () => require("ui/placeholder").Placeholder,
118-
{ skipAddToDom: false });
119-

Diff for: nativescript-angular/element-types.ts

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { View } from "tns-core-modules/ui/core/view";
2+
3+
export type NgView = (View & ViewExtensions);
4+
export type NgElement = NgView | CommentNode;
5+
6+
export interface ViewExtensions {
7+
nodeType: number;
8+
nodeName: string;
9+
templateParent: NgView;
10+
ngCssClasses: Map<string, boolean>;
11+
meta: ViewClassMeta;
12+
}
13+
14+
export interface ViewClass {
15+
new (): View;
16+
}
17+
18+
// used for creating comments and text nodes in the renderer
19+
export class CommentNode {
20+
meta: { skipAddToDom: true };
21+
templateParent: NgView;
22+
}
23+
24+
export interface ViewClassMeta {
25+
skipAddToDom?: boolean;
26+
insertChild?: (parent: NgView, child: NgView, atIndex: number) => void;
27+
removeChild?: (parent: NgView, child: NgView) => void;
28+
}
29+
30+
export function isDetachedElement(element): boolean {
31+
return (element && element.meta && element.meta.skipAddToDom);
32+
}

Diff for: nativescript-angular/index.ts

+8-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import "application";
1+
import "tns-core-modules/application";
22

33
export * from "./platform-common";
44
export * from "./platform";
@@ -15,13 +15,18 @@ export * from "./modal-dialog";
1515
export * from "./renderer";
1616
export * from "./view-util";
1717
export * from "./resource-loader";
18+
1819
export {
1920
ViewResolver,
20-
ViewClass,
21-
ViewClassMeta,
2221
registerElement,
2322
getViewClass,
2423
getViewMeta,
2524
isKnownView,
2625
} from "./element-registry";
26+
27+
export {
28+
ViewClass,
29+
ViewClassMeta,
30+
} from "./element-types";
31+
2732
export * from "./value-accessors/base-value-accessor";

Diff for: nativescript-angular/renderer.ts

+8-16
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { topmost } from "tns-core-modules/ui/frame";
1212
import { APP_ROOT_VIEW, DEVICE, getRootPage } from "./platform-providers";
1313
import { isBlank } from "./lang-facade";
1414
import { ViewUtil } from "./view-util";
15-
import { NgView } from "./element-registry";
15+
import { NgView, CommentNode } from "./element-types";
1616
import { rendererLog as traceLog } from "./trace";
1717

1818
// CONTENT_ATTR not exported from NativeScript_renderer - we need it for styles application.
@@ -43,6 +43,7 @@ export class NativeScriptRendererFactory implements RendererFactory2 {
4343
if (!rootView) {
4444
rootView = getRootPage() || topmost().currentPage;
4545
}
46+
4647
rootView.nodeName = "NONE";
4748
this.rootNgView = rootView;
4849
}
@@ -83,26 +84,17 @@ export class NativeScriptRenderer extends Renderer2 {
8384

8485
appendChild(parent: any, newChild: NgView): void {
8586
traceLog(`NativeScriptRenderer.appendChild child: ${newChild} parent: ${parent}`);
86-
87-
if (parent) {
88-
this.viewUtil.insertChild(parent, newChild);
89-
}
87+
this.viewUtil.insertChild(parent, newChild);
9088
}
9189

9290
insertBefore(parent: NgView, newChild: NgView, refChildIndex: number): void {
9391
traceLog(`NativeScriptRenderer.insertBefore child: ${newChild} parent: ${parent}`);
94-
95-
if (parent) {
96-
this.viewUtil.insertChild(parent, newChild, refChildIndex);
97-
}
92+
this.viewUtil.insertChild(parent, newChild, refChildIndex);
9893
}
9994

10095
removeChild(parent: any, oldChild: NgView): void {
10196
traceLog(`NativeScriptRenderer.removeChild child: ${oldChild} parent: ${parent}`);
102-
103-
if (parent) {
104-
this.viewUtil.removeChild(parent, oldChild);
105-
}
97+
this.viewUtil.removeChild(parent, oldChild);
10698
}
10799

108100
selectRootElement(selector: string): NgView {
@@ -111,15 +103,15 @@ export class NativeScriptRenderer extends Renderer2 {
111103
}
112104

113105
parentNode(node: NgView): any {
114-
return node.parent;
106+
return node.parent || node.templateParent;
115107
}
116108

117109
nextSibling(node: NgView): number {
118110
traceLog(`NativeScriptRenderer.nextSibling ${node}`);
119111
return this.viewUtil.nextSiblingIndex(node);
120112
}
121113

122-
createComment(_value: any) {
114+
createComment(_value: any): CommentNode {
123115
traceLog(`NativeScriptRenderer.createComment ${_value}`);
124116
return this.viewUtil.createComment();
125117
}
@@ -129,7 +121,7 @@ export class NativeScriptRenderer extends Renderer2 {
129121
return this.viewUtil.createView(name);
130122
}
131123

132-
createText(_value: string) {
124+
createText(_value: string): CommentNode {
133125
traceLog(`NativeScriptRenderer.createText ${_value}`);
134126
return this.viewUtil.createText();
135127
}

0 commit comments

Comments
 (0)