UNPKG

@myog-io/ngx-chunk-file-upload-base

Version:

Angular file upload directives with Chunk Upload

375 lines (351 loc) 10.1 kB
import { HttpClient, HttpErrorResponse, HttpEventType, HttpHeaders, HttpRequest, } from '@angular/common/http'; import { Observable } from 'rxjs'; import { of } from 'rxjs/observable/of'; import { FileItem } from './file-item.class'; import { FileUploader, FileUploaderOptions } from './file-uploader.class'; import { Injectable } from '@angular/core'; export interface UploaderLinksOptions { downloadEntry: string; updateEntry: string; createEntry: string; deleteEntry: string; } export interface UploaderServiceOptions { createMethod: string; updateMethod: string; authorizationHeaderName?: string; tokenPattern?: string; token?: string; chunkSize?: number; totalChunkParamName?: string; currentChunkParamName?: string; contentTypeParamName?: string; fileParamName?: string; idAttribute?: string; } @Injectable() export class FileUploaderService { public defaultLinks: UploaderLinksOptions = { downloadEntry: '', updateEntry: '', createEntry: '', deleteEntry: '', }; public defaultOptions: UploaderServiceOptions = { createMethod: 'POST', updateMethod: 'POST', authorizationHeaderName: 'Authorization', tokenPattern: null, token: null, chunkSize: 0, totalChunkParamName: 'total_chunks', currentChunkParamName: 'current_chunk', contentTypeParamName: 'content_type', fileParamName: 'file', idAttribute: 'id', }; public additionalHeaders: any = {}; protected cancelError = 'UPLOAD CANCELED'; protected uploadSubscription: any = null; public links: UploaderLinksOptions; public options: UploaderServiceOptions; private _uploader: FileUploader = null; constructor(protected http: HttpClient) { this.links = Object.assign({}, this.defaultLinks, this.links); this.options = Object.assign({}, this.defaultOptions, this.options); } get uploader(): FileUploader { return this._uploader; } set uploader(theUploader: FileUploader) { this._uploader = theUploader; } public onBeforeUpload( item: FileItem, options: FileUploaderOptions, ): Promise<any> { const promise = new Promise((resolve, reject) => { resolve(true); }); return promise; } public uploadFile(item: FileItem, options: FileUploaderOptions): void { this.onBeforeUpload(item, options).then(() => { this._uploadFile(item, options); }); } public onBeforeGetDefaultHeaders(): Promise<any> { const promise = new Promise((resolve, reject) => { resolve(true); }); return promise; } protected _getDefaultHeaders(): Promise<any> { return new Promise((resolve, reject) => { this.onBeforeGetDefaultHeaders().then( (result) => { const h: any = {}; if (this.options.tokenPattern && this.options.token) { h[this.options.authorizationHeaderName] = this.options.tokenPattern.replace( '#token#', this.options.token, ); } for (const key in this.additionalHeaders) { if (this.additionalHeaders.hasOwnProperty(key)) { h[key] = this.additionalHeaders[key]; } } resolve(h); }, error => { reject(error); }, ); }); } protected _getRequestHeaders(item: FileItem, options: FileUploaderOptions): Promise<any> { return new Promise((resolve, reject) => { this._getDefaultHeaders().then( h => { if (options.headers) { for (const header of options.headers) { h[header.name] = header.value; } } if (item.headers.length) { for (const header of item.headers) { h[header.name] = header.value; } } resolve(h); }, error => { reject(error); }, ); }); } public buildPackageToSend(item: FileItem, options: FileUploaderOptions) { const sendable: FormData = new FormData(); this.uploader._onBuildItemForm(item, sendable); let file: any = null; if (this.options.chunkSize > 0) { file = item.getCurrentChunkFile(); } else { file = item._file; } const appendFile = () => sendable.append(this.options.fileParamName, file, item.file.name); if (!options.parametersBeforeFiles) { appendFile(); } // For AWS, Additional Parameters must come BEFORE Files if (options.additionalParameter !== undefined) { Object.keys(options.additionalParameter).forEach((key: string) => { let paramVal = options.additionalParameter[key]; // Allow an additional parameter to include the filename if ( typeof paramVal === 'string' && paramVal.indexOf('{{file_name}}') >= 0 ) { paramVal = paramVal.replace('{{file_name}}', item.file.name); } sendable.append(key, paramVal); }); } if (this.options.chunkSize > 0 && this.options.totalChunkParamName) { sendable.append( this.options.totalChunkParamName, item.getTotalChunks().toString(), ); } if (this.options.chunkSize > 0 && this.options.currentChunkParamName) { sendable.append( this.options.currentChunkParamName, (item.getCurrentChunk() + 1).toString(), ); } sendable.append(this.options.contentTypeParamName, item.getContentType()); if (options.parametersBeforeFiles) { appendFile(); } return sendable; } protected _uploadFile(item: FileItem, options: FileUploaderOptions): void { this._getRequestHeaders(item, options).then( headers => { let request_method = this.options.createMethod; let link = this.links.createEntry; item.setIsUploading(true); if (this.options.chunkSize > 0) { try { item.getCurrentChunk(); } catch (err) { item.createFileChunk(this.options.chunkSize); } request_method = item.getCurrentChunk() > 0 ? this.options.updateMethod : this.options.createMethod; link = item.getCurrentChunk() > 0 ? this.links.updateEntry : this.links.createEntry; } if (item.getId()) { link = link.replace('#id#', item.getId()); } const data = this.buildPackageToSend(item, options); const request = new HttpRequest(request_method, link, data, { headers: new HttpHeaders(headers), reportProgress: true, withCredentials: item.withCredentials, }); this.uploadSubscription = this.http.request(request).subscribe( event => { this.getEventMessage(event, item); }, error => { if (this.cancelError === error) { this.uploader.onAbort(error, item); } else { this.uploader.onError(error, item); } }, ); }, error => { }, ); } public stopUpload() { if (this.uploadSubscription && this.uploadSubscription.unsubscribe) { this.uploadSubscription.error(this.cancelError); } } private getEventMessage(event: any, item: FileItem) { switch (event.type) { case HttpEventType.ResponseHeader: break; case HttpEventType.Sent: this.uploader.onStart(event, item); break; case HttpEventType.UploadProgress: // Let's Check if user can delete the file; if (event.loaded === event.total) { // Checking if we have the deleteEntry Link and if the ID is null, we know we can delete the file; if (!item.getId() && this.links['deleteEntry']) { item.canRemove = false; } } this.uploader.onProgress(event, item); break; case HttpEventType.Response: item.canRemove = true; if (this.options.chunkSize > 0) { if (item.getCurrentChunk() === 0) { const response = event.body; if (response[this.options.idAttribute]) { item.setId(response[this.options.idAttribute]); } } } this.uploader.onLoad(event, item); break; default: break; } } private handleError(item: FileItem) { const userMessage = `${item.file.name} upload failed.`; return (error: HttpErrorResponse) => { this.uploader.onError(error, item); const message = error.error instanceof Error ? error.error.message : `server returned code ${error.status} with body "${error.error}"`; return of(userMessage); }; } public deleteEntry( item: FileItem, options = {}, skipConfirmation = false, ): Observable<any> { if (item.getId() && this.links['deleteEntry']) { const link = this.links['deleteEntry'].replace(/#id#/g, item.getId()); let confirmation = false; if (skipConfirmation) { confirmation = true; } else { confirmation = confirm('Are you sure you want to delete this entry?'); } if (confirmation) { return this.delete(link, options); } else { return of(false); } } else { return of(false); } } protected delete(url: string, options = {}): Observable<any> { return new Observable(observe => { this._getDefaultHeaders().then( (headers: any) => { return this.http .delete(url, { headers: new HttpHeaders(headers) }) .subscribe( (response: Response) => { observe.next(response); }, (error: any) => { observe.error(error); }, ); }, (error: any) => { observe.error(error); }, ); }); } /* HTTP General methos only bellow */ protected get(url: string): Observable<any> { return new Observable(observe => { this._getDefaultHeaders().then( (headers: any) => { return this.http .get(url, { headers: new HttpHeaders(headers) }) .subscribe( (response: Response) => { observe.next(response); }, error => { observe.error(error); }, ); }, (error: any) => { observe.error(error); }, ); }); } public addHeader(name: string, value: string) { this.additionalHeaders[name] = value; } public removeHeader(name: string) { if (this.additionalHeaders.hasOwnProperty(name)) { delete this.additionalHeaders[name]; } } }