UNPKG

ionic-image-loader-v7

Version:
1,113 lines (1,105 loc) 42.7 kB
import * as i0 from '@angular/core'; import { Injectable, EventEmitter, Component, Input, Output, NgModule } from '@angular/core'; import * as i2 from '@awesome-cordova-plugins/file/ngx'; import { File } from '@awesome-cordova-plugins/file/ngx'; import { Subject, fromEvent } from 'rxjs'; import { first, take, filter } from 'rxjs/operators'; import * as i3 from '@angular/common/http'; import { HttpClientModule } from '@angular/common/http'; import * as i4 from '@ionic/angular'; import { IonicModule } from '@ionic/angular'; import * as i5 from '@awesome-cordova-plugins/ionic-webview/ngx'; import * as i4$1 from '@angular/common'; import { CommonModule } from '@angular/common'; class ImageLoaderConfigService { constructor() { this.debugMode = false; this.spinnerEnabled = true; this.fallbackAsPlaceholder = false; this.backgroundSize = 'contain'; this.backgroundRepeat = 'no-repeat'; this.display = 'block'; this.width = '100%'; this.height = '100%'; this.useImg = false; this.concurrency = 5; this.maxCacheSize = -1; this.maxCacheAge = -1; this.imageReturnType = 'uri'; // Must be default 'true' for the new WebView to show images this.fileNameCachedWithExtension = true; this.fallbackFileNameCachedExtension = '.jpg'; this.cacheDirectoryType = 'cache'; this._cacheDirectoryName = 'image-loader-cache'; } get cacheDirectoryName() { return this._cacheDirectoryName; } set cacheDirectoryName(name) { name.replace(/\W/g, ''); this._cacheDirectoryName = name; } /** * Enables debug mode to receive console logs, errors, warnings */ enableDebugMode() { this.debugMode = true; } /** * Enable/Disable the spinner by default. Defaults to true. * @param enable set to true to enable */ enableSpinner(enable) { this.spinnerEnabled = enable; } /** * Enable/Disable the fallback image as placeholder instead of the spinner. Defaults to false. * @param enable set to true to enable */ enableFallbackAsPlaceholder(enable) { this.fallbackAsPlaceholder = enable; } /** * Sets the cache directory name. Defaults to 'image-loader-cache' * @param name name of directory */ setCacheDirectoryName(name) { this.cacheDirectoryName = name; } /** * Set default height for images that are not using <img> tag * @param height height */ setHeight(height) { this.height = height; } /** * Set default width for images that are not using <img> tag * @param width Width */ setWidth(width) { this.width = width; } /** * Enable display mode for images that are not using <img> tag * @param display Display mode */ setDisplay(display) { this.display = display; } /** * Use <img> tag by default * @param use set to true to use <img> tag by default */ useImageTag(use) { this.useImg = use; } /** * Set default background size for images that are not using <img> tag * @param backgroundSize Background size */ setBackgroundSize(backgroundSize) { this.backgroundSize = backgroundSize; } /** * Set background repeat for images that are not using <img> tag * @param backgroundRepeat Background repeat */ setBackgroundRepeat(backgroundRepeat) { this.backgroundRepeat = backgroundRepeat; } /** * Set fallback URL to use when image src is undefined or did not resolve. * This image will not be cached. This should ideally be a locally saved image. * @param fallbackUrl The remote or local URL of the image */ setFallbackUrl(fallbackUrl) { this.fallbackUrl = fallbackUrl; } /** * Set the maximum number of allowed connections at the same time. * @param concurrency */ setConcurrency(concurrency) { this.concurrency = concurrency; } /** * Sets the maximum allowed cache size * @param cacheSize Cache size in bytes */ setMaximumCacheSize(cacheSize) { this.maxCacheSize = cacheSize; } /** * Sets the maximum allowed cache age * @param cacheAge Maximum cache age in milliseconds */ setMaximumCacheAge(cacheAge) { this.maxCacheAge = cacheAge; } /** * Set the return type of cached images * @param imageReturnType The return type; either 'base64' or 'uri' */ setImageReturnType(imageReturnType) { this.imageReturnType = imageReturnType; } /** * Set the default spinner name * @param name */ setSpinnerName(name) { this.spinnerName = name; } /** * Set the default spinner color * @param color */ setSpinnerColor(color) { this.spinnerColor = color; } /** * Set headers options for the HttpClient transfers. * @param headers */ setHttpHeaders(headers) { this.httpHeaders = headers; } /** * Set options for the FileTransfer plugin * @param options * @deprecated FileTransfer plugin removed. */ setFileTransferOptions(options) { // do nothing, plugin deprecated. } /** * Enable/Disable the save filename of cached images with extension. Defaults to false. * @param enable set to true to enable */ setFileNameCachedWithExtension(enable) { this.fileNameCachedWithExtension = enable; } /** * Set fallback extension filename of cached images. Defaults to '.jpg'. * @param extension fallback extension (e.x .jpg) */ setFallbackFileNameCachedExtension(extension) { this.fallbackFileNameCachedExtension = extension; } } ImageLoaderConfigService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.2.1", ngImport: i0, type: ImageLoaderConfigService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); ImageLoaderConfigService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "13.2.1", ngImport: i0, type: ImageLoaderConfigService, providedIn: 'root' }); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.2.1", ngImport: i0, type: ImageLoaderConfigService, decorators: [{ type: Injectable, args: [{ providedIn: 'root', }] }] }); const EXTENSIONS = ['jpg', 'png', 'jpeg', 'gif', 'svg', 'tiff']; class ImageLoaderService { constructor(config, file, http, platform, webview) { this.config = config; this.file = file; this.http = http; this.platform = platform; this.webview = webview; /** * Indicates if the cache service is ready. * When the cache service isn't ready, images are loaded via browser instead. */ this.isCacheReady = false; /** * Indicates if this service is initialized. * This service is initialized once all the setup is done. */ this.isInit = false; this.initPromise = new Promise(resolve => this.initPromiseResolve = resolve); this.lockSubject = new Subject(); this.lock$ = this.lockSubject.asObservable(); /** * Number of concurrent requests allowed */ this.concurrency = 5; /** * Queue items */ this.queue = []; this.processing = 0; /** * Fast accessible Object for currently processing items */ this.currentlyProcessing = {}; this.cacheIndex = []; this.currentCacheSize = 0; this.indexed = false; this.lockedCallsQueue = []; if (!platform.is('cordova')) { // we are running on a browser, or using livereload // plugin will not function in this case this.isInit = true; this.throwWarning('You are running on a browser or using livereload, IonicImageLoader will not function, falling back to browser loading.'); this.initPromiseResolve(); } else { fromEvent(document, 'deviceready') .pipe(first()) .subscribe(res => { if (this.nativeAvailable) { this.initCache(); } else { // we are running on a browser, or using livereload // plugin will not function in this case this.isInit = true; this.initPromiseResolve(); this.throwWarning('You are running on a browser or using livereload, IonicImageLoader will not function, falling back to browser loading.'); } }); } } get nativeAvailable() { return File.installed(); } get isCacheSpaceExceeded() { return (this.config.maxCacheSize > -1 && this.currentCacheSize > this.config.maxCacheSize); } get isWKWebView() { return (this.platform.is('ios') && window.webkit && window.webkit.messageHandlers); } get isIonicWKWebView() { return ( // Important: isWKWebview && isIonicWKWebview must be mutually excluse. // Otherwise the logic for copying to tmp under IOS will fail. (this.platform.is('android') && this.webview) || (this.platform.is('android')) && (location.host === 'localhost:8080') || window.LiveReload); } get isDevServer() { return window['IonicDevServer'] !== undefined; } /** * Check if we can process more items in the queue */ get canProcess() { return this.queue.length > 0 && this.processing < this.concurrency; } ready() { return this.initPromise; } /** * Preload an image * @param imageUrl Image URL * @returns returns a promise that resolves with the cached image URL */ preload(imageUrl) { return this.getImagePath(imageUrl); } getFileCacheDirectory() { if (this.config.cacheDirectoryType === 'data') { return this.file.dataDirectory; } else if (this.config.cacheDirectoryType === 'external') { return this.platform.is('android') ? this.file.externalDataDirectory : this.file.documentsDirectory; } return this.file.cacheDirectory; } /** * Clears cache of a single image * @param imageUrl Image URL */ async clearImageCache(imageUrl) { if (!this.platform.is('cordova')) { return; } await this.ready(); this.runLocked(async () => { const fileName = this.createFileName(imageUrl); const route = this.getFileCacheDirectory() + this.config.cacheDirectoryName; // pause any operations this.isInit = false; try { await this.file.removeFile(route, fileName); if (this.isWKWebView && !this.isIonicWKWebView) { await this.file.removeFile(this.file.tempDirectory + this.config.cacheDirectoryName, fileName); } } catch (err) { this.throwError(err); } return this.initCache(true); }); } /** * Clears the cache */ async clearCache() { if (!this.platform.is('cordova')) { return; } await this.ready(); this.runLocked(async () => { try { await this.file.removeRecursively(this.getFileCacheDirectory(), this.config.cacheDirectoryName); if (this.isWKWebView && !this.isIonicWKWebView) { // also clear the temp files try { this.file.removeRecursively(this.file.tempDirectory, this.config.cacheDirectoryName); } catch (err) { // Noop catch. Removing the tempDirectory might fail, // as it is not persistent. } } } catch (err) { this.throwError(err); } return this.initCache(true); }); } /** * Gets the filesystem path of an image. * This will return the remote path if anything goes wrong or if the cache service isn't ready yet. * @param imageUrl The remote URL of the image * @returns Returns a promise that will always resolve with an image URL */ async getImagePath(imageUrl) { if (typeof imageUrl !== 'string' || imageUrl.length <= 0) { throw new Error('The image url provided was empty or invalid.'); } await this.ready(); if (!this.isCacheReady) { this.throwWarning('The cache system is not running. Images will be loaded by your browser instead.'); return imageUrl; } if (this.isImageUrlRelative(imageUrl)) { return imageUrl; } try { return await this.getCachedImagePath(imageUrl); } catch (err) { // image doesn't exist in cache, lets fetch it and save it return this.addItemToQueue(imageUrl); } } async processLockedQueue() { if (await this.getLockedState()) { return; } if (this.lockedCallsQueue.length > 0) { await this.setLockedState(true); try { await this.lockedCallsQueue.slice(0, 1)[0](); } catch (err) { console.log('Error running locked function: ', err); } await this.setLockedState(false); return this.processLockedQueue(); } } getLockedState() { return this.lock$ .pipe(take(1)) .toPromise(); } awaitUnlocked() { return this.lock$ .pipe(filter(locked => !!locked), take(1)) .toPromise(); } async setLockedState(locked) { this.lockSubject.next(locked); } runLocked(fn) { this.lockedCallsQueue.push(fn); this.processLockedQueue(); } /** * Returns if an imageUrl is an relative path * @param imageUrl */ isImageUrlRelative(imageUrl) { return !/^(https?|file):\/\/\/?/i.test(imageUrl); } /** * Add an item to the queue * @param imageUrl * @param resolve * @param reject */ addItemToQueue(imageUrl, resolve, reject) { let p; if (!resolve && !reject) { p = new Promise((res, rej) => { resolve = res; reject = rej; }); } else { resolve = resolve || (() => { }); reject = reject || (() => { }); } this.queue.push({ imageUrl, resolve, reject, }); this.processQueue(); return p; } /** * Processes one item from the queue */ async processQueue() { // make sure we can process items first if (!this.canProcess) { return; } // increase the processing number this.processing++; // take the first item from queue const currentItem = this.queue.splice(0, 1)[0]; // function to call when done processing this item // this will reduce the processing number // then will execute this function again to process any remaining items const done = () => { this.processing--; this.processQueue(); // only delete if it's the last/unique occurrence in the queue if (this.currentlyProcessing[currentItem.imageUrl] !== undefined && !this.currentlyInQueue(currentItem.imageUrl)) { delete this.currentlyProcessing[currentItem.imageUrl]; } }; const error = (e) => { currentItem.reject(); this.throwError(e); done(); }; if (this.currentlyProcessing[currentItem.imageUrl] !== undefined) { try { // Prevented same Image from loading at the same time await this.currentlyProcessing[currentItem.imageUrl]; const localUrl = await this.getCachedImagePath(currentItem.imageUrl); currentItem.resolve(localUrl); done(); } catch (err) { error(err); } return; } this.currentlyProcessing[currentItem.imageUrl] = (async () => { // process more items concurrently if we can if (this.canProcess) { this.processQueue(); } const localDir = this.getFileCacheDirectory() + this.config.cacheDirectoryName + '/'; const fileName = this.createFileName(currentItem.imageUrl); try { const data = await this.http.get(currentItem.imageUrl, { responseType: 'blob', headers: this.config.httpHeaders, }).toPromise(); const file = await this.file.writeFile(localDir, fileName, data, { replace: true }); if (this.isCacheSpaceExceeded) { this.maintainCacheSize(); } await this.addFileToIndex(file); const localUrl = await this.getCachedImagePath(currentItem.imageUrl); currentItem.resolve(localUrl); done(); this.maintainCacheSize(); } catch (err) { error(err); throw err; } })(); } /** * Search if the url is currently in the queue * @param imageUrl Image url to search */ currentlyInQueue(imageUrl) { return this.queue.some(item => item.imageUrl === imageUrl); } /** * Initialize the cache service * @param [replace] Whether to replace the cache directory if it already exists */ async initCache(replace) { this.concurrency = this.config.concurrency; // create cache directories if they do not exist try { await this.createCacheDirectory(replace); await this.indexCache(); this.isCacheReady = true; } catch (err) { this.throwError(err); } this.isInit = true; this.initPromiseResolve(); } /** * Adds a file to index. * Also deletes any files if they are older than the set maximum cache age. * @param file FileEntry to index */ async addFileToIndex(file) { const metadata = await new Promise((resolve, reject) => file.getMetadata(resolve, reject)); if (this.config.maxCacheAge > -1 && Date.now() - metadata.modificationTime.getTime() > this.config.maxCacheAge) { // file age exceeds maximum cache age return this.removeFile(file.name); } else { // file age doesn't exceed maximum cache age, or maximum cache age isn't set this.currentCacheSize += metadata.size; // add item to index this.cacheIndex.push({ name: file.name, modificationTime: metadata.modificationTime, size: metadata.size, }); } } /** * Indexes the cache if necessary */ async indexCache() { this.cacheIndex = []; try { const files = await this.file.listDir(this.getFileCacheDirectory(), this.config.cacheDirectoryName); await Promise.all(files.map(this.addFileToIndex.bind(this))); // Sort items by date. Most recent to oldest. this.cacheIndex = this.cacheIndex.sort((a, b) => (a > b ? -1 : a < b ? 1 : 0)); this.indexed = true; } catch (err) { this.throwError(err); } } /** * This method runs every time a new file is added. * It checks the cache size and ensures that it doesn't exceed the maximum cache size set in the config. * If the limit is reached, it will delete old images to create free space. */ async maintainCacheSize() { if (this.config.maxCacheSize > -1 && this.indexed) { const maintain = async () => { if (this.currentCacheSize > this.config.maxCacheSize) { // grab the first item in index since it's the oldest one const file = this.cacheIndex.splice(0, 1)[0]; if (typeof file === 'undefined') { return maintain(); } // delete the file then process next file if necessary try { await this.removeFile(file.name); } catch (err) { // ignore errors, nothing we can do about it } this.currentCacheSize -= file.size; return maintain(); } }; return maintain(); } } /** * Remove a file * @param file The name of the file to remove */ async removeFile(file) { await this.file.removeFile(this.getFileCacheDirectory() + this.config.cacheDirectoryName, file); if (this.isWKWebView && !this.isIonicWKWebView) { try { return this.file.removeFile(this.file.tempDirectory + this.config.cacheDirectoryName, file); } catch (err) { // Noop catch. Removing the files from tempDirectory might fail, as it is not persistent. } } } /** * Get the local path of a previously cached image if exists * @param url The remote URL of the image * @returns Returns a promise that resolves with the local path if exists, or rejects if doesn't exist */ async getCachedImagePath(url) { await this.ready(); if (!this.isCacheReady) { throw new Error('Cache is not ready'); } // if we're running with livereload, ignore cache and call the resource from it's URL if (this.isDevServer) { return url; } // get file name const fileName = this.createFileName(url); // get full path const dirPath = this.getFileCacheDirectory() + this.config.cacheDirectoryName, tempDirPath = this.file.tempDirectory + this.config.cacheDirectoryName; try { // check if exists const fileEntry = await this.file.resolveLocalFilesystemUrl(dirPath + '/' + fileName); // file exists in cache if (this.config.imageReturnType === 'base64') { // read the file as data url and return the base64 string. // should always be successful as the existence of the file // is already ensured const base64 = await this.file.readAsDataURL(dirPath, fileName); return base64.replace('data:null', 'data:*/*'); } else if (this.config.imageReturnType !== 'uri') { return; } // now check if iOS device & using WKWebView Engine. // in this case only the tempDirectory is accessible, // therefore the file needs to be copied into that directory first! if (this.isIonicWKWebView) { return this.normalizeUrl(fileEntry); } if (!this.isWKWebView) { // return native path return fileEntry.nativeURL; } // check if file already exists in temp directory try { const tempFileEntry = await this.file.resolveLocalFilesystemUrl(tempDirPath + '/' + fileName); // file exists in temp directory // return native path return this.normalizeUrl(tempFileEntry); } catch (err) { // file does not yet exist in the temp directory. // copy it! const tempFileEntry = await this.file .copyFile(dirPath, fileName, tempDirPath, fileName); // now the file exists in the temp directory // return native path return this.normalizeUrl(tempFileEntry); } } catch (err) { throw new Error('File does not exist'); } } /** * Normalizes the image uri to a version that can be loaded in the webview * @param fileEntry the FileEntry of the image file * @returns the normalized Url */ normalizeUrl(fileEntry) { // Use Ionic normalizeUrl to generate the right URL for Ionic WKWebView if (Ionic && typeof Ionic.normalizeURL === 'function') { return Ionic.normalizeURL(fileEntry.nativeURL); } // use new webview function to do the trick if (this.webview) { return this.webview.convertFileSrc(fileEntry.nativeURL); } return fileEntry.nativeURL; } /** * Throws a console error if debug mode is enabled * @param args Error message */ throwError(...args) { if (this.config.debugMode) { args.unshift('ImageLoader Error: '); console.error.apply(console, args); } } /** * Throws a console warning if debug mode is enabled * @param args Error message */ throwWarning(...args) { if (this.config.debugMode) { args.unshift('ImageLoader Warning: '); console.warn.apply(console, args); } } /** * Check if the cache directory exists * @param directory The directory to check. Either this.file.tempDirectory or this.getFileCacheDirectory() * @returns Returns a promise that resolves if exists, and rejects if it doesn't */ cacheDirectoryExists(directory) { return this.file.checkDir(directory, this.config.cacheDirectoryName); } /** * Create the cache directories * @param replace override directory if exists * @returns Returns a promise that resolves if the directories were created, and rejects on error */ createCacheDirectory(replace = false) { let cacheDirectoryPromise, tempDirectoryPromise; if (replace) { // create or replace the cache directory cacheDirectoryPromise = this.file.createDir(this.getFileCacheDirectory(), this.config.cacheDirectoryName, replace); } else { // check if the cache directory exists. // if it does not exist create it! cacheDirectoryPromise = this.cacheDirectoryExists(this.getFileCacheDirectory()) .catch(() => this.file.createDir(this.getFileCacheDirectory(), this.config.cacheDirectoryName, false)); } if (this.isWKWebView && !this.isIonicWKWebView) { if (replace) { // create or replace the temp directory tempDirectoryPromise = this.file.createDir(this.file.tempDirectory, this.config.cacheDirectoryName, replace); } else { // check if the temp directory exists. // if it does not exist create it! tempDirectoryPromise = this.cacheDirectoryExists(this.file.tempDirectory).catch(() => this.file.createDir(this.file.tempDirectory, this.config.cacheDirectoryName, false)); } } else { tempDirectoryPromise = Promise.resolve(); } return Promise.all([cacheDirectoryPromise, tempDirectoryPromise]); } /** * Creates a unique file name out of the URL * @param url URL of the file * @returns Unique file name */ createFileName(url) { // hash the url to get a unique file name return (this.hashString(url).toString() + (this.config.fileNameCachedWithExtension ? this.getExtensionFromUrl(url) : '')); } /** * Converts a string to a unique 32-bit int * @param string string to hash * @returns 32-bit int */ hashString(string) { let hash = 0, char; if (string.length === 0) { return hash; } for (let i = 0; i < string.length; i++) { char = string.charCodeAt(i); // tslint:disable-next-line hash = (hash << 5) - hash + char; // tslint:disable-next-line hash = hash & hash; } return hash; } /** * Extract extension from filename or url * * @param url * @returns * * Not always will url's contain a valid image extention. We'll check if any valid extention is supplied. * If not, we will use the default. */ getExtensionFromUrl(url) { const urlWitoutParams = url.split(/\#|\?/)[0]; const ext = (urlWitoutParams.substr((~-urlWitoutParams.lastIndexOf('.') >>> 0) + 1) || '').toLowerCase(); return (EXTENSIONS.indexOf(ext) >= 0 ? ext : this.config.fallbackFileNameCachedExtension); } } ImageLoaderService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.2.1", ngImport: i0, type: ImageLoaderService, deps: [{ token: ImageLoaderConfigService }, { token: i2.File }, { token: i3.HttpClient }, { token: i4.Platform }, { token: i5.WebView }], target: i0.ɵɵFactoryTarget.Injectable }); ImageLoaderService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "13.2.1", ngImport: i0, type: ImageLoaderService, providedIn: 'root' }); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.2.1", ngImport: i0, type: ImageLoaderService, decorators: [{ type: Injectable, args: [{ providedIn: 'root', }] }], ctorParameters: function () { return [{ type: ImageLoaderConfigService }, { type: i2.File }, { type: i3.HttpClient }, { type: i4.Platform }, { type: i5.WebView }]; } }); const propMap = { display: 'display', height: 'height', width: 'width', backgroundSize: 'background-size', backgroundRepeat: 'background-repeat', }; class IonicImageLoaderComponent { constructor(_element, renderer, imageLoader, config) { this._element = _element; this.renderer = renderer; this.imageLoader = imageLoader; this.config = config; /** * Fallback URL to load when the image url fails to load or does not exist. */ this.fallbackUrl = this.config.fallbackUrl; /** * Whether to show a spinner while the image loads */ this.spinner = this.config.spinnerEnabled; /** * Whether to show the fallback image instead of a spinner while the image loads */ this.fallbackAsPlaceholder = this.config.fallbackAsPlaceholder; /** * Attributes to pass through to img tag if _useImg == true */ this.imgAttributes = []; /** * Enable/Disable caching */ this.cache = true; /** * Width of the image. This will be ignored if using useImg. */ this.width = this.config.width; /** * Height of the image. This will be ignored if using useImg. */ this.height = this.config.height; /** * Display type of the image. This will be ignored if using useImg. */ this.display = this.config.display; /** * Background size. This will be ignored if using useImg. */ this.backgroundSize = this.config.backgroundSize; /** * Background repeat. This will be ignored if using useImg. */ this.backgroundRepeat = this.config.backgroundRepeat; /** * Name of the spinner */ this.spinnerName = this.config.spinnerName; /** * Color of the spinner */ this.spinnerColor = this.config.spinnerColor; /** * Notify on image load.. */ this.load = new EventEmitter(); /** * Indicates if the image is still loading */ this.isLoading = true; this._useImg = this.config.useImg; } /** * Use <img> tag */ set useImg(val) { this._useImg = val !== false; } /** * Convenience attribute to disable caching */ set noCache(val) { this.cache = val !== false; } get src() { return this._src; } /** * The URL of the image to load. */ set src(imageUrl) { this._src = this.processImageUrl(imageUrl); this.updateImage(this._src); } ngOnInit() { if (this.fallbackAsPlaceholder && this.fallbackUrl) { this.setImage(this.fallbackUrl, false); } if (!this.src) { // image url was not passed // this can happen when [src] is set to a variable that turned out to be undefined // one example could be a list of users with their profile pictures // in this case, it would be useful to use the fallback image instead // if fallbackUrl was used as placeholder we do not need to set it again if (!this.fallbackAsPlaceholder && this.fallbackUrl) { // we're not going to cache the fallback image since it should be locally saved this.setImage(this.fallbackUrl); } else { this.isLoading = false; } } } updateImage(imageUrl) { this.imageLoader .getImagePath(imageUrl) .then((url) => this.setImage(url)) .catch((error) => this.setImage(this.fallbackUrl || imageUrl)); } /** * Gets the image URL to be loaded and disables caching if necessary */ processImageUrl(imageUrl) { if (this.cache === false) { // need to disable caching if (imageUrl.indexOf('?') < 0) { // add ? if doesn't exists imageUrl += '?'; } else { imageUrl += '&'; } // append timestamp at the end to make URL unique imageUrl += 'cache_buster=' + Date.now(); } return imageUrl; } /** * Set the image to be displayed * @param imageUrl image src * @param stopLoading set to true to mark the image as loaded */ setImage(imageUrl, stopLoading = true) { this.isLoading = !stopLoading; if (this._useImg) { // Using <img> tag if (!this.element) { // create img element if we dont have one this.element = this.renderer.createElement('img'); this.renderer.appendChild(this._element.nativeElement, this.element); } // set it's src this.renderer.setAttribute(this.element, 'src', imageUrl); // if imgAttributes are defined, add them to our img element this.imgAttributes.forEach((attribute) => { this.renderer.setAttribute(this.element, attribute.element, attribute.value); }); if (this.fallbackUrl && !this.imageLoader.nativeAvailable) { this.renderer.listen(this.element, 'error', () => this.renderer.setAttribute(this.element, 'src', this.fallbackUrl)); } } else { // Not using <img> tag this.element = this._element.nativeElement; for (const prop in propMap) { if (this[prop]) { this.renderer.setStyle(this.element, propMap[prop], this[prop]); } } this.renderer.setStyle(this.element, 'background-image', `url("${imageUrl || this.fallbackUrl}")`); } if (stopLoading) { this.load.emit(this); } } } IonicImageLoaderComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.2.1", ngImport: i0, type: IonicImageLoaderComponent, deps: [{ token: i0.ElementRef }, { token: i0.Renderer2 }, { token: ImageLoaderService }, { token: ImageLoaderConfigService }], target: i0.ɵɵFactoryTarget.Component }); IonicImageLoaderComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.2.1", type: IonicImageLoaderComponent, selector: "img-loader", inputs: { fallbackUrl: "fallbackUrl", spinner: "spinner", fallbackAsPlaceholder: "fallbackAsPlaceholder", imgAttributes: "imgAttributes", cache: "cache", width: "width", height: "height", display: "display", backgroundSize: "backgroundSize", backgroundRepeat: "backgroundRepeat", spinnerName: "spinnerName", spinnerColor: "spinnerColor", useImg: "useImg", noCache: "noCache", src: "src" }, outputs: { load: "load" }, ngImport: i0, template: ` <ion-spinner *ngIf="spinner && isLoading && !fallbackAsPlaceholder" [name]="spinnerName" [color]="spinnerColor" ></ion-spinner> <ng-content></ng-content> `, isInline: true, styles: ["ion-spinner{float:none;margin-left:auto;margin-right:auto;display:block}\n"], components: [{ type: i4.IonSpinner, selector: "ion-spinner", inputs: ["color", "duration", "name", "paused"] }], directives: [{ type: i4$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] }); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.2.1", ngImport: i0, type: IonicImageLoaderComponent, decorators: [{ type: Component, args: [{ selector: 'img-loader', template: ` <ion-spinner *ngIf="spinner && isLoading && !fallbackAsPlaceholder" [name]="spinnerName" [color]="spinnerColor" ></ion-spinner> <ng-content></ng-content> `, styles: [ 'ion-spinner { float: none; margin-left: auto; margin-right: auto; display: block; }', ], }] }], ctorParameters: function () { return [{ type: i0.ElementRef }, { type: i0.Renderer2 }, { type: ImageLoaderService }, { type: ImageLoaderConfigService }]; }, propDecorators: { fallbackUrl: [{ type: Input }], spinner: [{ type: Input }], fallbackAsPlaceholder: [{ type: Input }], imgAttributes: [{ type: Input }], cache: [{ type: Input }], width: [{ type: Input }], height: [{ type: Input }], display: [{ type: Input }], backgroundSize: [{ type: Input }], backgroundRepeat: [{ type: Input }], spinnerName: [{ type: Input }], spinnerColor: [{ type: Input }], load: [{ type: Output }], useImg: [{ type: Input }], noCache: [{ type: Input }], src: [{ type: Input }] } }); class IonicImageLoaderModule { } IonicImageLoaderModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.2.1", ngImport: i0, type: IonicImageLoaderModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); IonicImageLoaderModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "12.0.0", version: "13.2.1", ngImport: i0, type: IonicImageLoaderModule, declarations: [IonicImageLoaderComponent], imports: [IonicModule, HttpClientModule, CommonModule], exports: [IonicImageLoaderComponent] }); IonicImageLoaderModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "13.2.1", ngImport: i0, type: IonicImageLoaderModule, providers: [ File, ImageLoaderConfigService, ImageLoaderService ], imports: [[ IonicModule, HttpClientModule, CommonModule ]] }); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.2.1", ngImport: i0, type: IonicImageLoaderModule, decorators: [{ type: NgModule, args: [{ imports: [ IonicModule, HttpClientModule, CommonModule ], declarations: [ IonicImageLoaderComponent ], exports: [ IonicImageLoaderComponent ], providers: [ File, ImageLoaderConfigService, ImageLoaderService ] }] }] }); /* * Public API Surface of ionic-image-loader-v7 */ /** * Generated bundle index. Do not edit. */ export { ImageLoaderConfigService, ImageLoaderService, IonicImageLoaderComponent, IonicImageLoaderModule }; //# sourceMappingURL=ionic-image-loader-v7.mjs.map