Skip to content

Commit 76ff421

Browse files
committed
fix(virtual-scroll): ensure listeners added after init read
1 parent d6b2a83 commit 76ff421

File tree

1 file changed

+65
-70
lines changed

1 file changed

+65
-70
lines changed

src/components/virtual-scroll/virtual-scroll.ts

Lines changed: 65 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ export class VirtualScroll implements DoCheck, AfterContentInit, OnDestroy {
208208

209209
/**
210210
* @input {array} The data that builds the templates within the virtual scroll.
211-
* This is the same data that you'd pass to `ngFor`. It's important to note
211+
* This is the same data that you'd pass to `*ngFor`. It's important to note
212212
* that when this data has changed, then the entire virtual scroll is reset,
213213
* which is an expensive operation and should be avoided if possible.
214214
*/
@@ -225,12 +225,12 @@ export class VirtualScroll implements DoCheck, AfterContentInit, OnDestroy {
225225
* should get created when initially rendered. The number is a
226226
* multiplier against the viewable area's height. For example, if it
227227
* takes `20` cells to fill up the height of the viewable area, then
228-
* with a buffer ratio of `2` it will create `40` cells that are
228+
* with a buffer ratio of `3` it will create `60` cells that are
229229
* available for reuse while scrolling. For better performance, it's
230230
* better to have more cells than what are required to fill the
231-
* viewable area. Default is `2`.
231+
* viewable area. Default is `3`.
232232
*/
233-
@Input() bufferRatio: number = 2;
233+
@Input() bufferRatio: number = 3;
234234

235235
/**
236236
* @input {string} The approximate width of each item template's cell.
@@ -239,20 +239,22 @@ export class VirtualScroll implements DoCheck, AfterContentInit, OnDestroy {
239239
* the scrollable area. This value can use either `px` or `%` units.
240240
* Note that the actual rendered size of each cell comes from the
241241
* app's CSS, whereas this approximation is used to help calculate
242-
* initial dimensions. Default is `100%`.
242+
* initial dimensions before the item has been rendered. Default is
243+
* `100%`.
243244
*/
244245
@Input() approxItemWidth: string = '100%';
245246

246247
/**
247-
* @input {string} Default is `40px`. It is important to provide this
248+
* @input {string} It is important to provide this
248249
* if virtual item height will be significantly larger than the default
249250
* The approximate height of each virtual item template's cell.
250251
* This dimension is used to help determine how many cells should
251252
* be created when initialized, and to help calculate the height of
252253
* the scrollable area. This height value can only use `px` units.
253254
* Note that the actual rendered size of each cell comes from the
254255
* app's CSS, whereas this approximation is used to help calculate
255-
* initial dimensions.
256+
* initial dimensions before the item has been rendered. Default is
257+
* `40px`.
256258
*/
257259
@Input() approxItemHeight: string;
258260

@@ -274,7 +276,7 @@ export class VirtualScroll implements DoCheck, AfterContentInit, OnDestroy {
274276
* the scrollable area. This height value can only use `px` units.
275277
* Note that the actual rendered size of each cell comes from the
276278
* app's CSS, whereas this approximation is used to help calculate
277-
* initial dimensions. Default is `40px`.
279+
* initial dimensions before the item has been rendered. Default is `40px`.
278280
*/
279281
@Input() approxHeaderHeight: string = '40px';
280282

@@ -285,7 +287,7 @@ export class VirtualScroll implements DoCheck, AfterContentInit, OnDestroy {
285287
* the scrollable area. This value can use either `px` or `%` units.
286288
* Note that the actual rendered size of each cell comes from the
287289
* app's CSS, whereas this approximation is used to help calculate
288-
* initial dimensions. Default is `100%`.
290+
* initial dimensions before the item has been rendered. Default is `100%`.
289291
*/
290292
@Input() approxFooterWidth: string = '100%';
291293

@@ -296,7 +298,7 @@ export class VirtualScroll implements DoCheck, AfterContentInit, OnDestroy {
296298
* the scrollable area. This height value can only use `px` units.
297299
* Note that the actual rendered size of each cell comes from the
298300
* app's CSS, whereas this approximation is used to help calculate
299-
* initial dimensions. Default is `40px`.
301+
* initial dimensions before the item has been rendered. Default is `40px`.
300302
*/
301303
@Input() approxFooterHeight: string = '40px';
302304

@@ -345,7 +347,7 @@ export class VirtualScroll implements DoCheck, AfterContentInit, OnDestroy {
345347
private _content: Content,
346348
private _platform: Platform,
347349
@Optional() private _ctrl: ViewController,
348-
config: Config,
350+
private _config: Config,
349351
private _dom: DomController) {
350352

351353
// hide the virtual scroll element with opacity so we don't
@@ -355,6 +357,8 @@ export class VirtualScroll implements DoCheck, AfterContentInit, OnDestroy {
355357

356358
// wait for the content to be rendered and has readable dimensions
357359
_content.readReady.subscribe(() => {
360+
this._init = true;
361+
358362
if (this._hasChanges()) {
359363
this.readUpdate();
360364

@@ -363,15 +367,24 @@ export class VirtualScroll implements DoCheck, AfterContentInit, OnDestroy {
363367
subscription.unsubscribe();
364368
this.writeUpdate();
365369
});
366-
367-
if (!this._scrollSub) {
368-
// listen for scroll events
369-
this.addScrollListener(config.getBoolean('virtualScrollEventAssist'));
370-
}
371370
}
371+
372+
this._listeners();
372373
});
373374
}
374375

376+
/**
377+
* @private
378+
*/
379+
ngDoCheck() {
380+
if (this._init && this._hasChanges()) {
381+
// only continue if we've already initialized
382+
// and if there actually are changes
383+
this.readUpdate();
384+
this.writeUpdate();
385+
}
386+
}
387+
375388
readUpdate() {
376389
console.debug(`virtual-scroll, readUpdate`);
377390

@@ -406,37 +419,6 @@ export class VirtualScroll implements DoCheck, AfterContentInit, OnDestroy {
406419
return (isPresent(this._records) && isPresent(this._differ) && isPresent(this._differ.diff(this._records)));
407420
}
408421

409-
/**
410-
* @private
411-
*/
412-
ngDoCheck() {
413-
if (this._init && this._hasChanges()) {
414-
// only continue if we've already initialized
415-
// and if there actually are changes
416-
this.readUpdate();
417-
this.writeUpdate();
418-
}
419-
}
420-
421-
/**
422-
* @private
423-
*/
424-
ngAfterContentInit() {
425-
if (!this._init) {
426-
427-
if (!this._itmTmp) {
428-
throw 'virtualItem required within virtualScroll';
429-
}
430-
431-
this._init = true;
432-
433-
if (!this.approxItemHeight) {
434-
this.approxItemHeight = '40px';
435-
console.warn('Virtual Scroll: Please provide an "approxItemHeight" input to ensure proper virtual scroll rendering');
436-
}
437-
}
438-
}
439-
440422
/**
441423
* @private
442424
* DOM WRITE
@@ -502,7 +484,7 @@ export class VirtualScroll implements DoCheck, AfterContentInit, OnDestroy {
502484
writeToNodes(nodes, cells, recordsLength);
503485

504486
// ******** DOM WRITE ****************
505-
this.setVirtualHeight(
487+
this._setHeight(
506488
estimateHeight(recordsLength, cells[cells.length - 1], this._vHeight, 0.25)
507489
);
508490

@@ -525,13 +507,14 @@ export class VirtualScroll implements DoCheck, AfterContentInit, OnDestroy {
525507
data.scrollTop = ev.scrollTop;
526508

527509
if (this._queue === ScrollQueue.RequiresDomWrite) {
510+
// there are DOM writes we need to take care of in this frame
528511

529512
this._dom.write(() => {
530513
// ******** DOM WRITE ****************
531514
writeToNodes(nodes, cells, this._records.length);
532515

533516
// ******** DOM WRITE ****************
534-
this.setVirtualHeight(
517+
this._setHeight(
535518
estimateHeight(this._records.length, cells[cells.length - 1], this._vHeight, 0.25)
536519
);
537520

@@ -540,6 +523,7 @@ export class VirtualScroll implements DoCheck, AfterContentInit, OnDestroy {
540523
});
541524

542525
} else if (this._queue === ScrollQueue.RequiresChangeDetection) {
526+
// we need to do some change detection in this frame
543527

544528
this._dom.write(() => {
545529
// we've got work painting do, let's throw it in the
@@ -556,7 +540,8 @@ export class VirtualScroll implements DoCheck, AfterContentInit, OnDestroy {
556540
});
557541

558542
} else {
559-
543+
// no dom writes or change detection to take care of
544+
// let's see if we've scroll far enough to require another check
560545
data.scrollDiff = (data.scrollTop - this._lastCheck);
561546

562547
if (Math.abs(data.scrollDiff) > SCROLL_DIFFERENCE_MINIMUM) {
@@ -625,7 +610,7 @@ export class VirtualScroll implements DoCheck, AfterContentInit, OnDestroy {
625610
writeToNodes(nodes, cells, this._records.length);
626611

627612
// ******** DOM WRITE ****************
628-
this.setVirtualHeight(
613+
this._setHeight(
629614
estimateHeight(this._records.length, cells[cells.length - 1], this._vHeight, 0.05)
630615
);
631616

@@ -634,10 +619,9 @@ export class VirtualScroll implements DoCheck, AfterContentInit, OnDestroy {
634619
}
635620

636621
/**
637-
* @private
638622
* DOM WRITE
639623
*/
640-
setVirtualHeight(newVirtualHeight: number) {
624+
private _setHeight(newVirtualHeight: number) {
641625
if (newVirtualHeight !== this._vHeight) {
642626
// ******** DOM WRITE ****************
643627
this._renderer.setElementStyle(this._elementRef.nativeElement, 'height', newVirtualHeight > 0 ? newVirtualHeight + 'px' : '');
@@ -647,31 +631,42 @@ export class VirtualScroll implements DoCheck, AfterContentInit, OnDestroy {
647631
}
648632
}
649633

634+
private _listeners() {
635+
if (!this._scrollSub) {
636+
if (this._config.getBoolean('virtualScrollEventAssist')) {
637+
// use JS scrolling for iOS UIWebView
638+
// goal is to completely remove this when iOS
639+
// fully supports scroll events
640+
// listen to JS scroll events
641+
this._content.enableJsScroll();
642+
}
643+
644+
this._scrollSub = this._content.ionScroll.subscribe((ev: ScrollEvent) => {
645+
this.scrollUpdate(ev);
646+
});
647+
648+
this._scrollEndSub = this._content.ionScrollEnd.subscribe((ev: ScrollEvent) => {
649+
this.scrollEnd(ev);
650+
});
651+
}
652+
}
653+
650654
/**
651655
* @private
652-
* NO DOM
653656
*/
654-
addScrollListener(eventAssist: boolean) {
655-
if (eventAssist) {
656-
// use JS scrolling for iOS UIWebView
657-
// goal is to completely remove this when iOS
658-
// fully supports scroll events
659-
// listen to JS scroll events
660-
this._content.enableJsScroll();
657+
ngAfterContentInit() {
658+
if (!this._itmTmp) {
659+
throw 'virtualItem required within virtualScroll';
661660
}
662661

663-
this._scrollSub = this._content.ionScroll.subscribe((ev: ScrollEvent) => {
664-
this.scrollUpdate(ev);
665-
});
666-
667-
this._scrollEndSub = this._content.ionScrollEnd.subscribe((ev: ScrollEvent) => {
668-
this.scrollEnd(ev);
669-
});
662+
if (!this.approxItemHeight) {
663+
this.approxItemHeight = '40px';
664+
console.warn('Virtual Scroll: Please provide an "approxItemHeight" input to ensure proper virtual scroll rendering');
665+
}
670666
}
671667

672668
/**
673669
* @private
674-
* NO DOM
675670
*/
676671
ngOnDestroy() {
677672
this._scrollSub && this._scrollSub.unsubscribe();
@@ -680,7 +675,7 @@ export class VirtualScroll implements DoCheck, AfterContentInit, OnDestroy {
680675

681676
}
682677

683-
const SCROLL_DIFFERENCE_MINIMUM = 20;
678+
const SCROLL_DIFFERENCE_MINIMUM = 40;
684679

685680
export const enum ScrollQueue {
686681
NoChanges,

0 commit comments

Comments
 (0)