Skip to content

Commit

Permalink
feat(): almost stable provider with full functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
ihadeed committed Oct 12, 2016
1 parent 24ec0ac commit 810bc32
Show file tree
Hide file tree
Showing 3 changed files with 223 additions and 83 deletions.
104 changes: 58 additions & 46 deletions src/directives/img-loader.ts
Original file line number Diff line number Diff line change
@@ -1,64 +1,76 @@
import { Directive, Input, Output, EventEmitter, ElementRef, Renderer } from '@angular/core';
import {ImageLoaderConfig} from "../providers/image-loader-config";
import { ImageLoaderConfig } from "../providers/image-loader-config";
import {ImageLoader} from "../providers/image-loader";

@Directive({
selector: '[imgLoader]'
})
export class ImgLoader {

/**
* The URL of the image to load.
*/
@Input('imgLoader') imageUrl: string;
/**
* The URL of the image to load.
*/
@Input('imgLoader') imageUrl: string;

/**
* The URL of the image to show if an error occurs. Leave this blank to not show anything.
*/
@Input() errorImage: string;
/**
* The URL of the image to show if an error occurs. Leave this blank to not show anything.
*/
@Input() errorImage: string;

/**
* The name of the Ionic Spinner to show while loading. Leave this blank to not show anything.
*/
@Input() spinner: string;
/**
* The name of the Ionic Spinner to show while loading. Leave this blank to not show anything.
*/
@Input() spinner: string;

/**
* Event emitter that notifies you when the image is loaded
* @type {EventEmitter<void>}
*/
@Output() onLoad: EventEmitter<void> = new EventEmitter<void>();
/**
* Event emitter that notifies you when the image is loaded
* @type {EventEmitter<void>}
*/
@Output() onLoad: EventEmitter<void> = new EventEmitter<void>();

/**
* Event emiter that notifies you when an error occurs, and passes you the error response/message.
* @type {EventEmitter<any>}
*/
@Output() onError: EventEmitter<any> = new EventEmitter<any>();
/**
* Event emiter that notifies you when an error occurs, and passes you the error response/message.
* @type {EventEmitter<any>}
*/
@Output() onError: EventEmitter<any> = new EventEmitter<any>();

/**
* The tag name of the element this directive is attached to.
* This is used to determine whether we're on an `img` tag or something else.
*/
private tagName: string;
/**
* The tag name of the element this directive is attached to.
* This is used to determine whether we're on an `img` tag or something else.
*/
private tagName: string;

/**
* Whether the image is still loading
*/
private isLoading: boolean;
/**
* Whether the image is still loading
*/
private isLoading: boolean;

/**
* Whether an error occurred while loading the image
*/
error: boolean;
/**
* Whether an error occurred while loading the image
*/
error: boolean;

constructor(
private element: ElementRef
, private renderer: Renderer
, private config: ImageLoaderConfig
) {
}
constructor(
private element: ElementRef
, private renderer: Renderer
, private config: ImageLoaderConfig
, private imageLoader: ImageLoader
) {
}

ngOnInit(): void {
// set tag name
this.tagName = this.element.nativeElement.tagName;
}
ngOnInit(): void {
// set tag name
this.tagName = this.element.nativeElement.tagName;

// fetch image
this.imageLoader.getImagePath(this.imageUrl)
.then((imageUrl: string) => {
if (this.tagName === 'IMG') {
this.renderer.setElementAttribute(this.element, 'src', imageUrl);
} else {
this.renderer.setElementStyle(this.element, 'background-image', 'url(\'' + imageUrl +'\')');
}
});
}

}
19 changes: 14 additions & 5 deletions src/providers/image-loader-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,25 @@ import { Injectable } from '@angular/core';
@Injectable()
export class ImageLoaderConfig {

isDebug: boolean = false;
debugMode: boolean = false;

cacheDirectoryPath: string = 'image-loader-cache';
private _cacheDirectoryName: string = 'image-loader-cache';

set cacheDirectoryName(name: string) {
name.replace(/\W/g, '');
this._cacheDirectoryName = name;
}

get cacheDirectoryName(): string {
return this._cacheDirectoryName;
}

enableDebugMode(): void {
this.isDebug = true;
this.debugMode = true;
}

setCacheDirectory(path: string): void {
this.cacheDirectoryPath = path;
setCacheDirectoryName(name: string): void {
this.cacheDirectoryName = name;
}

}
183 changes: 151 additions & 32 deletions src/providers/image-loader.ts
Original file line number Diff line number Diff line change
@@ -1,84 +1,203 @@
import { Injectable } from '@angular/core';
import { Http } from '@angular/http';
import { HTTP, File } from 'ionic-native';
import { HTTP, File, DirectoryEntry, FileError, FileEntry } from 'ionic-native';
import { Platform } from 'ionic-angular';
import { ImageLoaderConfig } from "./image-loader-config";

declare var cordovaHTTP: any;
declare var cordova: any;

@Injectable()
export class ImageLoader {

private isNativeAvailable: boolean = false;
private isNativeHttpAvailable: boolean = false;
private isCacheReady: boolean = false;

constructor(
private http: Http,
private platform: Platform,
private config: ImageLoaderConfig
) {}
constructor(private http: Http,
private platform: Platform,
private config: ImageLoaderConfig) {
}

ngOnInit(): void {
this.platform.ready().then(() => {
if (typeof cordovaHTTP !== 'undefined') {
this.isNativeAvailable = true;
} else if (this.config.isDebug) {
this.isNativeHttpAvailable = true;
} else if (this.config.debugMode) {
console.info('ImageLoader: Falling back to @angular/http since cordovaHTTP isn\'t available');
}
this.initCache();
});
}

getRawImage(url: string): Promise<any> {
return new Promise<any>((resolve, reject) => {
getImagePath(imageUrl: string): Promise<string> {
if (this.isCacheReady) {
return new Promise<string>((resolve) => {
this.getCachedImagePath(imageUrl)
.then(imagePath => resolve(imagePath))
.catch(() => {
// image doesn't exist in cache, lets fetch it and save it
this.getRawImage(imageUrl)
.then(image => {
this.cacheImage(image, this.createFileName(imageUrl))
.then(() => {
this.getCachedImagePath(imageUrl)
.then(imagePath => resolve(imagePath))
.catch((e) => {
this.throwError(e);
resolve(imageUrl);
});
})
.catch((e) => {
this.throwError(e);
resolve(imageUrl);
});
})
.catch((e) => {
this.throwError(e);
resolve(imageUrl);
});
});
});
} else {
this.throwWarning('The cache system is not running. Images will be loaded by your browser instead.');
return Promise.resolve(imageUrl);
}
}

if (this.isNativeAvailable) {
// cordovaHTTP is available, lets get the image via background thread
private initCache(replace?: boolean): void {
if (!this.filePluginExists) {
return;
}

this.cacheDirectoryExists
.then((exists: boolean) => {
if (exists) {
this.isCacheReady = true;
} else {
this.createCacheDirectory(replace)
.then((dirEntry: DirectoryEntry) => this.isCacheReady = true)
.catch(this.throwError);
}
})
.catch(this.throwError);
}

private getRawImage(url: string): Promise<any> {
return new Promise<any>((resolve, reject) => {
if (this.isNativeHttpAvailable) {
// cordovaHTTP is available, lets get the image via background thread
HTTP.get(url, {}, {})
.then(
data => {
console.log(data);

},
error => {

}
reject
);

} else {
// cordovaHTTP isn't available so we'll use @angular/http

this.http.get(url)
.subscribe(
data => {
console.log(data);
resolve(data.arrayBuffer());
},
error => {

}
reject
);

}

});
}

checkIfImageExistsInCache(imageHash: string): Promise<void> {
return new Promise<void>((resolve, reject) => {
/**
*
* @param image {ArrayBuffer} Image in binary
* @param fileName {string} File name to save as
* @returns {Promise<string>} Promise that resolves with native URL of file
*/
private cacheImage(image: ArrayBuffer, fileName: string): Promise<void> {
return File.writeFile(cordova.file.cacheDirectory + '/' + this.config.cacheDirectoryName, fileName, new Blob([image]), {replace: true});
}

private getCachedImagePath(url: string): Promise<string> {
return new Promise<string>((resolve, reject) => {
if (!this.isCacheReady) {
return reject();
}
let fileName = this.createFileName(url);
let dirPath = cordova.file.cacheDirectory + '/' + this.config.cacheDirectoryName;
File.checkFile(dirPath, fileName)
.then((exists: boolean) => {
if (exists) {

File.resolveLocalFilesystemUrl(dirPath + '/' + fileName)
.then((fileEntry: FileEntry) => {
resolve(fileEntry.nativeURL);
})
.catch(reject);

} else {
reject();
}
})
.catch(this.throwError);
});
}

hashURL(url: string): string {
private throwError(error: any): void {
if (this.config.debugMode) {
console.error('ImageLoader Error', error);
}
}

private throwWarning(error: any): void {
if (this.config.debugMode) {
console.warn('ImageLoader Warning', error);
}
}

private get filePluginExists(): boolean {
if (!cordova || !cordova.file) {
this.throwWarning('Unable to find the cordova file plugin. ImageLoader will not cache images.');
return false;
}
return true;
}

private get cacheDirectoryExists(): Promise<boolean> {
return File.checkDir(cordova.file.cacheDirectory, this.config.cacheDirectoryName);
}

private createCacheDirectory(replace: boolean = false): Promise<any> {
return File.createDir(cordova.file.cacheDirectory, this.config.cacheDirectoryName, replace);
}

/**
* Creates a unique file name out of the URL
* @param url {string} URL of the file
* @returns {string} Unique file name
*/
private createFileName(url: string): string {
// get file extension and clean up anything after the extension
let ext: string = url.split('.').pop().split(/\#|\?/)[0];
// hash the url to get a unique file name
let hash = this.hashString(url);
return hash + '.' + ext;
}

/**
* Converts a string to a unique 32-bit int
* @param string {string} string to hash
* @returns {number} 32-bit int
*/
private hashString(string: string): number {
let hash = 0;
let char;
if (url.length === 0) return hash.toString();
for (let i = 0; i < url.length; i++) {
char = url.charCodeAt(i);
hash = ((hash<<5)-hash)+char;
if (string.length === 0) return hash;
for (let i = 0; i < string.length; i++) {
char = string.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash = hash & hash;
}
return hash.toString();
return hash;
}

}

0 comments on commit 810bc32

Please sign in to comment.