Skip to content

Commit 1c842f8

Browse files
authored
fix(list-view): ensure templates are in a consistent state on itemLoa… (#10)
* fix(list-view): ensure templates are in a consistent state on itemLoading * tests: add tests for listview with events
1 parent c06ebf3 commit 1c842f8

File tree

3 files changed

+67
-21
lines changed

3 files changed

+67
-21
lines changed
Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
import { NativeScriptConfig } from '@nativescript/core';
22

33
export default {
4-
id: 'org.nativescript.demong',
5-
appResourcesPath: 'App_Resources',
6-
android: {
7-
v8Flags: '--expose_gc',
4+
id: 'org.nativescript.demong',
5+
appResourcesPath: 'App_Resources',
6+
android: {
7+
v8Flags: '--expose_gc',
88
markingMode: 'none',
99
codeCache: true,
10-
suppressCallJSMethodExceptions: false
10+
suppressCallJSMethodExceptions: false,
11+
discardUncaughtJsExceptions: true,
1112
},
1213
ios: {
13-
discardUncaughtJsExceptions: false
14+
discardUncaughtJsExceptions: true,
1415
},
15-
appPath: 'src',
16+
appPath: 'src',
1617
} as NativeScriptConfig;

apps/nativescript-demo-ng/src/tests/list-view-tests.ts

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { Component, Input, NgModule, NO_ERRORS_SCHEMA, ViewChild } from '@angular/core';
22
import { TestBed, waitForAsync } from '@angular/core/testing';
33
import { ListViewComponent, NativeScriptModule } from '@nativescript/angular';
4-
import { promiseWait } from '@nativescript/angular/testing';
54
// import trace = require("trace");
65
// trace.setCategories("ns-list-view, " + trace.categories.Navigation);
76
// trace.enable();
@@ -75,6 +74,38 @@ export class TestListViewSelectorComponent {
7574
}
7675
}
7776

77+
@Component({
78+
selector: 'list-with-template-selector-with-events',
79+
template: `
80+
<GridLayout>
81+
<ListView [items]="myItems" [itemTemplateSelector]="templateSelector" (itemLoading)="dummyEvent($event)">
82+
<ng-template nsTemplateKey="first" let-item="item">
83+
<StackLayout>
84+
<Label [text]="item.name" (loaded)="dummyEvent($event)"></Label>
85+
<item-component templateName="first"></item-component>
86+
</StackLayout>
87+
</ng-template>
88+
<ng-template nsTemplateKey="second" let-item="item">
89+
<StackLayout>
90+
<Label [text]="item.name" (loaded)="dummyEvent($event)"></Label>
91+
<item-component templateName="second"></item-component>
92+
</StackLayout>
93+
</ng-template>
94+
</ListView>
95+
</GridLayout>
96+
`,
97+
})
98+
export class TestListViewSelectorWithEventsComponent {
99+
public myItems: Array<DataItem> = ITEMS;
100+
public templateSelector = (item: DataItem, index: number, items: any) => {
101+
return item.id % 2 === 0 ? 'first' : 'second';
102+
};
103+
constructor() {
104+
testTemplates = { first: 0, second: 0 };
105+
}
106+
dummyEvent(evt) {}
107+
}
108+
78109
@Component({
79110
selector: 'list-view-default-item-template',
80111
template: `
@@ -97,8 +128,10 @@ export class TestDefaultItemTemplateComponent {
97128
}
98129
}
99130

131+
const declarations = [TestListViewComponent, TestListViewSelectorComponent, ItemTemplateComponent, TestDefaultItemTemplateComponent, TestListViewSelectorWithEventsComponent];
132+
100133
@NgModule({
101-
declarations: [TestListViewComponent, TestListViewSelectorComponent, ItemTemplateComponent, TestDefaultItemTemplateComponent],
134+
declarations: [...declarations],
102135
imports: [NativeScriptModule],
103136
schemas: [NO_ERRORS_SCHEMA],
104137
})
@@ -107,7 +140,7 @@ export class ListViewModule {}
107140
describe('ListView-tests', () => {
108141
beforeEach(() =>
109142
TestBed.configureTestingModule({
110-
declarations: [TestListViewComponent, TestListViewSelectorComponent, ItemTemplateComponent, TestDefaultItemTemplateComponent],
143+
declarations: [...declarations],
111144
imports: [NativeScriptModule],
112145
schemas: [NO_ERRORS_SCHEMA],
113146
}).compileComponents()
@@ -134,6 +167,20 @@ describe('ListView-tests', () => {
134167
})
135168
);
136169

170+
it(
171+
"itemTemplateSelector doesn't break with events",
172+
waitForAsync(async () => {
173+
try {
174+
const fixture = TestBed.createComponent(TestListViewSelectorWithEventsComponent);
175+
fixture.autoDetectChanges(true);
176+
await fixture.whenRenderingDone();
177+
expect(testTemplates).toEqual({ first: 2, second: 1 });
178+
} catch (e) {
179+
fail(e);
180+
}
181+
})
182+
);
183+
137184
it(
138185
"'defaultTemplate' does not throw when list-view is scrolled",
139186
waitForAsync(async () => {

packages/angular/src/lib/cdk/list-view/list-view.component.ts

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { AfterContentInit, ContentChild, Directive, DoCheck, ElementRef, EmbeddedViewRef, EventEmitter, Host, Inject, InjectionToken, Input, IterableDiffer, IterableDiffers, OnDestroy, Output, TemplateRef, ViewChild, ViewContainerRef, ɵisListLikeIterable as isListLikeIterable, NgZone, ɵmarkDirty, Component, ChangeDetectionStrategy } from '@angular/core';
2-
import { ObservableArray, View, KeyedTemplate, LayoutBase, ItemEventData, TemplatedItemsView, profile, ListView } from '@nativescript/core';
1+
import { AfterContentInit, ChangeDetectionStrategy, Component, ContentChild, Directive, DoCheck, ElementRef, EmbeddedViewRef, EventEmitter, Host, HostListener, Input, IterableDiffer, IterableDiffers, NgZone, OnDestroy, Output, TemplateRef, ViewChild, ViewContainerRef, ɵisListLikeIterable as isListLikeIterable, ɵmarkDirty } from '@angular/core';
2+
import { ItemEventData, KeyedTemplate, LayoutBase, ListView, ObservableArray, profile, View } from '@nativescript/core';
33

44
import { extractSingleViewRecursive } from '../../element-registry/registry';
55
import { NativeScriptDebug } from '../../trace';
@@ -15,8 +15,7 @@ export class NsTemplatedItem<T> implements NgViewTemplate<{ index: number; data:
1515
constructor(private template: TemplateRef<ItemContext<T>>, public location: ViewContainerRef, private onCreate?: (view: View) => void) {}
1616
create(context?: { index: number; data: T }): View {
1717
const viewRef = this.location.createEmbeddedView(this.template, context ? this.setupItemContext(context) : new ItemContext());
18-
viewRef.detach(); // create detached
19-
viewRef.markForCheck();
18+
viewRef.detach(); // create detached, just beware this doesn't always work and the view might run the first CD anyway.
2019
const resultView = getItemViewRoot(viewRef);
2120
resultView[NG_VIEW] = viewRef;
2221
if (this.onCreate) {
@@ -71,6 +70,7 @@ export class NsTemplatedItem<T> implements NgViewTemplate<{ index: number; data:
7170

7271
export interface SetupItemViewArgs<T> {
7372
view: EmbeddedViewRef<ItemContext<T>>;
73+
nativeElement: View;
7474
data: T;
7575
index: number;
7676
context: ItemContext<T>;
@@ -125,8 +125,6 @@ export class ListViewComponent<T = any> implements DoCheck, OnDestroy, AfterCont
125125

126126
constructor(_elementRef: ElementRef, private _iterableDiffers: IterableDiffers, private zone: NgZone) {
127127
this.templatedItemsView = _elementRef.nativeElement;
128-
129-
this.templatedItemsView.on('itemLoading', this.onItemLoading, this);
130128
}
131129

132130
ngAfterContentInit() {
@@ -138,7 +136,6 @@ export class ListViewComponent<T = any> implements DoCheck, OnDestroy, AfterCont
138136
}
139137

140138
ngOnDestroy() {
141-
this.templatedItemsView.off('itemLoading', this.onItemLoading, this);
142139
this.templatedItemsView = null;
143140

144141
if (this._templateMap) {
@@ -166,7 +163,7 @@ export class ListViewComponent<T = any> implements DoCheck, OnDestroy, AfterCont
166163
const templates: KeyedTemplate[] = [];
167164
this._templateMap.forEach((value, key) => {
168165
templates.push({
169-
createView: value.create.bind(value),
166+
createView: () => null, // we'll handle creation later, otherwise core will create an invalid template
170167
key,
171168
});
172169
});
@@ -189,6 +186,7 @@ export class ListViewComponent<T = any> implements DoCheck, OnDestroy, AfterCont
189186
);
190187
}
191188

189+
@HostListener('itemLoading', ['$event'])
192190
@profile
193191
public onItemLoading(args: ItemEventData) {
194192
if (!this._templateMap) {
@@ -223,15 +221,15 @@ export class ListViewComponent<T = any> implements DoCheck, OnDestroy, AfterCont
223221
template = this._templateMap.get(templateKey);
224222
args.view = template.create({ index, data: currentItem });
225223
}
226-
this.setupViewRef(template.getEmbeddedViewRef(args.view), currentItem, index);
224+
this.setupViewRef(template.getEmbeddedViewRef(args.view), currentItem, index, args.view);
227225

228226
template.attach(args.view);
229227
ɵmarkDirty(this);
230228
}
231229

232-
public setupViewRef(viewRef: EmbeddedViewRef<ItemContext<T>>, data: T, index: number): void {
230+
public setupViewRef(viewRef: EmbeddedViewRef<ItemContext<T>>, data: T, index: number, nativeElement: View): void {
233231
const context = viewRef.context;
234-
this.setupItemView.next({ view: viewRef, data: data, index: index, context: context });
232+
this.setupItemView.next({ view: viewRef, nativeElement, data: data, index: index, context: context });
235233
}
236234

237235
ngDoCheck() {

0 commit comments

Comments
 (0)