@pshurygin/ngx-quicklink
Version:
Quicklink for Angular
318 lines (311 loc) • 11.5 kB
JavaScript
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.
var globalRegistry = [];
var PrefetchRegistry = /** @class */ (function () {
function PrefetchRegistry(router) {
this.router = router;
this.trees = globalRegistry;
}
PrefetchRegistry.prototype.add = function (tree) {
this.trees.push(tree);
};
PrefetchRegistry.prototype.remove = function (tree) {
this.trees.splice(this.trees.indexOf(tree), 1);
};
PrefetchRegistry.prototype.shouldPrefetch = function (url) {
var tree = this.router.parseUrl(url);
return this.trees.some(containsTree.bind(null, tree));
};
PrefetchRegistry = __decorate([
Injectable(),
__metadata("design:paramtypes", [Router])
], PrefetchRegistry);
return 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(function (key) { return 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) {
var 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 (var 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 {
var current = containeePaths.slice(0, container.segments.length);
var 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(function (a, i) { return a.path === bs[i].path || a.path.startsWith(':') || bs[i].path.startsWith(':'); });
}
var ɵ0 = function (cb) {
var start = Date.now();
return setTimeout(function () {
cb({
didTimeout: false,
timeRemaining: function () {
return Math.max(0, 50 - (Date.now() - start));
}
});
}, 1);
}, ɵ1 = function () { };
var requestIdleCallback = typeof window !== 'undefined'
? window.requestIdleCallback || ɵ0
: ɵ1;
var observerSupported = function () {
return typeof window !== 'undefined' ? !!window.IntersectionObserver : false;
};
var LinkHandler = new InjectionToken('LinkHandler');
var ObservableLinkHandler = /** @class */ (function () {
function ObservableLinkHandler(loader, queue, ngZone) {
var _this = this;
this.loader = loader;
this.queue = queue;
this.ngZone = ngZone;
this.elementLink = new Map();
this.observer = observerSupported()
? new IntersectionObserver(function (entries) {
entries.forEach(function (entry) {
if (entry.isIntersecting) {
var link = entry.target;
var routerLink_1 = _this.elementLink.get(link);
if (!routerLink_1 || !routerLink_1.urlTree)
return;
_this.queue.add(routerLink_1.urlTree);
_this.observer.unobserve(link);
requestIdleCallback(function () {
_this.loader.preload().subscribe(function () { return void 0; });
_this.queue.remove(routerLink_1.urlTree);
});
}
});
})
: null;
}
ObservableLinkHandler.prototype.register = function (el) {
var _this = this;
this.elementLink.set(el.element, el);
this.ngZone.runOutsideAngular(function () {
_this.observer.observe(el.element);
});
};
// First call to unregister will not hit this.
ObservableLinkHandler.prototype.unregister = function (el) {
if (this.elementLink.has(el.element)) {
this.observer.unobserve(el.element);
this.elementLink.delete(el.element);
}
};
ObservableLinkHandler.prototype.supported = function () {
return observerSupported();
};
ObservableLinkHandler = __decorate([
Injectable(),
__metadata("design:paramtypes", [RouterPreloader, PrefetchRegistry, NgZone])
], ObservableLinkHandler);
return ObservableLinkHandler;
}());
var PreloadLinkHandler = /** @class */ (function () {
function PreloadLinkHandler(loader, queue) {
this.loader = loader;
this.queue = queue;
}
PreloadLinkHandler.prototype.register = function (el) {
var _this = this;
this.queue.add(el.urlTree);
requestIdleCallback(function () { return _this.loader.preload().subscribe(function () { return void 0; }); });
};
PreloadLinkHandler.prototype.unregister = function (_) { };
PreloadLinkHandler.prototype.supported = function () {
return true;
};
PreloadLinkHandler = __decorate([
Injectable(),
__metadata("design:paramtypes", [RouterPreloader, PrefetchRegistry])
], PreloadLinkHandler);
return PreloadLinkHandler;
}());
var LinkDirective = /** @class */ (function () {
function LinkDirective(linkHandlers, el, link, linkWithHref) {
this.linkHandlers = linkHandlers;
this.el = el;
this.linkHandler = this.linkHandlers.filter(function (h) { return h.supported(); }).shift();
this.rl = link || linkWithHref;
}
LinkDirective.prototype.ngOnChanges = function (c) {
if (c.routerLink) {
this.linkHandler.unregister(this);
this.linkHandler.register(this);
}
};
LinkDirective.prototype.ngOnDestroy = function () {
this.linkHandler.unregister(this);
};
Object.defineProperty(LinkDirective.prototype, "element", {
get: function () {
return this.el.nativeElement;
},
enumerable: true,
configurable: true
});
Object.defineProperty(LinkDirective.prototype, "urlTree", {
get: function () {
return this.rl.urlTree;
},
enumerable: true,
configurable: true
});
__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);
return LinkDirective;
}());
var QuicklinkStrategy = /** @class */ (function () {
function QuicklinkStrategy(queue, router) {
this.queue = queue;
this.router = router;
this.loading = new Set();
}
QuicklinkStrategy.prototype.preload = function (route, load) {
if (this.loading.has(route)) {
// Don't preload the same route twice
return EMPTY;
}
var 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;
}
var 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);
return QuicklinkStrategy;
}());
var findPath = function (config, route) {
config = config.slice();
var parent = new Map();
var visited = new Set();
var _loop_1 = function () {
var el = config.shift();
visited.add(el);
if (el === route)
return "break";
var children = el.children || [];
var current_1 = el._loadedConfig;
if (current_1 && current_1.routes) {
children = children.concat(current_1.routes);
}
children.forEach(function (r) {
if (visited.has(r))
return;
parent.set(r, el);
config.push(r);
});
};
while (config.length) {
var state_1 = _loop_1();
if (state_1 === "break")
break;
}
var path = '';
var 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;
}
var QuicklinkModule = /** @class */ (function () {
function QuicklinkModule() {
}
QuicklinkModule = __decorate([
NgModule({
declarations: [LinkDirective],
providers: [
{
provide: LinkHandler,
useClass: ObservableLinkHandler,
multi: true
},
{
provide: LinkHandler,
useClass: PreloadLinkHandler,
multi: true
},
PrefetchRegistry,
QuicklinkStrategy
],
exports: [LinkDirective]
})
], QuicklinkModule);
return 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