UNPKG

@pshurygin/ngx-quicklink

Version:
292 lines (285 loc) 9.82 kB
import { __decorate, __metadata, __param } from 'tslib'; import { Injectable, InjectionToken, NgZone, Input, Directive, Inject, Optional, ElementRef, NgModule } from '@angular/core'; import { Router, PRIMARY_OUTLET, RouterPreloader, RouterLink, RouterLinkWithHref } from '@angular/router'; import { EMPTY } from 'rxjs'; // Using a global registry so we can keep it populated across lazy-loaded // modules with different parent injectors which create instance of the registry. const globalRegistry = []; let PrefetchRegistry = class PrefetchRegistry { constructor(router) { this.router = router; this.trees = globalRegistry; } add(tree) { this.trees.push(tree); } remove(tree) { this.trees.splice(this.trees.indexOf(tree), 1); } shouldPrefetch(url) { const tree = this.router.parseUrl(url); return this.trees.some(containsTree.bind(null, tree)); } }; PrefetchRegistry = __decorate([ Injectable(), __metadata("design:paramtypes", [Router]) ], PrefetchRegistry); function containsQueryParams(container, containee) { // TODO: This does not handle array params correctly. return (Object.keys(containee).length <= Object.keys(container).length && Object.keys(containee).every(key => containee[key] === container[key])); } function containsTree(containee, container) { return (containsQueryParams(container.queryParams, containee.queryParams) && containsSegmentGroup(container.root, containee.root, containee.root.segments)); } function containsSegmentGroup(container, containee, containeePaths) { if (container.segments.length > containeePaths.length) { const current = container.segments.slice(0, containeePaths.length); if (!equalPath(current, containeePaths)) return false; if (containee.hasChildren()) return false; return true; } else if (container.segments.length === containeePaths.length) { if (!equalPath(container.segments, containeePaths)) return false; if (!containee.hasChildren()) return true; for (const c in containee.children) { if (!container.children[c]) break; if (containsSegmentGroup(container.children[c], containee.children[c], containee.children[c].segments)) return true; } return false; } else { const current = containeePaths.slice(0, container.segments.length); const next = containeePaths.slice(container.segments.length); if (!equalPath(container.segments, current)) return false; if (!container.children[PRIMARY_OUTLET]) return false; return containsSegmentGroup(container.children[PRIMARY_OUTLET], containee, next); } } function equalPath(as, bs) { if (as.length !== bs.length) return false; return as.every((a, i) => a.path === bs[i].path || a.path.startsWith(':') || bs[i].path.startsWith(':')); } const ɵ0 = function (cb) { const start = Date.now(); return setTimeout(function () { cb({ didTimeout: false, timeRemaining: function () { return Math.max(0, 50 - (Date.now() - start)); } }); }, 1); }, ɵ1 = () => { }; const requestIdleCallback = typeof window !== 'undefined' ? window.requestIdleCallback || ɵ0 : ɵ1; const observerSupported = () => typeof window !== 'undefined' ? !!window.IntersectionObserver : false; const LinkHandler = new InjectionToken('LinkHandler'); let ObservableLinkHandler = class ObservableLinkHandler { constructor(loader, queue, ngZone) { this.loader = loader; this.queue = queue; this.ngZone = ngZone; this.elementLink = new Map(); this.observer = observerSupported() ? new IntersectionObserver(entries => { entries.forEach(entry => { if (entry.isIntersecting) { const link = entry.target; const routerLink = this.elementLink.get(link); if (!routerLink || !routerLink.urlTree) return; this.queue.add(routerLink.urlTree); this.observer.unobserve(link); requestIdleCallback(() => { this.loader.preload().subscribe(() => void 0); this.queue.remove(routerLink.urlTree); }); } }); }) : null; } register(el) { this.elementLink.set(el.element, el); this.ngZone.runOutsideAngular(() => { this.observer.observe(el.element); }); } // First call to unregister will not hit this. unregister(el) { if (this.elementLink.has(el.element)) { this.observer.unobserve(el.element); this.elementLink.delete(el.element); } } supported() { return observerSupported(); } }; ObservableLinkHandler = __decorate([ Injectable(), __metadata("design:paramtypes", [RouterPreloader, PrefetchRegistry, NgZone]) ], ObservableLinkHandler); let PreloadLinkHandler = class PreloadLinkHandler { constructor(loader, queue) { this.loader = loader; this.queue = queue; } register(el) { this.queue.add(el.urlTree); requestIdleCallback(() => this.loader.preload().subscribe(() => void 0)); } unregister(_) { } supported() { return true; } }; PreloadLinkHandler = __decorate([ Injectable(), __metadata("design:paramtypes", [RouterPreloader, PrefetchRegistry]) ], PreloadLinkHandler); let LinkDirective = class LinkDirective { constructor(linkHandlers, el, link, linkWithHref) { this.linkHandlers = linkHandlers; this.el = el; this.linkHandler = this.linkHandlers.filter(h => h.supported()).shift(); this.rl = link || linkWithHref; } ngOnChanges(c) { if (c.routerLink) { this.linkHandler.unregister(this); this.linkHandler.register(this); } } ngOnDestroy() { this.linkHandler.unregister(this); } get element() { return this.el.nativeElement; } get urlTree() { return this.rl.urlTree; } }; __decorate([ Input(), __metadata("design:type", Object) ], LinkDirective.prototype, "routerLink", void 0); LinkDirective = __decorate([ Directive({ selector: '[routerLink]' }), __param(0, Inject(LinkHandler)), __param(2, Optional()), __param(3, Optional()), __metadata("design:paramtypes", [Array, ElementRef, RouterLink, RouterLinkWithHref]) ], LinkDirective); let QuicklinkStrategy = class QuicklinkStrategy { constructor(queue, router) { this.queue = queue; this.router = router; this.loading = new Set(); } preload(route, load) { if (this.loading.has(route)) { // Don't preload the same route twice return EMPTY; } const conn = typeof window !== 'undefined' ? navigator.connection : undefined; if (conn) { // Don't preload if the user is on 2G. or if Save-Data is enabled.. if ((conn.effectiveType || '').includes('2g') || conn.saveData) return EMPTY; } // Prevent from preloading if (route.data && route.data.preload === false) { return EMPTY; } const fullPath = findPath(this.router.config, route); if (this.queue.shouldPrefetch(fullPath)) { this.loading.add(route); return load(); } return EMPTY; } }; QuicklinkStrategy = __decorate([ Injectable(), __metadata("design:paramtypes", [PrefetchRegistry, Router]) ], QuicklinkStrategy); const findPath = (config, route) => { config = config.slice(); const parent = new Map(); const visited = new Set(); while (config.length) { const el = config.shift(); visited.add(el); if (el === route) break; let children = el.children || []; const current = el._loadedConfig; if (current && current.routes) { children = children.concat(current.routes); } children.forEach((r) => { if (visited.has(r)) return; parent.set(r, el); config.push(r); }); } let path = ''; let current = route; while (current) { if (isPrimaryRoute(current)) { path = `/${current.path}${path}`; } else { path = `/(${current.outlet}:${current.path}${path})`; } current = parent.get(current); } return path; }; function isPrimaryRoute(route) { return route.outlet === PRIMARY_OUTLET || !route.outlet; } let QuicklinkModule = class QuicklinkModule { }; QuicklinkModule = __decorate([ NgModule({ declarations: [LinkDirective], providers: [ { provide: LinkHandler, useClass: ObservableLinkHandler, multi: true }, { provide: LinkHandler, useClass: PreloadLinkHandler, multi: true }, PrefetchRegistry, QuicklinkStrategy ], exports: [LinkDirective] }) ], QuicklinkModule); export { QuicklinkModule, QuicklinkStrategy, LinkHandler as ɵa, ObservableLinkHandler as ɵb, PreloadLinkHandler as ɵc, PrefetchRegistry as ɵe, LinkDirective as ɵɵLinkDirective }; //# sourceMappingURL=pshurygin-ngx-quicklink.js.map