Skip to content

Commit 1b191b0

Browse files
authored
fix(segmented-bar): listview crash when scrolling (#2128)
1 parent f10a8fb commit 1b191b0

File tree

4 files changed

+156
-0
lines changed

4 files changed

+156
-0
lines changed

Diff for: e2e/tests-app-ng/app/app.routes.ts

+6
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { ListViewComponent } from "./list-view/list-view-page.component";
1717
import { ListViewControlComponent } from "./list-view/list-view-item-template.component";
1818
import { ListViewAsyncPipeComponent } from "./list-view/async-pipe-template.component";
1919
import { ListViewMainPageComponent } from "./list-view/list-view-main-page.component";
20+
import { ListViewSegmentedBarPageComponent } from "./list-view/list-view-nested-segmented-bar-page.component";
2021
import { ListViewWithNestedTemplateComponent } from "./list-view/list-view-nested-template.component";
2122
import { ListViewMultipleTemplatesComponent } from "./list-view/multiple-templates.component";
2223

@@ -68,6 +69,7 @@ export const routableComponents = [
6869
ListViewComponent,
6970
ListViewControlComponent,
7071
ListViewAsyncPipeComponent,
72+
ListViewSegmentedBarPageComponent,
7173
ListViewWithNestedTemplateComponent,
7274
ListViewMultipleTemplatesComponent,
7375

@@ -131,6 +133,10 @@ export const routes = [
131133
{ path: "ListViewExamples/commonTemplate", component: ListViewComponent, data: { title: "commonTemplate" } },
132134
{ path: "ListViewExamples/customTemplate", component: ListViewControlComponent, data: { title: "customTemplate" } },
133135
{ path: "listView/asyncPipeTemplate", component: ListViewAsyncPipeComponent, data: { title: "asyncPipeTemplate" } },
136+
{
137+
path: "ListViewExamples/segmentedBarTemplate",
138+
component: ListViewSegmentedBarPageComponent,
139+
data: { title: "segmentedBarTemplate" } },
134140
{
135141
path: "listView/nestedTemplate",
136142
component: ListViewWithNestedTemplateComponent,

Diff for: e2e/tests-app-ng/app/list-view/list-view-main-page.component.ts

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { Component } from "@angular/core";
77
<Button text="ListView" [nsRouterLink]="'commonTemplate'"></Button>
88
<Button text="ListViewCustomTemplate" [nsRouterLink]="'customTemplate'"></Button>
99
<Button text="ListViewAsyncPipe" [nsRouterLink]="['/listView','asyncPipeTemplate']"></Button>
10+
<Button text="ListViewSegmentedBarTemplate" [nsRouterLink]="'segmentedBarTemplate'"></Button>
1011
<Button text="NestedTemplate" [nsRouterLink]="['/listView','nestedTemplate']"></Button>
1112
<Button text="MultipleTemplates" [nsRouterLink]="['/listView','multiple-templates']"></Button>
1213
</StackLayout>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
import { Component, ViewChild, ElementRef, OnInit } from "@angular/core";
2+
import { SegmentedBarItem, SegmentedBar } from "tns-core-modules/ui/segmented-bar/segmented-bar";
3+
import { ListView } from "tns-core-modules/ui/list-view/list-view";
4+
import { EventData } from "tns-core-modules/ui/page/page";
5+
6+
interface DataItem {
7+
id: number;
8+
name: string;
9+
type: string;
10+
}
11+
12+
@Component({
13+
moduleId: module.id,
14+
selector: "segmented-bar-list-test",
15+
template: `
16+
<GridLayout automationText="mainView" iosOverflowSafeArea="false" style="height: 100%;">
17+
<ListView
18+
#listViewTest
19+
[itemTemplateSelector]="templateSelector"
20+
[items]="displayedItems"
21+
>
22+
<ng-template let-item="item">
23+
<label [text]="'Unsupported Element ' + item.type" color="red"></label>
24+
</ng-template>
25+
26+
<ng-template nsTemplateKey="segmentedBarTemplate" let-item="item">
27+
<SegmentedBar
28+
[items]="segmentedBarItems"
29+
(selectedIndexChange)="onSegmentedBarPress($event)"
30+
></SegmentedBar>
31+
</ng-template>
32+
33+
<ng-template nsTemplateKey="dataItemTemplate" let-item="item">
34+
<StackLayout>
35+
<Label [text]="'Item ID: ' + item.id" height="50"></Label>
36+
<Label [text]="item.name" height="50"></Label>
37+
</StackLayout>
38+
</ng-template>
39+
40+
<ng-template nsTemplateKey="buttonTemplate" let-item="item">
41+
<button text="Pushing me shouldn't crash!" (tap)="onButtonPress()"></button>
42+
</ng-template>
43+
</ListView>
44+
</GridLayout>
45+
`,
46+
})
47+
export class ListViewSegmentedBarPageComponent implements OnInit {
48+
public displayedItems: Array<DataItem> = [];
49+
public items: Array<DataItem>;
50+
public segmentedBarItems: SegmentedBarItem[] = this.createSegmentedBarItems();
51+
52+
@ViewChild("listViewTest", { static: false })
53+
private listViewTest?: ElementRef;
54+
55+
constructor() {
56+
this.items = [];
57+
58+
for (let i = 0; i < 20; i++) {
59+
const type = "dataItemTemplate";
60+
61+
this.items.push({
62+
id: i,
63+
name: `data item ${i}`,
64+
type: type,
65+
});
66+
}
67+
}
68+
69+
public ngOnInit() {
70+
this.displayedItems = this.updateItems(true);
71+
}
72+
73+
public onButtonPress() {
74+
// tslint:disable-next-line: no-unused-expression
75+
new Promise((resolve) => {
76+
setTimeout(() => {
77+
if (this.listViewTest) {
78+
console.log("Scrolling to the top of the list...");
79+
const listView = this.listViewTest.nativeElement as ListView;
80+
listView.scrollToIndex(0);
81+
}
82+
resolve();
83+
}, 150);
84+
});
85+
86+
this.displayedItems = this.updateItems(false);
87+
}
88+
89+
public onSegmentedBarPress(args: EventData) {
90+
if (args && args.object) {
91+
const segmentBar = args.object as SegmentedBar;
92+
const selectedOdd = segmentBar.selectedIndex === 0;
93+
this.displayedItems = this.updateItems(selectedOdd);
94+
}
95+
}
96+
97+
public createSegmentedBarItems() {
98+
const itemOdd = new SegmentedBarItem();
99+
itemOdd.title = "Odd Items";
100+
const itemEven = new SegmentedBarItem();
101+
itemEven.title = "Even Items";
102+
return [itemOdd, itemEven];
103+
}
104+
105+
public templateSelector(item: DataItem): string {
106+
return item.type;
107+
}
108+
109+
private updateItems(odd: boolean) {
110+
const items = [
111+
{
112+
id: -1,
113+
name: "Segmented Bar",
114+
type: "segmentedBarTemplate",
115+
},
116+
...(odd
117+
? this.items.filter((item) => item.id % 2 === 1)
118+
: this.items.filter((item) => item.id % 2 === 0)),
119+
{
120+
id: 999,
121+
name: "Refresh test",
122+
type: "buttonTemplate",
123+
},
124+
];
125+
return items;
126+
}
127+
}

Diff for: nativescript-angular/view-util.ts

+22
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,10 @@ export class ViewUtil {
260260
}
261261

262262
private removeLayoutChild(parent: NgLayoutBase, child: NgView): void {
263+
if (isLogEnabled()) {
264+
traceLog(`ViewUtil.removeLayoutChild parent: ${parent} child: ${child}`);
265+
}
266+
263267
const index = parent.getChildIndex(child);
264268

265269
if (index !== -1) {
@@ -371,6 +375,12 @@ export class ViewUtil {
371375

372376
const propMap = this.getProperties(view);
373377
const propertyName = propMap.get(attributeName);
378+
379+
// Ensure the children of a collection currently have no parent set.
380+
if (Array.isArray(value)) {
381+
this.removeParentReferencesFromItems(value);
382+
}
383+
374384
if (propertyName) {
375385
// We have a lower-upper case mapped property.
376386
view[propertyName] = value;
@@ -381,6 +391,18 @@ export class ViewUtil {
381391
view[attributeName] = value;
382392
}
383393

394+
private removeParentReferencesFromItems(items: any[]): void {
395+
for (const item of items) {
396+
if (item.parent && item.parentNode) {
397+
if (isLogEnabled()) {
398+
traceLog(`Unassigning parent ${item.parentNode} on value: ${item}`);
399+
}
400+
item.parent = undefined;
401+
item.parentNode = undefined;
402+
}
403+
}
404+
}
405+
384406
private getProperties(instance: any): Map<string, string> {
385407
const type = instance && instance.constructor;
386408
if (!type) {

0 commit comments

Comments
 (0)