UNPKG

@trademe/ng-defer-load

Version:

Angular directive to load elements lazily

152 lines (147 loc) 6.67 kB
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