UNPKG

ng-lazyload-image-angular-universal

Version:

This is an extension from ng-lazyload-image, made available for Angular Universal.

817 lines (798 loc) 23.3 kB
import { isPlatformServer } from '@angular/common'; import { Directive, ElementRef, EventEmitter, Inject, Input, NgZone, Optional, Output, PLATFORM_ID, NgModule } from '@angular/core'; import { Observable, empty, of, ReplaySubject, Subject } from 'rxjs'; import { sampleTime, share, startWith, catchError, filter, map, mergeMap, take, tap, switchMap, first } from 'rxjs/operators'; /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** @type {?} */ var cssClassNames = { loaded: 'ng-lazyloaded', loading: 'ng-lazyloading', failed: 'ng-failed-lazyloaded', }; /** * @param {?} element * @param {?} cssClassName * @return {?} */ function removeCssClassName(element, cssClassName) { element.className = element.className.replace(cssClassName, ''); } /** * @param {?} element * @param {?} cssClassName * @return {?} */ function addCssClassName(element, cssClassName) { if (!element.className.includes(cssClassName)) { element.className += " " + cssClassName; } } /** * @param {?} element * @param {?} cssClassName * @return {?} */ function hasCssClassName(element, cssClassName) { return element.className && element.className.includes(cssClassName); } /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * @return {?} */ function isWindowDefined() { return typeof window !== 'undefined'; } /** * @param {?} element * @return {?} */ function isChildOfPicture(element) { return Boolean(element.parentElement && element.parentElement.nodeName.toLowerCase() === 'picture'); } /** * @param {?} element * @return {?} */ function isImageElement(element) { return element.nodeName.toLowerCase() === 'img'; } /** * @param {?} element * @param {?} imagePath * @param {?} useSrcset * @return {?} */ function setImage(element, imagePath, useSrcset) { if (isImageElement(element)) { if (useSrcset) { element.srcset = imagePath; } else { element.src = imagePath; } } else { element.style.backgroundImage = "url('" + imagePath + "')"; } return element; } /** * @param {?} attrName * @return {?} */ function setSources(attrName) { return (/** * @param {?} image * @return {?} */ function (image) { /** @type {?} */ var sources = image.parentElement.getElementsByTagName('source'); for (var i = 0; i < sources.length; i++) { /** @type {?} */ var attrValue = sources[i].getAttribute(attrName); if (attrValue) { sources[i].srcset = attrValue; } } }); } /** @type {?} */ var setSourcesToDefault = setSources('defaultImage'); /** @type {?} */ var setSourcesToLazy = setSources('lazyLoad'); /** @type {?} */ var setSourcesToError = setSources('errorImage'); /** * @param {?} setSourcesFn * @return {?} */ function setImageAndSources(setSourcesFn) { return (/** * @param {?} element * @param {?} imagePath * @param {?} useSrcset * @return {?} */ function (element, imagePath, useSrcset) { if (isImageElement(element) && isChildOfPicture(element)) { setSourcesFn(element); } if (imagePath) { setImage(element, imagePath, useSrcset); } }); } /** @type {?} */ var setImageAndSourcesToDefault = setImageAndSources(setSourcesToDefault); /** @type {?} */ var setImageAndSourcesToLazy = setImageAndSources(setSourcesToLazy); /** @type {?} */ var setImageAndSourcesToError = setImageAndSources(setSourcesToError); /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** @type {?} */ var end = (/** * @param {?} __0 * @return {?} */ function (_a) { var element = _a.element; return addCssClassName(element, cssClassNames.loaded); }); /** @type {?} */ var loadImage = (/** * @param {?} __0 * @return {?} */ function (_a) { var element = _a.element, useSrcset = _a.useSrcset, imagePath = _a.imagePath; /** @type {?} */ var img; if (isImageElement(element) && isChildOfPicture(element)) { /** @type {?} */ var parentClone = (/** @type {?} */ (element.parentNode.cloneNode(true))); img = parentClone.getElementsByTagName('img')[0]; setSourcesToLazy(img); setImage(img, imagePath, useSrcset); } else { img = new Image(); if (isImageElement(element) && element.sizes) { img.sizes = element.sizes; } if (useSrcset) { img.srcset = imagePath; } else { img.src = imagePath; } } return Observable.create((/** * @param {?} observer * @return {?} */ function (observer) { img.onload = (/** * @return {?} */ function () { observer.next(imagePath); observer.complete(); }); img.onerror = (/** * @param {?} err * @return {?} */ function (err) { observer.error(null); }); })); }); /** @type {?} */ var setErrorImage = (/** * @param {?} __0 * @return {?} */ function (_a) { var element = _a.element, errorImagePath = _a.errorImagePath, useSrcset = _a.useSrcset; setImageAndSourcesToError(element, errorImagePath, useSrcset); addCssClassName(element, cssClassNames.failed); }); /** @type {?} */ var setLoadedImage = (/** * @param {?} __0 * @return {?} */ function (_a) { var element = _a.element, imagePath = _a.imagePath, useSrcset = _a.useSrcset; setImageAndSourcesToLazy(element, imagePath, useSrcset); }); /** @type {?} */ var setup = (/** * @param {?} __0 * @return {?} */ function (_a) { var element = _a.element, defaultImagePath = _a.defaultImagePath, useSrcset = _a.useSrcset; setImageAndSourcesToDefault(element, defaultImagePath, useSrcset); if (hasCssClassName(element, cssClassNames.loaded)) { removeCssClassName(element, cssClassNames.loaded); } }); /** @type {?} */ var sharedPreset = { finally: end, loadImage: loadImage, setErrorImage: setErrorImage, setLoadedImage: setLoadedImage, setup: setup }; /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ var Rect = /** @class */ (function () { function Rect(left, top, right, bottom) { this.left = left; this.top = top; this.right = right; this.bottom = bottom; } /** * @param {?} element * @return {?} */ Rect.fromElement = /** * @param {?} element * @return {?} */ function (element) { var _a = element.getBoundingClientRect(), left = _a.left, top = _a.top, right = _a.right, bottom = _a.bottom; if (left === 0 && top === 0 && right === 0 && bottom === 0) { return Rect.empty; } else { return new Rect(left, top, right, bottom); } }; /** * @param {?} _window * @return {?} */ Rect.fromWindow = /** * @param {?} _window * @return {?} */ function (_window) { return new Rect(0, 0, _window.innerWidth, _window.innerHeight); }; /** * @param {?} inflateBy * @return {?} */ Rect.prototype.inflate = /** * @param {?} inflateBy * @return {?} */ function (inflateBy) { this.left -= inflateBy; this.top -= inflateBy; this.right += inflateBy; this.bottom += inflateBy; }; /** * @param {?} rect * @return {?} */ Rect.prototype.intersectsWith = /** * @param {?} rect * @return {?} */ function (rect) { return rect.left < this.right && this.left < rect.right && rect.top < this.bottom && this.top < rect.bottom; }; /** * @param {?} rect * @return {?} */ Rect.prototype.getIntersectionWith = /** * @param {?} rect * @return {?} */ function (rect) { /** @type {?} */ var left = Math.max(this.left, rect.left); /** @type {?} */ var top = Math.max(this.top, rect.top); /** @type {?} */ var right = Math.min(this.right, rect.right); /** @type {?} */ var bottom = Math.min(this.bottom, rect.bottom); if (right >= left && bottom >= top) { return new Rect(left, top, right, bottom); } else { return Rect.empty; } }; Rect.empty = new Rect(0, 0, 0, 0); return Rect; }()); /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** @type {?} */ var scrollListeners = new WeakMap(); /** * @param {?} obs * @param {?=} scheduler * @return {?} */ function sampleObservable(obs, scheduler) { return obs.pipe(sampleTime(100, scheduler), share(), startWith('')); } // Only create one scroll listener per target and share the observable. // Typical, there will only be one observable per application /** @type {?} */ var getScrollListener = (/** * @param {?} scrollTarget * @return {?} */ function (scrollTarget) { if (!scrollTarget || typeof scrollTarget.addEventListener !== 'function') { if (isWindowDefined()) { console.warn('`addEventListener` on ' + scrollTarget + ' (scrollTarget) is not a function. Skipping this target'); } return empty(); } if (scrollListeners.has(scrollTarget)) { return scrollListeners.get(scrollTarget); } /** @type {?} */ var srollEvent = Observable.create((/** * @param {?} observer * @return {?} */ function (observer) { /** @type {?} */ var eventName = 'scroll'; /** @type {?} */ var handler = (/** * @param {?} event * @return {?} */ function (event) { return observer.next(event); }); /** @type {?} */ var options = { passive: true, capture: false }; scrollTarget.addEventListener(eventName, handler, options); return (/** * @return {?} */ function () { return scrollTarget.removeEventListener(eventName, handler, options); }); })); /** @type {?} */ var listener = sampleObservable(srollEvent); scrollListeners.set(scrollTarget, listener); return listener; }); /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** @type {?} */ var isVisible = (/** * @param {?} __0 * @param {?=} getWindow * @return {?} */ function (_a, getWindow) { var element = _a.element, offset = _a.offset, scrollContainer = _a.scrollContainer; if (getWindow === void 0) { getWindow = (/** * @return {?} */ function () { return window; }); } /** @type {?} */ var elementBounds = Rect.fromElement(element); if (elementBounds === Rect.empty) { return false; } /** @type {?} */ var windowBounds = Rect.fromWindow(getWindow()); elementBounds.inflate(offset); if (scrollContainer) { /** @type {?} */ var scrollContainerBounds = Rect.fromElement(scrollContainer); /** @type {?} */ var intersection = scrollContainerBounds.getIntersectionWith(windowBounds); return elementBounds.intersectsWith(intersection); } else { return elementBounds.intersectsWith(windowBounds); } }); /** @type {?} */ var getObservable = (/** * @param {?} attributes * @return {?} */ function (attributes) { if (attributes.scrollObservable) { return attributes.scrollObservable.pipe(startWith('')); } if (attributes.scrollContainer) { return getScrollListener(attributes.scrollContainer); } return getScrollListener(isWindowDefined() ? window : undefined); }); /** @type {?} */ var scrollPreset = Object.assign({}, sharedPreset, { isVisible: isVisible, getObservable: getObservable }); /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * @template E * @param {?=} options * @return {?} */ function cretateHooks(options) { if (!options) { return scrollPreset; } /** @type {?} */ var hooks = {}; if (options.preset) { Object.assign(hooks, options.preset); } else { Object.assign(hooks, scrollPreset); } Object.keys(options) .filter((/** * @param {?} key * @return {?} */ function (key) { return key !== 'preset'; })) .forEach((/** * @param {?} key * @return {?} */ function (key) { hooks[key] = options[key]; })); return (/** @type {?} */ (hooks)); } /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * @template E * @param {?} hookSet * @param {?} attributes * @return {?} */ function lazyLoadImage(hookSet, attributes) { return (/** * @param {?} scrollObservable * @return {?} */ function (scrollObservable) { return scrollObservable.pipe(filter((/** * @param {?} event * @return {?} */ function (event) { return hookSet.isVisible({ element: attributes.element, event: event, offset: attributes.offset, scrollContainer: attributes.scrollContainer }); })), take(1), mergeMap((/** * @return {?} */ function () { return hookSet.loadImage(attributes); })), tap((/** * @param {?} imagePath * @return {?} */ function (imagePath) { return hookSet.setLoadedImage({ element: attributes.element, imagePath: imagePath, useSrcset: attributes.useSrcset }); })), map((/** * @return {?} */ function () { return true; })), catchError((/** * @return {?} */ function () { hookSet.setErrorImage(attributes); return of(false); })), tap((/** * @return {?} */ function () { return hookSet.finally(attributes); }))); }); } /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ var LazyLoadImageDirective = /** @class */ (function () { function LazyLoadImageDirective(el, ngZone, platformId, options) { this.platformId = platformId; // Whether srcset attribute should be used instead of src this.onLoad = new EventEmitter(); // Callback when an image is loaded this.elementRef = el; this.ngZone = ngZone; this.propertyChanges$ = new ReplaySubject(); this.hooks = cretateHooks(options); } /** * @return {?} */ LazyLoadImageDirective.prototype.ngOnChanges = /** * @return {?} */ function () { this.propertyChanges$.next({ element: this.elementRef.nativeElement, imagePath: this.lazyImage, defaultImagePath: this.defaultImage, errorImagePath: this.errorImage, useSrcset: this.useSrcset, offset: this.offset | 0, scrollContainer: this.scrollTarget, scrollObservable: this.scrollObservable }); }; /** * @return {?} */ LazyLoadImageDirective.prototype.ngAfterContentInit = /** * @return {?} */ function () { var _this = this; // Disable lazy load image in server side if (isPlatformServer(this.platformId)) { this.scrollSubscription = this.propertyChanges$ .pipe(tap((/** * @param {?} attributes * @return {?} */ function (attributes) { return _this.hooks.setup(attributes); })), first()) .subscribe(); return null; } this.ngZone.runOutsideAngular((/** * @return {?} */ function () { _this.scrollSubscription = _this.propertyChanges$ .pipe(tap((/** * @param {?} attributes * @return {?} */ function (attributes) { return _this.hooks.setup(attributes); })), switchMap((/** * @param {?} attributes * @return {?} */ function (attributes) { return _this.hooks .getObservable(attributes) .pipe(lazyLoadImage(_this.hooks, attributes)); }))) .subscribe((/** * @param {?} success * @return {?} */ function (success) { return _this.onLoad.emit(success); })); })); }; /** * @return {?} */ LazyLoadImageDirective.prototype.ngOnDestroy = /** * @return {?} */ function () { [this.scrollSubscription] .filter((/** * @param {?} subscription * @return {?} */ function (subscription) { return subscription && !subscription.isUnsubscribed; })) .forEach((/** * @param {?} subscription * @return {?} */ function (subscription) { return subscription.unsubscribe(); })); }; LazyLoadImageDirective.decorators = [ { type: Directive, args: [{ selector: '[lazyLoad]' },] } ]; /** @nocollapse */ LazyLoadImageDirective.ctorParameters = function () { return [ { type: ElementRef }, { type: NgZone }, { type: Object, decorators: [{ type: Inject, args: [PLATFORM_ID,] }] }, { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: ['options',] }] } ]; }; LazyLoadImageDirective.propDecorators = { lazyImage: [{ type: Input, args: ['lazyLoad',] }], defaultImage: [{ type: Input }], errorImage: [{ type: Input }], scrollTarget: [{ type: Input }], scrollObservable: [{ type: Input }], offset: [{ type: Input }], useSrcset: [{ type: Input }], onLoad: [{ type: Output }] }; return LazyLoadImageDirective; }()); /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ var LazyLoadImageModule = /** @class */ (function () { function LazyLoadImageModule() { } /** * @param {?} options * @return {?} */ LazyLoadImageModule.forRoot = /** * @param {?} options * @return {?} */ function (options) { return { ngModule: LazyLoadImageModule, providers: [{ provide: 'options', useValue: options }] }; }; LazyLoadImageModule.decorators = [ { type: NgModule, args: [{ declarations: [LazyLoadImageDirective], exports: [LazyLoadImageDirective] },] } ]; return LazyLoadImageModule; }()); /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** @type {?} */ var observers = new WeakMap(); /** @type {?} */ var intersectionSubject = new Subject(); /** * @param {?} entrys * @return {?} */ function loadingCallback(entrys) { entrys.forEach((/** * @param {?} entry * @return {?} */ function (entry) { return intersectionSubject.next(entry); })); } /** @type {?} */ var getIntersectionObserver = (/** * @param {?} attributes * @return {?} */ function (attributes) { if (!attributes.scrollContainer && !isWindowDefined()) { return empty(); } /** @type {?} */ var options = { root: attributes.scrollContainer }; if (attributes.offset) { options.rootMargin = attributes.offset + "px"; } /** @type {?} */ var scrollContainer = attributes.scrollContainer || window; /** @type {?} */ var observer = observers.get(scrollContainer); if (!observer) { observer = new IntersectionObserver(loadingCallback, options); observers.set(scrollContainer, observer); } observer.observe(attributes.element); return Observable.create((/** * @param {?} obs * @return {?} */ function (obs) { /** @type {?} */ var subscription = intersectionSubject.pipe(filter((/** * @param {?} entry * @return {?} */ function (entry) { return entry.target === attributes.element; }))).subscribe(obs); return (/** * @return {?} */ function () { subscription.unsubscribe(); observer.unobserve(attributes.element); }); })); }); /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** @type {?} */ var isVisible$1 = (/** * @param {?} __0 * @return {?} */ function (_a) { var event = _a.event; return event.isIntersecting; }); /** @type {?} */ var getObservable$1 = (/** * @param {?} attributes * @param {?=} _getInterObserver * @return {?} */ function (attributes, _getInterObserver) { if (_getInterObserver === void 0) { _getInterObserver = getIntersectionObserver; } if (attributes.scrollObservable) { return attributes.scrollObservable; } return _getInterObserver(attributes); }); /** @type {?} */ var intersectionObserverPreset = Object.assign({}, sharedPreset, { isVisible: isVisible$1, getObservable: getObservable$1 }); /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ export default LazyLoadImageModule; export { LazyLoadImageDirective, LazyLoadImageModule, intersectionObserverPreset, scrollPreset }; //# sourceMappingURL=ng-lazyload-image-angular-universal.js.map