From f05c7d677d82a3386f5a8df3a4c2d817ddaad754 Mon Sep 17 00:00:00 2001 From: Manu MA Date: Fri, 14 Dec 2018 00:36:30 +0100 Subject: [PATCH] fix(angular): virtual-scroll (#16729) fixes #16725 fixes #16432 fixes #16023 fixes #14591 fixes #16050 fixes #15587 --- .../virtual-scroll/virtual-scroll.ts | 56 ++++++++++--------- .../test-app/src/app/app-routing.module.ts | 5 ++ angular/test/test-app/src/app/app.module.ts | 8 ++- .../app/home-page/home-page.component.html | 5 ++ .../virtual-scroll-detail.component.html | 17 ++++++ .../virtual-scroll-detail.component.ts | 45 +++++++++++++++ .../virtual-scroll-inner.component.html | 3 + .../virtual-scroll-inner.component.ts | 17 ++++++ .../virtual-scroll.component.html | 21 +++++++ .../virtual-scroll.component.ts | 23 ++++++++ 10 files changed, 174 insertions(+), 26 deletions(-) create mode 100644 angular/test/test-app/src/app/virtual-scroll-detail/virtual-scroll-detail.component.html create mode 100644 angular/test/test-app/src/app/virtual-scroll-detail/virtual-scroll-detail.component.ts create mode 100644 angular/test/test-app/src/app/virtual-scroll-inner/virtual-scroll-inner.component.html create mode 100644 angular/test/test-app/src/app/virtual-scroll-inner/virtual-scroll-inner.component.ts create mode 100644 angular/test/test-app/src/app/virtual-scroll/virtual-scroll.component.html create mode 100644 angular/test/test-app/src/app/virtual-scroll/virtual-scroll.component.ts diff --git a/angular/src/directives/virtual-scroll/virtual-scroll.ts b/angular/src/directives/virtual-scroll/virtual-scroll.ts index 15c6d0e297..c91359b75a 100644 --- a/angular/src/directives/virtual-scroll/virtual-scroll.ts +++ b/angular/src/directives/virtual-scroll/virtual-scroll.ts @@ -1,4 +1,5 @@ -import { ChangeDetectorRef, ContentChild, Directive, ElementRef, EmbeddedViewRef } from '@angular/core'; +import { ContentChild, Directive, ElementRef, EmbeddedViewRef, NgZone } from '@angular/core'; +import { Cell, CellType } from '@ionic/core'; import { proxyInputs } from '../proxies'; @@ -21,15 +22,18 @@ import { VirtualContext } from './virtual-utils'; }) export class VirtualScroll { + private refMap = new WeakMap> (); + @ContentChild(VirtualItem) itmTmp!: VirtualItem; @ContentChild(VirtualHeader) hdrTmp!: VirtualHeader; @ContentChild(VirtualFooter) ftrTmp!: VirtualFooter; constructor( private el: ElementRef, - public cd: ChangeDetectorRef, + private zone: NgZone, ) { - el.nativeElement.nodeRender = this.nodeRender.bind(this); + const nativeEl = el.nativeElement as HTMLIonVirtualScrollElement; + nativeEl.nodeRender = this.nodeRender.bind(this); proxyInputs(this, this.el.nativeElement, [ 'approxItemHeight', @@ -42,40 +46,42 @@ export class VirtualScroll { ]); } - private nodeRender(el: HTMLElement | null, cell: any, index: number) { - if (!el) { - const view = this.itmTmp.viewContainer.createEmbeddedView( - this.getComponent(cell.type), - { $implicit: null, index }, - index - ); - el = getElement(view); - (el as any)['$ionView'] = view; - } - const node = (el as any)['$ionView']; - const ctx = node.context as VirtualContext; - ctx.$implicit = cell.value; - ctx.index = cell.index; - node.detectChanges(); - return el; + private nodeRender(el: HTMLElement | null, cell: Cell, index: number): HTMLElement { + return this.zone.run(() => { + if (!el) { + const view = this.itmTmp.viewContainer.createEmbeddedView( + this.getComponent(cell.type), + { $implicit: null, index }, + index + ); + el = getElement(view); + this.refMap.set(el, view); + } + const node = this.refMap.get(el)!; + const ctx = node.context as VirtualContext; + ctx.$implicit = cell.value; + ctx.index = cell.index; + node.markForCheck(); + return el; + }); } - private getComponent(type: number) { + private getComponent(type: CellType) { switch (type) { - case 0: return this.itmTmp.templateRef; - case 1: return this.hdrTmp.templateRef; - case 2: return this.ftrTmp.templateRef; + case 'item': return this.itmTmp.templateRef; + case 'header': return this.hdrTmp.templateRef; + case 'footer': return this.ftrTmp.templateRef; } throw new Error('template for virtual item was not provided'); } } -function getElement(view: EmbeddedViewRef): HTMLElement | null { +function getElement(view: EmbeddedViewRef): HTMLElement { const rootNodes = view.rootNodes; for (let i = 0; i < rootNodes.length; i++) { if (rootNodes[i].nodeType === 1) { return rootNodes[i]; } } - return null; + throw new Error('virtual element was not created'); } diff --git a/angular/test/test-app/src/app/app-routing.module.ts b/angular/test/test-app/src/app/app-routing.module.ts index 5ffb92548d..0be12e9767 100644 --- a/angular/test/test-app/src/app/app-routing.module.ts +++ b/angular/test/test-app/src/app/app-routing.module.ts @@ -9,6 +9,8 @@ import { TabsComponent } from './tabs/tabs.component'; import { TabsTab1Component } from './tabs-tab1/tabs-tab1.component'; import { TabsTab1NestedComponent } from './tabs-tab1-nested/tabs-tab1-nested.component'; import { TabsTab2Component } from './tabs-tab2/tabs-tab2.component'; +import { VirtualScrollComponent } from './virtual-scroll/virtual-scroll.component'; +import { VirtualScrollDetailComponent } from './virtual-scroll-detail/virtual-scroll-detail.component'; const routes: Routes = [ { path: '', component: HomePageComponent }, @@ -16,6 +18,9 @@ const routes: Routes = [ { path: 'modals', component: ModalComponent }, { path: 'router-link', component: RouterLinkComponent }, { path: 'router-link-page', component: RouterLinkPageComponent }, + { path: 'virtual-scroll', component: VirtualScrollComponent }, + { path: 'virtual-scroll-detail/:itemId', component: VirtualScrollDetailComponent }, + { path: 'tabs', redirectTo: '/tabs/account', pathMatch: 'full' }, { path: 'tabs', diff --git a/angular/test/test-app/src/app/app.module.ts b/angular/test/test-app/src/app/app.module.ts index 69c1432da4..00c2fed534 100644 --- a/angular/test/test-app/src/app/app.module.ts +++ b/angular/test/test-app/src/app/app.module.ts @@ -15,6 +15,9 @@ import { TabsComponent } from './tabs/tabs.component'; import { TabsTab1Component } from './tabs-tab1/tabs-tab1.component'; import { TabsTab2Component } from './tabs-tab2/tabs-tab2.component'; import { TabsTab1NestedComponent } from './tabs-tab1-nested/tabs-tab1-nested.component'; +import { VirtualScrollComponent } from './virtual-scroll/virtual-scroll.component'; +import { VirtualScrollDetailComponent } from './virtual-scroll-detail/virtual-scroll-detail.component'; +import { VirtualScrollInnerComponent } from './virtual-scroll-inner/virtual-scroll-inner.component'; @NgModule({ declarations: [ @@ -28,7 +31,10 @@ import { TabsTab1NestedComponent } from './tabs-tab1-nested/tabs-tab1-nested.com TabsComponent, TabsTab1Component, TabsTab2Component, - TabsTab1NestedComponent + TabsTab1NestedComponent, + VirtualScrollComponent, + VirtualScrollDetailComponent, + VirtualScrollInnerComponent ], imports: [ BrowserModule, diff --git a/angular/test/test-app/src/app/home-page/home-page.component.html b/angular/test/test-app/src/app/home-page/home-page.component.html index 7a186032ae..ed6721f4e0 100644 --- a/angular/test/test-app/src/app/home-page/home-page.component.html +++ b/angular/test/test-app/src/app/home-page/home-page.component.html @@ -27,5 +27,10 @@ Tabs test + + + Virtual Scroll + + diff --git a/angular/test/test-app/src/app/virtual-scroll-detail/virtual-scroll-detail.component.html b/angular/test/test-app/src/app/virtual-scroll-detail/virtual-scroll-detail.component.html new file mode 100644 index 0000000000..8c69d360d4 --- /dev/null +++ b/angular/test/test-app/src/app/virtual-scroll-detail/virtual-scroll-detail.component.html @@ -0,0 +1,17 @@ + + + + + + virtual-scroll page + + + + +

Item {{itemNu}}

+

ngOnInit: {{onInit}}

+

ionViewWillEnter: {{willEnter}}

+

ionViewDidEnter: {{didEnter}}

+

ionViewWillLeave: {{willLeave}}

+

ionViewDidLeave: {{didLeave}}

+
diff --git a/angular/test/test-app/src/app/virtual-scroll-detail/virtual-scroll-detail.component.ts b/angular/test/test-app/src/app/virtual-scroll-detail/virtual-scroll-detail.component.ts new file mode 100644 index 0000000000..f8cd21680d --- /dev/null +++ b/angular/test/test-app/src/app/virtual-scroll-detail/virtual-scroll-detail.component.ts @@ -0,0 +1,45 @@ +import { Component, NgZone, OnInit } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; + +@Component({ + selector: 'app-virtual-scroll-detail', + templateUrl: './virtual-scroll-detail.component.html', +}) +export class VirtualScrollDetailComponent implements OnInit { + + onInit = 0; + willEnter = 0; + didEnter = 0; + willLeave = 0; + didLeave = 0; + + itemNu = 'none'; + + constructor(private route: ActivatedRoute) {} + + ngOnInit() { + this.itemNu = this.route.snapshot.paramMap.get('itemId'); + NgZone.assertInAngularZone(); + this.onInit++; + } + + ionViewWillEnter() { + if (this.onInit !== 1) { + throw new Error('ngOnInit was not called'); + } + NgZone.assertInAngularZone(); + this.willEnter++; + } + ionViewDidEnter() { + NgZone.assertInAngularZone(); + this.didEnter++; + } + ionViewWillLeave() { + NgZone.assertInAngularZone(); + this.willLeave++; + } + ionViewDidLeave() { + NgZone.assertInAngularZone(); + this.didLeave++; + } +} diff --git a/angular/test/test-app/src/app/virtual-scroll-inner/virtual-scroll-inner.component.html b/angular/test/test-app/src/app/virtual-scroll-inner/virtual-scroll-inner.component.html new file mode 100644 index 0000000000..ffee72f251 --- /dev/null +++ b/angular/test/test-app/src/app/virtual-scroll-inner/virtual-scroll-inner.component.html @@ -0,0 +1,3 @@ +

+ [{{onInit}}] Item {{value}} +

diff --git a/angular/test/test-app/src/app/virtual-scroll-inner/virtual-scroll-inner.component.ts b/angular/test/test-app/src/app/virtual-scroll-inner/virtual-scroll-inner.component.ts new file mode 100644 index 0000000000..3bd7a4beaa --- /dev/null +++ b/angular/test/test-app/src/app/virtual-scroll-inner/virtual-scroll-inner.component.ts @@ -0,0 +1,17 @@ +import { Component, OnInit, NgZone, Input } from '@angular/core'; + +@Component({ + selector: 'app-virtual-scroll-inner', + templateUrl: './virtual-scroll-inner.component.html', +}) +export class VirtualScrollInnerComponent implements OnInit { + + @Input() value: string; + onInit = 0; + + ngOnInit() { + NgZone.assertInAngularZone(); + this.onInit++; + console.log('created'); + } +} diff --git a/angular/test/test-app/src/app/virtual-scroll/virtual-scroll.component.html b/angular/test/test-app/src/app/virtual-scroll/virtual-scroll.component.html new file mode 100644 index 0000000000..de045fc121 --- /dev/null +++ b/angular/test/test-app/src/app/virtual-scroll/virtual-scroll.component.html @@ -0,0 +1,21 @@ + + + + + Virtual Scroll Test + + + + + + {{ header }} + -- {{ footer }} + + + + + + + + + diff --git a/angular/test/test-app/src/app/virtual-scroll/virtual-scroll.component.ts b/angular/test/test-app/src/app/virtual-scroll/virtual-scroll.component.ts new file mode 100644 index 0000000000..8afebfbdb6 --- /dev/null +++ b/angular/test/test-app/src/app/virtual-scroll/virtual-scroll.component.ts @@ -0,0 +1,23 @@ +import { Component, OnInit } from '@angular/core'; +import { HeaderFn } from '@ionic/core'; + +@Component({ + selector: 'app-virtual-scroll', + templateUrl: './virtual-scroll.component.html', +}) +export class VirtualScrollComponent { + + items = Array.from({length: 1000}, (_, i) => i); + + myHeaderFn: HeaderFn = (_, index) => { + if ((index % 10) === 0) { + return `Header ${index}`; + } + } + + myFooterFn: HeaderFn = (_, index) => { + if ((index % 5) === 0) { + return `Footer ${index}`; + } + } +}