@bespunky/angular-zen
Version:
The Angular tools you always wished were there.
229 lines (223 loc) • 11 kB
JavaScript
import * as i0 from '@angular/core';
import { NgModule, Injectable } from '@angular/core';
import * as i1 from '@bespunky/angular-zen/core';
import { CoreModule } from '@bespunky/angular-zen/core';
import * as i2 from '@bespunky/angular-zen/universal';
import { UniversalModule } from '@bespunky/angular-zen/universal';
import { of, Observable } from 'rxjs';
class AsyncModule {
}
AsyncModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: AsyncModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
AsyncModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "14.2.12", ngImport: i0, type: AsyncModule, imports: [CoreModule, UniversalModule], exports: [CoreModule] });
AsyncModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: AsyncModule, imports: [CoreModule, UniversalModule, CoreModule] });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: AsyncModule, decorators: [{
type: NgModule,
args: [{
declarations: [],
imports: [CoreModule, UniversalModule],
exports: [CoreModule]
}]
}] });
/**
* Provides methods for lazy loading scripts and styles programatically.
* The service keeps track of loaded files to skip reloading unless specified otherwise in the options of `loadScript()` or `loadStyle()`.
*/
class LazyLoaderService {
constructor(head, universal) {
this.head = head;
this.universal = universal;
/**
* Defines the default options when calling the `LazyLoaderService.loadScript()` method.
*/
this.defaultScriptOptions = {
async: true,
defer: true,
alreadyLoaded: (url) => this.isCached(url) || this.isScriptPresent(url),
force: false
};
/**
* Defines the default options when calling the `LazyLoaderService.loadStyle()` method.
*/
this.defaultStyleOptions = {
alreadyLoaded: (url) => this.isCached(url) || this.isStylePresent(url),
force: false
};
this.cache = {};
}
/**
* Checks whether the file from the specified url has already been cached.
* @param url The url for the file to check.
* @returns A value indicating whether the file from the specified url has already been cached.
*/
isCached(url) {
return !!this.cache[url];
}
/**
* Checks whether a script element is already present in `<head>` for the given url.
* This doesn't guarantee that the script has been loaded.
*
* @param {string} url The url of the loaded script.
* @returns {boolean} `true` if an element matching the url is present in `<head>`; otherwise `false.
*/
isScriptPresent(url) {
return this.head.contains('script', { src: url });
}
/**
* Checks whether a link element is already present in `<head>` for the given style url.
* This doesn't guarantee that the style has been loaded.
*
* @param {string} url The url of the loaded link.
* @returns {boolean} `true` if an element matching the url is present in `<head>`; otherwise `false.
*/
isStylePresent(url) {
return this.head.contains('link', { rel: 'stylesheet', href: url });
}
/**
* Loads a script programatically.
*
* @param url The full url of the script to load.
* @param options (Optional) Specifies custom options to override default behaviour.
* @returns An observable object which allows subscribers to know when the script has been loaded and access its associated `<script>` element.
* The observable will complete immediately in case the script was already previously loaded.
* If the script was already loaded outside of the service, the observable will stream `undefined`.
*/
loadScript(url, options = this.defaultScriptOptions) {
// Set default options if not specified by caller
options.async = options.async === undefined ? this.defaultScriptOptions.async : options.async;
options.defer = options.defer === undefined ? this.defaultScriptOptions.defer : options.defer;
options.alreadyLoaded = options.alreadyLoaded === undefined ? this.defaultScriptOptions.alreadyLoaded : options.alreadyLoaded;
options.force = options.force === undefined ? this.defaultScriptOptions.force : options.force;
return this.loadFile(url, 'script', options, this.createScriptElement.bind(this));
}
/**
* Loads a style programatically.
*
* @param url The full url of the style to load.
* @param options (Optional) Specifies custom options to override default behaviour.
* @returns An observable object which allows subscribers to know when the style has been loaded and access its associated `<link>` element.
* The observable will complete immediately in case the style was already previously loaded.
* If the style was already loaded outside of the service, the observable will stream `undefined`.
*/
loadStyle(url, options = this.defaultStyleOptions) {
// Set default options if not specified by caller
options.alreadyLoaded = options.alreadyLoaded === undefined ? this.defaultStyleOptions.alreadyLoaded : options.alreadyLoaded;
options.force = options.force === undefined ? this.defaultStyleOptions.force : options.force;
return this.loadFile(url, 'style', options, this.createLinkElement.bind(this));
}
loadFile(url, type, options, createElement) {
if (!this.universal.isPlatformBrowser)
return of(null);
// If the script should be loaded, load it
if (!options.alreadyLoaded(url) || options.force) {
// Initialize a base object to store the data later
const lazyFile = { url, type, completed: false, element: null };
// Create an observable that waits until the script has been loaded and executed
const observable = new Observable(observer => {
// Create the callback that will mark the script as completed and notify the subscriber
const onLoad = () => {
lazyFile.completed = true;
observer.next(lazyFile);
observer.complete();
};
// Create the actual file tag, start downloading and store the element reference
lazyFile.element = createElement(url, onLoad, observer.error.bind(observer), options);
});
// Cache the file and the observable for later use
this.cache[url] = { lazyFile: lazyFile, observable };
return observable;
}
// If the file was already loaded and it shouldn't be downloaded again, complete immediately with the previous link data.
// If the file was already loaded outside of the service, the observable will stream `undefined` as there is nothing cached.
return of(this.cache[url]?.lazyFile);
}
/**
* Creates a `<script>` tag for the given url and adds it to the `<head>` tag to start downloading the script.
*
* @param url The url for the script to download.
* @param onLoad The callback to execute when the script has been downloaded and executed.
* @param onError The callback to execute when script download or execution has failed.
* @param options The options to add to the script.
* @returns A reference to the `<script>` element that was added to the DOM.
*/
createScriptElement(url, onLoad, onError, options) {
return this.head.addScriptElement('text/javascript', url, {
async: options.async,
defer: options.defer,
onload: onLoad,
onerror: onError
});
}
/**
* Creates a `<link>` tag for the given url and adds it to the `<head>` tag to start downloading the link.
*
* @param url The url for the link to download.
* @param onLoad The callback to execute when the script has been downloaded and executed.
* @param onError The callback to execute when script download or execution has failed.
* @returns A reference to the `<link>` element that was added to the DOM.
*/
createLinkElement(url, onLoad, onError) {
return this.head.addLinkElement('stylesheet', {
type: 'text/css',
href: url,
onload: onLoad,
onerror: onError
});
}
}
LazyLoaderService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: LazyLoaderService, deps: [{ token: i1.HeadService }, { token: i2.UniversalService }], target: i0.ɵɵFactoryTarget.Injectable });
LazyLoaderService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: LazyLoaderService, providedIn: 'root' });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: LazyLoaderService, decorators: [{
type: Injectable,
args: [{
providedIn: 'root'
}]
}], ctorParameters: function () { return [{ type: i1.HeadService }, { type: i2.UniversalService }]; } });
/**
* Creates a promise without which doesn't have the actual async code to resolve, and extracts its `resolve()` and `reject()` methods for later use.
* Use this when you have to create a promise for an async code that runs in a place different to the one where you create the promise, and it will
* allow you full control of the promise from other parts of your code.
*
* @example
* import { promiseLater } from '@bespunky/angular-zen/core';
* // Other imports...
*
* export class SomeDirective implements OnInit, OnChanges
* {
* private const waitForInit: Promise<SomeType>;
*
* constructor()
* {
* this.waitForInit = promiseLater();
* }
*
* ngOnInit()
* {
* this.waitForInit.resolve();
* }
*
* ngOnChanges(changes: SimpleChanges)
* {
* this.waitForInit.promise.then(() => {
* // ... Some code
* });
* }
* }
*
* @export
* @template T The type of the value promised after resolving the async operation.
* @returns An object containing the promise, anlong with its `resolve()` and `reject()` methods.
*/
function promiseLater() {
const latePromise = {};
const promise = new Promise((resolve, reject) => {
latePromise.resolve = resolve;
latePromise.reject = reject;
});
latePromise.promise = promise;
return latePromise;
}
/**
* Generated bundle index. Do not edit.
*/
export { AsyncModule, LazyLoaderService, promiseLater };
//# sourceMappingURL=bespunky-angular-zen-async.mjs.map