@trademe/ng-defer-load
Version:
Angular directive to load elements lazily
152 lines (147 loc) • 6.67 kB
JavaScript
import { isPlatformServer, isPlatformBrowser, CommonModule } from '@angular/common';
import * as i0 from '@angular/core';
import { EventEmitter, PLATFORM_ID, Directive, Inject, Input, Output, NgModule } from '@angular/core';
import { fromEvent } from 'rxjs';
import { debounceTime } from 'rxjs/operators';
class DeferLoadDirective {
constructor(_element, _zone, platformId) {
this._element = _element;
this._zone = _zone;
this.platformId = platformId;
this.preRender = true;
this.fallbackEnabled = true;
this.removeListenersAfterLoad = true;
this.deferLoad = new EventEmitter();
this.checkForIntersection = (entries) => {
entries.forEach((entry) => {
if (this.checkIfIntersecting(entry)) {
this.load();
if (this._intersectionObserver && this._element.nativeElement) {
this._intersectionObserver.unobserve((this._element.nativeElement));
}
}
});
};
this.onScroll = () => {
if (this.isVisible()) {
this._zone.run(() => this.load());
}
};
}
ngOnInit() {
if ((isPlatformServer(this.platformId) && this.preRender)
|| (isPlatformBrowser(this.platformId) && !this.fallbackEnabled && !this.hasCompatibleBrowser())) {
this.load();
}
}
ngAfterViewInit() {
if (isPlatformBrowser(this.platformId)) {
if (this.hasCompatibleBrowser()) {
this.registerIntersectionObserver();
if (this._intersectionObserver && this._element.nativeElement) {
this._intersectionObserver.observe((this._element.nativeElement));
}
}
else if (this.fallbackEnabled) {
this.addScrollListeners();
}
}
}
hasCompatibleBrowser() {
const hasIntersectionObserver = 'IntersectionObserver' in window;
const userAgent = window.navigator.userAgent;
const matches = userAgent.match(/Edge\/(\d*)\./i);
const isEdge = !!matches && matches.length > 1;
const isEdgeVersion16OrBetter = isEdge && (!!matches && parseInt(matches[1], 10) > 15);
return hasIntersectionObserver && (!isEdge || isEdgeVersion16OrBetter);
}
ngOnDestroy() {
this.removeListeners();
}
registerIntersectionObserver() {
if (!!this._intersectionObserver) {
return;
}
this._intersectionObserver = new IntersectionObserver(entries => {
this.checkForIntersection(entries);
}, {});
}
checkIfIntersecting(entry) {
// For Samsung native browser, IO has been partially implemented whereby the
// callback fires, but entry object is empty. We will check manually.
if (entry && entry.time) {
return entry.isIntersecting && entry.target === this._element.nativeElement;
}
return this.isVisible();
}
load() {
if (this.removeListenersAfterLoad) {
this.removeListeners();
}
this.deferLoad.emit();
}
addScrollListeners() {
if (this.isVisible()) {
this.load();
return;
}
this._zone.runOutsideAngular(() => {
this._scrollSubscription = fromEvent(window, 'scroll')
.pipe(debounceTime(50))
.subscribe(this.onScroll);
});
}
removeListeners() {
var _a, _b;
(_a = this._scrollSubscription) === null || _a === void 0 ? void 0 : _a.unsubscribe();
(_b = this._intersectionObserver) === null || _b === void 0 ? void 0 : _b.disconnect();
}
isVisible() {
let scrollPosition = this.getScrollPosition();
let elementOffset = this._element.nativeElement.getBoundingClientRect().top + window.scrollY;
return elementOffset <= scrollPosition;
}
getScrollPosition() {
// Getting screen size and scroll position for IE
return window.scrollY + (document.documentElement.clientHeight || document.body.clientHeight);
}
}
DeferLoadDirective.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: DeferLoadDirective, deps: [{ token: i0.ElementRef }, { token: i0.NgZone }, { token: PLATFORM_ID }], target: i0.ɵɵFactoryTarget.Directive });
DeferLoadDirective.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "14.2.12", type: DeferLoadDirective, selector: "[deferLoad]", inputs: { preRender: "preRender", fallbackEnabled: "fallbackEnabled", removeListenersAfterLoad: "removeListenersAfterLoad" }, outputs: { deferLoad: "deferLoad" }, ngImport: i0 });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: DeferLoadDirective, decorators: [{
type: Directive,
args: [{
selector: '[deferLoad]'
}]
}], ctorParameters: function () {
return [{ type: i0.ElementRef }, { type: i0.NgZone }, { type: Object, decorators: [{
type: Inject,
args: [PLATFORM_ID]
}] }];
}, propDecorators: { preRender: [{
type: Input
}], fallbackEnabled: [{
type: Input
}], removeListenersAfterLoad: [{
type: Input
}], deferLoad: [{
type: Output
}] } });
class DeferLoadModule {
}
DeferLoadModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: DeferLoadModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
DeferLoadModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "14.2.12", ngImport: i0, type: DeferLoadModule, declarations: [DeferLoadDirective], imports: [CommonModule], exports: [DeferLoadDirective] });
DeferLoadModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: DeferLoadModule, imports: [CommonModule] });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: DeferLoadModule, decorators: [{
type: NgModule,
args: [{
imports: [CommonModule],
declarations: [DeferLoadDirective],
exports: [DeferLoadDirective]
}]
}] });
/**
* Generated bundle index. Do not edit.
*/
export { DeferLoadDirective, DeferLoadModule };
//# sourceMappingURL=trademe-ng-defer-load.mjs.map