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
JavaScript
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