123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716 |
- import { ChangeDetectorRef, ContentChild, Directive, ElementRef, Input, IterableDiffers, NgZone, Renderer } from '@angular/core';
- import { adjustRendered, calcDimensions, estimateHeight, initReadNodes, populateNodeData, processRecords, updateDimensions, updateNodeContext, writeToNodes } from './virtual-util';
- import { Config } from '../../config/config';
- import { Content } from '../content/content';
- import { DomController } from '../../platform/dom-controller';
- import { isFunction, isPresent } from '../../util/util';
- import { Platform } from '../../platform/platform';
- import { ViewController } from '../../navigation/view-controller';
- import { VirtualItem } from './virtual-item';
- import { VirtualFooter } from './virtual-footer';
- import { VirtualHeader } from './virtual-header';
- /**
- * @name VirtualScroll
- * @description
- * Virtual Scroll displays a virtual, "infinite" list. An array of records
- * is passed to the virtual scroll containing the data to create templates
- * for. The template created for each record, referred to as a cell, can
- * consist of items, headers, and footers.
- *
- * For performance reasons, not every record in the list is rendered at once;
- * instead a small subset of records (enough to fill the viewport) are rendered
- * and reused as the user scrolls.
- *
- * ### The Basics
- *
- * The array of records should be passed to the `virtualScroll` property.
- * The data given to the `virtualScroll` property must be an array. An item
- * template with the `*virtualItem` property is required in the `virtualScroll`.
- * The `virtualScroll` and `*virtualItem` properties can be added to any element.
- *
- * ```html
- * <ion-list [virtualScroll]="items">
- *
- * <ion-item *virtualItem="let item">
- * {% raw %}{{ item }}{% endraw %}
- * </ion-item>
- *
- * </ion-list>
- * ```
- *
- *
- * ### Section Headers and Footers
- *
- * Section headers and footers are optional. They can be dynamically created
- * from developer-defined functions. For example, a large list of contacts
- * usually has a divider for each letter in the alphabet. Developers provide
- * their own custom function to be called on each record. The logic in the
- * custom function should determine whether to create the section template
- * and what data to provide to the template. The custom function should
- * return `null` if a template shouldn't be created.
- *
- * ```html
- * <ion-list [virtualScroll]="items" [headerFn]="myHeaderFn">
- *
- * <ion-item-divider *virtualHeader="let header">
- * Header: {% raw %}{{ header }}{% endraw %}
- * </ion-item-divider>
- *
- * <ion-item *virtualItem="let item">
- * Item: {% raw %}{{ item }}{% endraw %}
- * </ion-item>
- *
- * </ion-list>
- * ```
- *
- * Below is an example of a custom function called on every record. It
- * gets passed the individual record, the record's index number,
- * and the entire array of records. In this example, after every 20
- * records a header will be inserted. So between the 19th and 20th records,
- * between the 39th and 40th, and so on, a `<ion-item-divider>` will
- * be created and the template's data will come from the function's
- * returned data.
- *
- * ```ts
- * myHeaderFn(record, recordIndex, records) {
- * if (recordIndex % 20 === 0) {
- * return 'Header ' + recordIndex;
- * }
- * return null;
- * }
- * ```
- *
- *
- * ### Approximate Widths and Heights
- *
- * If the height of items in the virtual scroll are not close to the
- * default size of 40px, it is extremely important to provide a value for
- * approxItemHeight height. An exact pixel-perfect size is not necessary,
- * but without an estimate the virtual scroll will not render correctly.
- *
- * The approximate width and height of each template is used to help
- * determine how many cells should be created, and to help calculate
- * the height of the scrollable area. Note that the actual rendered size
- * of each cell comes from the app's CSS, whereas this approximation
- * is only used to help calculate initial dimensions.
- *
- * It's also important to know that Ionic's default item sizes have
- * slightly different heights between platforms, which is perfectly fine.
- *
- *
- * ### Images Within Virtual Scroll
- *
- * HTTP requests, image decoding, and image rendering can cause jank while
- * scrolling. In order to better control images, Ionic provides `<ion-img>`
- * to manage HTTP requests and image rendering. While scrolling through items
- * quickly, `<ion-img>` knows when and when not to make requests, when and
- * when not to render images, and only loads the images that are viewable
- * after scrolling. [Read more about `ion-img`.](../../img/Img/)
- *
- * It's also important for app developers to ensure image sizes are locked in,
- * and after images have fully loaded they do not change size and affect any
- * other element sizes. Simply put, to ensure rendering bugs are not introduced,
- * it's vital that elements within a virtual item does not dynamically change.
- *
- * For virtual scrolling, the natural effects of the `<img>` are not desirable
- * features. We recommend using the `<ion-img>` component over the native
- * `<img>` element because when an `<img>` element is added to the DOM, it
- * immediately makes a HTTP request for the image file. Additionally, `<img>`
- * renders whenever it wants which could be while the user is scrolling. However,
- * `<ion-img>` is governed by the containing `ion-content` and does not render
- * images while scrolling quickly.
- *
- * ```html
- * <ion-list [virtualScroll]="items">
- *
- * <ion-item *virtualItem="let item">
- * <ion-avatar item-start>
- * <ion-img [src]="item.avatarUrl"></ion-img>
- * </ion-avatar>
- * {% raw %} {{ item.firstName }} {{ item.lastName }}{% endraw %}
- * </ion-item>
- *
- * </ion-list>
- * ```
- *
- *
- * ### Custom Components
- *
- * If a custom component is going to be used within Virtual Scroll, it's best
- * to wrap it with a good old `<div>` to ensure the component is rendered
- * correctly. Since each custom component's implementation and internals can be
- * quite different, wrapping within a `<div>` is a safe way to make sure
- * dimensions are measured correctly.
- *
- * ```html
- * <ion-list [virtualScroll]="items">
- *
- * <div *virtualItem="let item">
- * <my-custom-item [item]="item">
- * {% raw %} {{ item }}{% endraw %}
- * </my-custom-item>
- * </div>
- *
- * </ion-list>
- * ```
- *
- *
- * ## Virtual Scroll Performance Tips
- *
- * #### iOS Cordova WKWebView
- *
- * When deploying to iOS with Cordova, it's highly recommended to use the
- * [WKWebView plugin](http://blog.ionic.io/cordova-ios-performance-improvements-drop-in-speed-with-wkwebview/)
- * in order to take advantage of iOS's higher performimg webview. Additionally,
- * WKWebView is superior at scrolling efficiently in comparision to the older
- * UIWebView.
- *
- * #### Lock in element dimensions and locations
- *
- * In order for virtual scroll to efficiently size and locate every item, it's
- * very important every element within each virtual item does not dynamically
- * change its dimensions or location. The best way to ensure size and location
- * does not change, it's recommended each virtual item has locked in its size
- * via CSS.
- *
- * #### Use `ion-img` for images
- *
- * When including images within Virtual Scroll, be sure to use
- * [`ion-img`](../img/Img/) rather than the standard `<img>` HTML element.
- * With `ion-img`, images are lazy loaded so only the viewable ones are
- * rendered, and HTTP requests are efficiently controlled while scrolling.
- *
- * #### Set Approximate Widths and Heights
- *
- * As mentioned above, all elements should lock in their dimensions. However,
- * virtual scroll isn't aware of the dimensions until after they have been
- * rendered. For the initial render, virtual scroll still needs to set
- * how many items should be built. With "approx" property inputs, such as
- * `approxItemHeight`, we're able to give virtual scroll an approximate size,
- * therefore allowing virtual scroll to decide how many items should be
- * created.
- *
- * #### Changing dataset should use `virtualTrackBy`
- *
- * It is possible for the identities of elements in the iterator to change
- * while the data does not. This can happen, for example, if the iterator
- * produced from an RPC to the server, and that RPC is re-run. Even if the
- * "data" hasn't changed, the second response will produce objects with
- * different identities, and Ionic will tear down the entire DOM and rebuild
- * it. This is an expensive operation and should be avoided if possible.
- *
- * #### Efficient headers and footer functions
- *
- * Each virtual item must stay extremely efficient, but one way to really
- * kill its performance is to perform any DOM operations within section header
- * and footer functions. These functions are called for every record in the
- * dataset, so please make sure they're performant.
- *
- */
- export class VirtualScroll {
- constructor(_iterableDiffers, _elementRef, _renderer, _zone, _cd, _content, _plt, _ctrl, _config, _dom) {
- this._iterableDiffers = _iterableDiffers;
- this._elementRef = _elementRef;
- this._renderer = _renderer;
- this._zone = _zone;
- this._cd = _cd;
- this._content = _content;
- this._plt = _plt;
- this._ctrl = _ctrl;
- this._config = _config;
- this._dom = _dom;
- this._init = false;
- this._lastEle = false;
- this._records = [];
- this._cells = [];
- this._nodes = [];
- this._vHeight = 0;
- this._lastCheck = 0;
- this._recordSize = 0;
- this._data = {
- scrollTop: 0,
- };
- this._queue = 1 /* NoChanges */;
- /**
- * @input {number} The buffer ratio is used to decide how many cells
- * should get created when initially rendered. The number is a
- * multiplier against the viewable area's height. For example, if it
- * takes `20` cells to fill up the height of the viewable area, then
- * with a buffer ratio of `3` it will create `60` cells that are
- * available for reuse while scrolling. For better performance, it's
- * better to have more cells than what are required to fill the
- * viewable area. Default is `3`.
- */
- this.bufferRatio = 3;
- /**
- * @input {string} The approximate width of each item template's cell.
- * This dimension is used to help determine how many cells should
- * be created when initialized, and to help calculate the height of
- * the scrollable area. This value can use either `px` or `%` units.
- * Note that the actual rendered size of each cell comes from the
- * app's CSS, whereas this approximation is used to help calculate
- * initial dimensions before the item has been rendered. Default is
- * `100%`.
- */
- this.approxItemWidth = '100%';
- /**
- * @input {string} The approximate width of each header template's cell.
- * This dimension is used to help determine how many cells should
- * be created when initialized, and to help calculate the height of
- * the scrollable area. This value can use either `px` or `%` units.
- * Note that the actual rendered size of each cell comes from the
- * app's CSS, whereas this approximation is used to help calculate
- * initial dimensions. Default is `100%`.
- */
- this.approxHeaderWidth = '100%';
- /**
- * @input {string} The approximate height of each header template's cell.
- * This dimension is used to help determine how many cells should
- * be created when initialized, and to help calculate the height of
- * the scrollable area. This height value can only use `px` units.
- * Note that the actual rendered size of each cell comes from the
- * app's CSS, whereas this approximation is used to help calculate
- * initial dimensions before the item has been rendered. Default is `40px`.
- */
- this.approxHeaderHeight = '40px';
- /**
- * @input {string} The approximate width of each footer template's cell.
- * This dimension is used to help determine how many cells should
- * be created when initialized, and to help calculate the height of
- * the scrollable area. This value can use either `px` or `%` units.
- * Note that the actual rendered size of each cell comes from the
- * app's CSS, whereas this approximation is used to help calculate
- * initial dimensions before the item has been rendered. Default is `100%`.
- */
- this.approxFooterWidth = '100%';
- /**
- * @input {string} The approximate height of each footer template's cell.
- * This dimension is used to help determine how many cells should
- * be created when initialized, and to help calculate the height of
- * the scrollable area. This height value can only use `px` units.
- * Note that the actual rendered size of each cell comes from the
- * app's CSS, whereas this approximation is used to help calculate
- * initial dimensions before the item has been rendered. Default is `40px`.
- */
- this.approxFooterHeight = '40px';
- // hide the virtual scroll element with opacity so we don't
- // see jank as it loads up, but we're still able to read
- // dimensions because it's still rendered and only opacity hidden
- this.setElementClass('virtual-loading', true);
- // wait for the content to be rendered and has readable dimensions
- const readSub = _ctrl.readReady.subscribe(() => {
- readSub.unsubscribe();
- this.readUpdate(true);
- });
- // wait for the content to be writable
- const writeSub = _ctrl.writeReady.subscribe(() => {
- writeSub.unsubscribe();
- this._init = true;
- this.writeUpdate(true);
- this._listeners();
- });
- }
- /**
- * @input {array} The data that builds the templates within the virtual scroll.
- * This is the same data that you'd pass to `*ngFor`. It's important to note
- * that when this data has changed, then the entire virtual scroll is reset,
- * which is an expensive operation and should be avoided if possible.
- */
- set virtualScroll(val) {
- this._records = val;
- }
- get virtualScroll() {
- return this._records;
- }
- /**
- * @input {function} Section headers and the data used within its given
- * template can be dynamically created by passing a function to `headerFn`.
- * For example, a large list of contacts usually has dividers between each
- * letter in the alphabet. App's can provide their own custom `headerFn`
- * which is called with each record within the dataset. The logic within
- * the header function can decide if the header template should be used,
- * and what data to give to the header template. The function must return
- * `null` if a header cell shouldn't be created.
- */
- set headerFn(val) {
- if (isFunction(val)) {
- this._hdrFn = val.bind((this._ctrl._cmp) || this);
- }
- }
- /**
- * @input {function} Section footers and the data used within its given
- * template can be dynamically created by passing a function to `footerFn`.
- * The logic within the footer function can decide if the footer template
- * should be used, and what data to give to the footer template. The function
- * must return `null` if a footer cell shouldn't be created.
- */
- set footerFn(val) {
- if (isFunction(val)) {
- this._ftrFn = val.bind((this._ctrl._cmp) || this);
- }
- }
- /**
- * @hidden
- */
- firstRecord() {
- const cells = this._cells;
- return (cells.length > 0) ? cells[0].record : 0;
- }
- /**
- * @hidden
- */
- lastRecord() {
- const cells = this._cells;
- return (cells.length > 0) ? cells[cells.length - 1].record : 0;
- }
- /**
- * @hidden
- */
- ngOnChanges(changes) {
- if ('virtualScroll' in changes) {
- // React on virtualScroll changes only once all inputs have been initialized
- const value = changes['virtualScroll'].currentValue;
- if (!isPresent(this._differ) && isPresent(value)) {
- try {
- this._differ = this._iterableDiffers.find(value).create(this.virtualTrackBy);
- }
- catch (e) {
- throw new Error(`Cannot find a differ supporting object '${value}'. VirtualScroll only supports binding to Iterables such as Arrays.`);
- }
- }
- }
- }
- /**
- * @hidden
- */
- ngDoCheck() {
- // only continue if we've already initialized
- if (!this._init) {
- return;
- }
- // and if there actually are changes
- const changes = isPresent(this._differ) ? this._differ.diff(this.virtualScroll) : null;
- if (!isPresent(changes)) {
- return;
- }
- let needClean = false;
- var lastRecord = this._recordSize;
- changes.forEachOperation((_, pindex, cindex) => {
- // add new record after current position
- if (pindex === null && (cindex < lastRecord)) {
- (void 0) /* console.debug */;
- needClean = true;
- return;
- }
- // remove record after current position
- if (pindex < lastRecord && cindex === null) {
- (void 0) /* console.debug */;
- needClean = true;
- return;
- }
- });
- this._recordSize = this._records ? this._records.length : 0;
- this.readUpdate(needClean);
- this.writeUpdate(needClean);
- }
- /**
- * @hidden
- */
- readUpdate(needClean) {
- if (needClean) {
- // reset everything
- (void 0) /* console.debug */;
- this._cells.length = 0;
- // this._nodes.length = 0;
- // this._itmTmp.viewContainer.clear();
- // ******** DOM READ ****************
- this.calcDimensions();
- }
- else {
- (void 0) /* console.debug */;
- }
- }
- /**
- * @hidden
- */
- writeUpdate(needClean) {
- (void 0) /* console.debug */;
- const data = this._data;
- const stopAtHeight = (data.scrollTop + data.renderHeight);
- data.scrollDiff = SCROLL_DIFFERENCE_MINIMUM + 1;
- processRecords(stopAtHeight, this._records, this._cells, this._hdrFn, this._ftrFn, this._data);
- // ******** DOM WRITE ****************
- this.renderVirtual(needClean);
- }
- /**
- * @hidden
- */
- calcDimensions() {
- calcDimensions(this._data, this._elementRef.nativeElement, this.approxItemWidth, this.approxItemHeight, this.approxHeaderWidth, this.approxHeaderHeight, this.approxFooterWidth, this.approxFooterHeight, this.bufferRatio);
- }
- /**
- * @hidden
- * DOM WRITE
- */
- renderVirtual(needClean) {
- this._plt.raf(() => {
- const nodes = this._nodes;
- const cells = this._cells;
- const data = this._data;
- const records = this._records;
- if (needClean) {
- // ******** DOM WRITE ****************
- updateDimensions(this._plt, nodes, cells, data, true);
- data.topCell = 0;
- data.bottomCell = (cells.length - 1);
- }
- adjustRendered(cells, data);
- this._zone.run(() => {
- populateNodeData(data.topCell, data.bottomCell, true, cells, records, nodes, this._itmTmp.viewContainer, this._itmTmp.templateRef, this._hdrTmp && this._hdrTmp.templateRef, this._ftrTmp && this._ftrTmp.templateRef);
- });
- if (needClean) {
- this._cd.detectChanges();
- }
- // at this point, this fn was called from within another
- // requestAnimationFrame, so the next dom reads/writes within the next frame
- // wait a frame before trying to read and calculate the dimensions
- // ******** DOM READ ****************
- this._dom.read(() => initReadNodes(this._plt, nodes, cells, data));
- this._dom.write(() => {
- // update the bound context for each node
- updateNodeContext(nodes, cells, data);
- // ******** DOM WRITE ****************
- this._stepChangeDetection();
- // ******** DOM WRITE ****************
- this._stepDOMWrite();
- // ******** DOM WRITE ****************
- this._content.imgsUpdate();
- // First time load
- if (!this._lastEle) {
- // add an element at the end so :last-child css doesn't get messed up
- // ******** DOM WRITE ****************
- var ele = this._elementRef.nativeElement;
- var lastEle = this._renderer.createElement(ele, 'div');
- lastEle.className = 'virtual-last';
- this._lastEle = true;
- // ******** DOM WRITE ****************
- this.setElementClass('virtual-scroll', true);
- // ******** DOM WRITE ****************
- this.setElementClass('virtual-loading', false);
- }
- (void 0) /* assert */;
- });
- });
- }
- /**
- * @hidden
- */
- resize() {
- // only continue if we've already initialized
- if (!this._init) {
- return;
- }
- // check if component is rendered in the dom currently
- if (this._elementRef.nativeElement.offsetParent === null) {
- return;
- }
- (void 0) /* console.debug */;
- this.calcDimensions();
- this.writeUpdate(false);
- }
- /**
- * @hidden
- */
- _stepDOMWrite() {
- const cells = this._cells;
- const nodes = this._nodes;
- // ******** DOM WRITE ****************
- writeToNodes(this._plt, nodes, cells, this._recordSize);
- // ******** DOM WRITE ****************
- this._setHeight(estimateHeight(this._recordSize, cells[cells.length - 1], this._vHeight, 0.25));
- // we're done here, good work
- this._queue = 1 /* NoChanges */;
- }
- /**
- * @hidden
- */
- _stepChangeDetection() {
- // we need to do some change detection in this frame
- // we've got work painting do, let's throw it in the
- // domWrite callback so everyone plays nice
- // ******** DOM WRITE ****************
- const nodes = this._nodes;
- for (var i = 0; i < nodes.length; i++) {
- if (nodes[i].hasChanges) {
- nodes[i].view.detectChanges();
- }
- }
- // on the next frame we need write to the dom nodes manually
- this._queue = 3 /* DomWrite */;
- }
- /**
- * @hidden
- */
- _stepNoChanges() {
- const data = this._data;
- // let's see if we've scroll far enough to require another check
- const diff = data.scrollDiff = (data.scrollTop - this._lastCheck);
- if (Math.abs(diff) < SCROLL_DIFFERENCE_MINIMUM) {
- return;
- }
- const cells = this._cells;
- const nodes = this._nodes;
- const records = this._records;
- // don't bother updating if the scrollTop hasn't changed much
- this._lastCheck = data.scrollTop;
- if (diff > 0) {
- // load data we may not have processed yet
- var stopAtHeight = (data.scrollTop + data.renderHeight);
- processRecords(stopAtHeight, records, cells, this._hdrFn, this._ftrFn, data);
- }
- // ******** DOM READ ****************
- updateDimensions(this._plt, nodes, cells, data, false);
- adjustRendered(cells, data);
- var hasChanges = populateNodeData(data.topCell, data.bottomCell, diff > 0, cells, records, nodes, this._itmTmp.viewContainer, this._itmTmp.templateRef, this._hdrTmp && this._hdrTmp.templateRef, this._ftrTmp && this._ftrTmp.templateRef);
- if (hasChanges) {
- // queue making updates in the next frame
- this._queue = 2 /* ChangeDetection */;
- // update the bound context for each node
- updateNodeContext(nodes, cells, data);
- }
- }
- /**
- * @hidden
- */
- scrollUpdate(ev) {
- // set the scroll top from the scroll event
- this._data.scrollTop = ev.scrollTop;
- // there is a queue system so that we can
- // spread out the work over multiple frames
- const queue = this._queue;
- if (queue === 1 /* NoChanges */) {
- // no dom writes or change detection to take care of
- this._stepNoChanges();
- }
- else if (queue === 2 /* ChangeDetection */) {
- this._dom.write(() => this._stepChangeDetection());
- }
- else {
- (void 0) /* assert */;
- // there are DOM writes we need to take care of in this frame
- this._dom.write(() => this._stepDOMWrite());
- }
- }
- /**
- * @hidden
- * DOM WRITE
- */
- scrollEnd() {
- // ******** DOM READ ****************
- updateDimensions(this._plt, this._nodes, this._cells, this._data, false);
- adjustRendered(this._cells, this._data);
- populateNodeData(this._data.topCell, this._data.bottomCell, true, this._cells, this._records, this._nodes, this._itmTmp.viewContainer, this._itmTmp.templateRef, this._hdrTmp && this._hdrTmp.templateRef, this._ftrTmp && this._ftrTmp.templateRef);
- // ******** DOM WRITE ***************
- this._dom.write(() => {
- // update the bound context for each node
- updateNodeContext(this._nodes, this._cells, this._data);
- // ******** DOM WRITE ***************
- this._stepChangeDetection();
- // ******** DOM WRITE ****************
- this._stepDOMWrite();
- });
- }
- /**
- * @hidden
- * NO DOM
- */
- _listeners() {
- (void 0) /* assert */;
- if (!this._scrollSub) {
- if (this._config.getBoolean('virtualScrollEventAssist')) {
- // use JS scrolling for iOS UIWebView
- // goal is to completely remove this when iOS
- // fully supports scroll events
- // listen to JS scroll events
- this._content.enableJsScroll();
- }
- this._resizeSub = this._plt.resize.subscribe(this.resize.bind(this));
- this._scrollSub = this._content.ionScroll.subscribe(this.scrollUpdate.bind(this));
- this._scrollEndSub = this._content.ionScrollEnd.subscribe(this.scrollEnd.bind(this));
- }
- }
- /**
- * @hidden
- * DOM WRITE
- */
- _setHeight(newVirtualHeight) {
- if (newVirtualHeight !== this._vHeight) {
- // ******** DOM WRITE ****************
- this._renderer.setElementStyle(this._elementRef.nativeElement, 'height', newVirtualHeight > 0 ? newVirtualHeight + 'px' : '');
- this._vHeight = newVirtualHeight;
- (void 0) /* console.debug */;
- }
- }
- /**
- * @hidden
- */
- ngAfterContentInit() {
- (void 0) /* assert */;
- if (!this.approxItemHeight) {
- this.approxItemHeight = '40px';
- console.warn('Virtual Scroll: Please provide an "approxItemHeight" input to ensure proper virtual scroll rendering');
- }
- }
- /**
- * @hidden
- */
- setElementClass(className, add) {
- this._renderer.setElementClass(this._elementRef.nativeElement, className, add);
- }
- /**
- * @hidden
- */
- ngOnDestroy() {
- this._resizeSub && this._resizeSub.unsubscribe();
- this._scrollSub && this._scrollSub.unsubscribe();
- this._scrollEndSub && this._scrollEndSub.unsubscribe();
- this._resizeSub = this._scrollEndSub = this._scrollSub = null;
- this._hdrFn = this._ftrFn = this._records = this._cells = this._nodes = this._data = null;
- }
- }
- VirtualScroll.decorators = [
- { type: Directive, args: [{
- selector: '[virtualScroll]'
- },] },
- ];
- /** @nocollapse */
- VirtualScroll.ctorParameters = () => [
- { type: IterableDiffers, },
- { type: ElementRef, },
- { type: Renderer, },
- { type: NgZone, },
- { type: ChangeDetectorRef, },
- { type: Content, },
- { type: Platform, },
- { type: ViewController, },
- { type: Config, },
- { type: DomController, },
- ];
- VirtualScroll.propDecorators = {
- '_itmTmp': [{ type: ContentChild, args: [VirtualItem,] },],
- '_hdrTmp': [{ type: ContentChild, args: [VirtualHeader,] },],
- '_ftrTmp': [{ type: ContentChild, args: [VirtualFooter,] },],
- 'virtualScroll': [{ type: Input },],
- 'bufferRatio': [{ type: Input },],
- 'approxItemWidth': [{ type: Input },],
- 'approxItemHeight': [{ type: Input },],
- 'approxHeaderWidth': [{ type: Input },],
- 'approxHeaderHeight': [{ type: Input },],
- 'approxFooterWidth': [{ type: Input },],
- 'approxFooterHeight': [{ type: Input },],
- 'headerFn': [{ type: Input },],
- 'footerFn': [{ type: Input },],
- 'virtualTrackBy': [{ type: Input },],
- };
- const SCROLL_DIFFERENCE_MINIMUM = 40;
- //# sourceMappingURL=virtual-scroll.js.map
|