lzy-load
Version:
LzyLoad is an Angular Lib for loading content on demand
363 lines (353 loc) • 18.1 kB
JavaScript
(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