UNPKG

lzy-load

Version:

LzyLoad is an Angular Lib for loading content on demand

363 lines (353 loc) 18.1 kB
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@angular/core'), require('rxjs'), require('rxjs/internal/operators')) : typeof define === 'function' && define.amd ? define('lzy-load', ['exports', '@angular/core', 'rxjs', 'rxjs/internal/operators'], factory) : (global = global || self, factory(global['lzy-load'] = {}, global.ng.core, global.rxjs, global.rxjs['internal/operators'])); }(this, (function (exports, core, rxjs, operators) { 'use strict'; var LL_PRO_NAME = 'lzyLoadItemPos'; var LzyLoadItemDirective = /** @class */ (function () { function LzyLoadItemDirective(hostElement, renderer) { this.hostElement = hostElement; this.renderer = renderer; // Field that represent the current state of the observed element this.isVisible = false; // Field to know if the content should be loaded this.loadContent = false; } Object.defineProperty(LzyLoadItemDirective.prototype, "pos", { get: function () { return this._pos; }, set: function (value) { if (value !== null && value !== undefined) { this._pos = value; // Marking the html element with the position that it is on the array so is easier to access it in the elements array this.renderer.setProperty(this.hostElement.nativeElement, LL_PRO_NAME, this._pos); } }, enumerable: false, configurable: true }); return LzyLoadItemDirective; }()); LzyLoadItemDirective.decorators = [ { type: core.Directive, args: [{ selector: '[lzyLoadItem]', exportAs: 'lzyLoadItem' },] } ]; LzyLoadItemDirective.ctorParameters = function () { return [ { type: core.ElementRef }, { type: core.Renderer2 } ]; }; LzyLoadItemDirective.propDecorators = { pos: [{ type: core.Input }] }; (function (LoadStrategy) { // Loading the elements when they get visible LoadStrategy[LoadStrategy["OnVisible"] = 0] = "OnVisible"; /* Using an number of loaded elements. The number is gonna be calculated as a percent of the number of visible elements in the view */ LoadStrategy[LoadStrategy["PctOfVisibleElem"] = 1] = "PctOfVisibleElem"; /* Loading a fixed lenght of element in advance plus the visible items in the viewport */ LoadStrategy[LoadStrategy["AheadOfVisElem"] = 2] = "AheadOfVisElem"; /* Using an number of loaded elements. Is calculated using as reference the total number of elements to display. Can't be greater thant the lenght of list*/ LoadStrategy[LoadStrategy["PctOfTotElem"] = 3] = "PctOfTotElem"; })(exports.LoadStrategy || (exports.LoadStrategy = {})); (function (UnloadStrategy) { /* Don't temove the elements once they have been loaded */ UnloadStrategy[UnloadStrategy["KeepLoaded"] = 0] = "KeepLoaded"; /* Unload the elements when they got out of the loaded elements' range in the view. This mode is gonna depend of the selected LoadingMode */ UnloadStrategy[UnloadStrategy["LeavLoadedRange"] = 1] = "LeavLoadedRange"; })(exports.UnloadStrategy || (exports.UnloadStrategy = {})); /* * Important note: Is required that the items marked as lazy-load-items using the "LazyLoadItemDirective" * are present in the template at the moment of the initialization of the component because the * ContentChildren query is evaluated just before the AfterContentInit life circle, * if the items are being added dynamically later to the template then is possible that the parent of this component needs to run manualy * change detection to notify this component that the template has changed. Example of this case is in the cases-table in * gallery-management */ var LzyLoadContComponent = /** @class */ (function () { function LzyLoadContComponent(renderer, hostElement) { this.renderer = renderer; this.hostElement = hostElement; // Component fields this.init = false; this.loadingRangeAmount = 0; this.preloadAmo = 0; this.scrollEvent = new rxjs.Subject(); this.onResizeEvent = new rxjs.Subject(); // new properties this.numOfVisiElem = 0; this.loadedRange = []; this.settings = { loadStrategy: exports.LoadStrategy.PctOfVisibleElem, unloadStrategy: exports.UnloadStrategy.KeepLoaded, percent: 2 }; // Subscription that is gonna handle the unsubscribe to all the other subscriptions this.subscription = new rxjs.Subscription(); } LzyLoadContComponent.prototype.ngOnInit = function () { this.registerIO(); this.initScroll(); this.initScreenResizing(); }; LzyLoadContComponent.prototype.registerIO = function () { // Initializing the IO options this.IOOptions = { root: null, rootMargin: '0px', threshold: [0.1] }; // If the container was passed then use it as a root for the IO if (this.container) { this.IOOptions.root = this.container; } else { // If the container input was not provided then use the self component as a container this.container = this.hostElement.nativeElement; this.IOOptions.root = this.container; } }; LzyLoadContComponent.prototype.initScroll = function () { var _this = this; // Listening for scroll event in the container this.renderer.listen(this.container, 'scroll', function () { _this.scrollEvent.next(); }); // subscribing to scroll events to preload the items that need to be // preload in both directions up and down of the current position // including 100 mil of debounce time this.subscription.add(this.scrollEvent.pipe(operators.debounceTime(100)).subscribe(function () { _this.updateLoadedElements(); })); }; LzyLoadContComponent.prototype.initScreenResizing = function () { var _this = this; // Subscribing to the resize event and giving it a 300 miliseconds delay to prevent trigger this code too offten this.subscription.add(this.onResizeEvent.pipe(operators.debounceTime(300)).subscribe(function () { // Updating the settings after changing the viewport _this.calSettings(); // resetting the component after the change in the layout _this.updateLoadedElements(); })); }; // Used for initialize the component settings LzyLoadContComponent.prototype.initialize = function () { this.initializeIntersectionObs(); }; LzyLoadContComponent.prototype.resetAndRecalculateSettings = function () { // Resetting the isReady variable to false that will trigger the initial calculations for the first // execution of the IO callback this.init = false; // Disconnecting from the observer and its targets if (this.observer) { this.observer.disconnect(); } // Initializing again the observer with the current elements this.initializeIntersectionObs(); }; LzyLoadContComponent.prototype.ngAfterContentInit = function () { var _this = this; // Listening for changes in the elements this.subscription.add(this.itemsIO.changes.subscribe(function () { _this.resetAndRecalculateSettings(); })); // Initializing if are items to observe for if (this.itemsIO.length > 0) { this.initialize(); } }; // Calculate preload amounts LzyLoadContComponent.prototype.calSettings = function () { switch (this.settings.loadStrategy) { case exports.LoadStrategy.AheadOfVisElem: { this.loadingRangeAmount = Math.ceil(this.settings.number + this.numOfVisiElem); // Calculating the amount of items to be load in both directions this.preloadAmo = this.settings.number; break; } case exports.LoadStrategy.PctOfVisibleElem: { // Calculating the amount of preload elements depending of the amount of visible items in the view this.loadingRangeAmount = Math.ceil(this.settings.factor * this.numOfVisiElem); // Calculating the amount of items to be load in both directions this.preloadAmo = Math.ceil((this.loadingRangeAmount - this.numOfVisiElem) / 2); break; } case exports.LoadStrategy.OnVisible: { this.loadingRangeAmount = this.numOfVisiElem; this.preloadAmo = 0; break; } case exports.LoadStrategy.PctOfTotElem: { // Calculating the amount of preload elements depending of the amount of visible items in the view this.loadingRangeAmount = Math.ceil(this.settings.percent * this.itemsIO.length); // Calculating the amount of items to be load in both directions this.preloadAmo = Math.ceil((this.loadingRangeAmount - this.numOfVisiElem) / 2); break; } } }; // loading the content LzyLoadContComponent.prototype.inLoadingRange = function (element) { element.loadContent = true; }; // unloading the content LzyLoadContComponent.prototype.outOfLoadingRange = function (element) { if (this.settings.unloadStrategy === exports.UnloadStrategy.LeavLoadedRange) { element.loadContent = false; } }; // Calculate the amount of visible elements that are in the exact moment and update the property in the component LzyLoadContComponent.prototype.getVisibleItems = function () { this.numOfVisiElem = this.itemsIO.filter(function (item) { return item.isVisible; }).length; return this.numOfVisiElem; }; // Initialize the IO and the targets to observe LzyLoadContComponent.prototype.initializeIntersectionObs = function () { var _this = this; // callback provided to the IO var ioCallBack = function (entries) { var updEntriesVisibility = function () { entries.forEach(function (entry) { // when an element is intersecting the view var itemElement = _this.itemsIO.toArray()[entry.target[LL_PRO_NAME]]; if (itemElement) { itemElement.isVisible = entry.isIntersecting; } }); }; // differentiating between the first call to the function and the rest. The first is always gonna be // the initialization of all the targets if (!_this.init) { // Updating the visibility of each element updEntriesVisibility(); // Calculating the amount of visible items and updating the property "visibleItemsAmo" for first time _this.getVisibleItems(); // calculating amount of visible items depending if the selected model for the component needs it _this.calSettings(); // Preloading the items _this.updateLoadedElements(); _this.init = true; } else { // This code will be executed always after the first time // Updating the visibility of each element updEntriesVisibility(); } }; // Creating the observer this.observer = new IntersectionObserver(ioCallBack, this.IOOptions); // Registering the items with the observer this.itemsIO.forEach(function (item) { _this.observer.observe(item.hostElement.nativeElement); }); }; // calculate the new range of elements in the list that should be preload LzyLoadContComponent.prototype.calLoadedRange = function (visibleItems) { var newRange = [0, 0]; // getting the range of current visible items var firstVisibleItem = visibleItems[0]; var lastVisibleItem = visibleItems[visibleItems.length - 1]; // Last item position in the array of items var lastItemPosInArray = (this.itemsIO.length - 1); // Setting the new start position to the current position menus the preload amount divided by two // in order to preload forward and in backward newRange[0] = Math.max(0, (firstVisibleItem.pos - this.preloadAmo)); if (newRange[0] === 0) { // prefetching the elements ahead of the last visible item newRange[1] = Math.min(this.loadingRangeAmount, lastItemPosInArray); } else if ((lastVisibleItem.pos + this.preloadAmo) >= lastItemPosInArray) { // pre-fetching the elements ahead and backward of the range of visible items newRange[1] = lastItemPosInArray; newRange[0] = Math.max(0, lastItemPosInArray - this.loadingRangeAmount); } else { newRange[1] = (lastVisibleItem.pos + this.preloadAmo); } return newRange; }; // Function that calculate the current visible items and the ones that need to be preload LzyLoadContComponent.prototype.updateLoadedElements = function () { var _this = this; // Getting the current visible elements in the container var visibleItems = this.itemsIO.filter(function (item) { return item.isVisible; }); // if are not visible items, then do nothing if (visibleItems.length > 0) { // Calculating the elements should be preload this.loadedRange = this.calLoadedRange(visibleItems); this.itemsIO.forEach(function (item, index) { if (index >= _this.loadedRange[0] && index <= _this.loadedRange[1]) { // element within the loading range _this.inLoadingRange(item); } else { // element out the loading range _this.outOfLoadingRange(item); } }); } }; // Listening to the resize event so we can recalculate the amount of visible items in the list and other // important properties that are use for the component LzyLoadContComponent.prototype.onResize = function () { this.onResizeEvent.next(); }; LzyLoadContComponent.prototype.ngOnDestroy = function () { if (this.observer) { this.observer.disconnect(); } this.subscription.unsubscribe(); }; return LzyLoadContComponent; }()); LzyLoadContComponent.decorators = [ { type: core.Component, args: [{ selector: 'lzy-load-cont', template: "<ng-content></ng-content>", styles: [":host{background:transparent;border:inherit;color:inherit;display:flex;margin:0;padding:0}"] },] } ]; LzyLoadContComponent.ctorParameters = function () { return [ { type: core.Renderer2 }, { type: core.ElementRef } ]; }; LzyLoadContComponent.propDecorators = { settings: [{ type: core.Input }], container: [{ type: core.Input }], itemsIO: [{ type: core.ContentChildren, args: [LzyLoadItemDirective, { descendants: true },] }], onResize: [{ type: core.HostListener, args: ['window:resize', [],] }] }; var LzyLoadModule = /** @class */ (function () { function LzyLoadModule() { } return LzyLoadModule; }()); LzyLoadModule.decorators = [ { type: core.NgModule, args: [{ declarations: [LzyLoadContComponent, LzyLoadItemDirective], imports: [], exports: [LzyLoadContComponent, LzyLoadItemDirective] },] } ]; /* * Public API Surface of lzy-load */ /** * Generated bundle index. Do not edit. */ exports.LL_PRO_NAME = LL_PRO_NAME; exports.LzyLoadContComponent = LzyLoadContComponent; exports.LzyLoadItemDirective = LzyLoadItemDirective; exports.LzyLoadModule = LzyLoadModule; Object.defineProperty(exports, '__esModule', { value: true }); }))); //# sourceMappingURL=lzy-load.umd.js.map