diff --git a/CHANGELOG.md b/CHANGELOG.md index df0bac5..959217f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.2.1] - 2020-06-29 + +### Added +- Cancel uploading request. +- XHR request instead of http client request because when unsubscribing the uploading event it was not cancelling the uploading request. + ## [1.2.0] - 2020-06-23 ### Fixed @@ -62,6 +68,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), - Added File select directive - Uploading files in a single request +[1.2.1]: https://github.com/jayprajapati857/ngx-uploader-directive/compare/1.2.0...1.2.1 [1.2.0]: https://github.com/jayprajapati857/ngx-uploader-directive/compare/1.1.7...1.2.0 [1.1.7]: https://github.com/jayprajapati857/ngx-uploader-directive/compare/1.1.5...1.1.7 [1.1.5]: https://github.com/jayprajapati857/ngx-uploader-directive/compare/1.1.4...1.1.5 diff --git a/README.md b/README.md index ce93be4..6d2da40 100644 --- a/README.md +++ b/README.md @@ -7,10 +7,12 @@ Angular 9 File Uploader Directive which provides two directives, which are selec Facilities provided by this directives: - Upload all selected files in a single request. -- Only allow such type to upload settings (Ex. jpg, png, txt, pdf). -- Maximum file upload size settings. - Single file in single request. - Multiple files in single request. +- Multiple files in multiple request (as configured). +- Cancel ongoing requests. +- Only allow such type to upload settings (Ex. jpg, png, txt, pdf). +- Maximum file upload size settings. This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 7.3.10. @@ -96,7 +98,6 @@ export interface ISelectedFile { selectedEventType: 'DROP' | 'SELECT'; // Type of selection of file. progress?: IUploadProgress; // File upload Progress. nativeFile?: File; // Native File. - formData?: FormData; // Form data to upload with file. response?: any; // Response for the selected file. } @@ -361,10 +362,6 @@ npm install npm start ``` -## Future plans - -- Cancel uploading files request. - ## Changelog [Changelog](https://github.com/jayprajapati857/ngx-uploader-directive/blob/master/CHANGELOG.md) diff --git a/package.json b/package.json index c73e7b6..b577c09 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ngx-uploader-directive", - "version": "1.2.0", + "version": "1.2.1", "description": "Angular 9 File Uploader Directive which provides two directives, which are select and file drag and drop to upload files on server.", "repository": { "type": "git", @@ -32,7 +32,7 @@ "scripts": { "ng": "ng", "start": "ng build ngx-uploader-directive-lib && npm install && ng serve --open", - "dev": "ng build ngx-uploader-directive-lib && ng serve", + "dev": "ng build ngx-uploader-directive-lib && ng serve --port 4300", "build:lib": "ng build ngx-uploader-directive-lib", "test": "ng test", "lint": "ng lint", diff --git a/projects/ngx-uploader-directive/CHANGELOG.md b/projects/ngx-uploader-directive/CHANGELOG.md index df0bac5..959217f 100644 --- a/projects/ngx-uploader-directive/CHANGELOG.md +++ b/projects/ngx-uploader-directive/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.2.1] - 2020-06-29 + +### Added +- Cancel uploading request. +- XHR request instead of http client request because when unsubscribing the uploading event it was not cancelling the uploading request. + ## [1.2.0] - 2020-06-23 ### Fixed @@ -62,6 +68,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), - Added File select directive - Uploading files in a single request +[1.2.1]: https://github.com/jayprajapati857/ngx-uploader-directive/compare/1.2.0...1.2.1 [1.2.0]: https://github.com/jayprajapati857/ngx-uploader-directive/compare/1.1.7...1.2.0 [1.1.7]: https://github.com/jayprajapati857/ngx-uploader-directive/compare/1.1.5...1.1.7 [1.1.5]: https://github.com/jayprajapati857/ngx-uploader-directive/compare/1.1.4...1.1.5 diff --git a/projects/ngx-uploader-directive/README.md b/projects/ngx-uploader-directive/README.md index ce93be4..6d2da40 100644 --- a/projects/ngx-uploader-directive/README.md +++ b/projects/ngx-uploader-directive/README.md @@ -7,10 +7,12 @@ Angular 9 File Uploader Directive which provides two directives, which are selec Facilities provided by this directives: - Upload all selected files in a single request. -- Only allow such type to upload settings (Ex. jpg, png, txt, pdf). -- Maximum file upload size settings. - Single file in single request. - Multiple files in single request. +- Multiple files in multiple request (as configured). +- Cancel ongoing requests. +- Only allow such type to upload settings (Ex. jpg, png, txt, pdf). +- Maximum file upload size settings. This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 7.3.10. @@ -96,7 +98,6 @@ export interface ISelectedFile { selectedEventType: 'DROP' | 'SELECT'; // Type of selection of file. progress?: IUploadProgress; // File upload Progress. nativeFile?: File; // Native File. - formData?: FormData; // Form data to upload with file. response?: any; // Response for the selected file. } @@ -361,10 +362,6 @@ npm install npm start ``` -## Future plans - -- Cancel uploading files request. - ## Changelog [Changelog](https://github.com/jayprajapati857/ngx-uploader-directive/blob/master/CHANGELOG.md) diff --git a/projects/ngx-uploader-directive/package.json b/projects/ngx-uploader-directive/package.json index c228f25..00d9635 100644 --- a/projects/ngx-uploader-directive/package.json +++ b/projects/ngx-uploader-directive/package.json @@ -1,6 +1,6 @@ { "name": "ngx-uploader-directive", - "version": "1.2.0", + "version": "1.2.1", "description": "Angular 9 File Uploader Directive which provides two directives, which are select and file drag and drop to upload files on server.", "repository": { "type": "git", diff --git a/projects/ngx-uploader-directive/src/lib/configs/config.ts b/projects/ngx-uploader-directive/src/lib/configs/config.ts index 5d08331..1d0edfd 100644 --- a/projects/ngx-uploader-directive/src/lib/configs/config.ts +++ b/projects/ngx-uploader-directive/src/lib/configs/config.ts @@ -1,3 +1,3 @@ export const environment = { - production: true + production: false }; diff --git a/projects/ngx-uploader-directive/src/lib/directives/ngx-uploader-drop.directive.ts b/projects/ngx-uploader-directive/src/lib/directives/ngx-uploader-drop.directive.ts index 32ae21c..8bd9c27 100644 --- a/projects/ngx-uploader-directive/src/lib/directives/ngx-uploader-drop.directive.ts +++ b/projects/ngx-uploader-directive/src/lib/directives/ngx-uploader-drop.directive.ts @@ -27,7 +27,7 @@ import { Directive, Input, EventEmitter, Output, ElementRef, HostListener } from import { IUploadOptions, IUploadInput, IUploadOutput } from '../models/ngx-uploader-directive-models'; import { Subscription } from 'rxjs'; import { HttpClient } from '@angular/common/http'; -import { NgxUploaderDirectiveService } from '../ngx-uploader-directive.service'; +import { NgxUploaderDirectiveService } from '../services/ngx-uploader-directive.service'; import { environment } from '../configs/config'; @Directive({ diff --git a/projects/ngx-uploader-directive/src/lib/directives/ngx-uploader-select.directive.ts b/projects/ngx-uploader-directive/src/lib/directives/ngx-uploader-select.directive.ts index 128c8e4..7fc2ea3 100644 --- a/projects/ngx-uploader-directive/src/lib/directives/ngx-uploader-select.directive.ts +++ b/projects/ngx-uploader-directive/src/lib/directives/ngx-uploader-select.directive.ts @@ -27,7 +27,7 @@ import { Directive, Input, EventEmitter, Output, ElementRef } from '@angular/cor import { IUploadOptions, IUploadInput, IUploadOutput } from '../models/ngx-uploader-directive-models'; import { Subscription } from 'rxjs'; import { HttpClient } from '@angular/common/http'; -import { NgxUploaderDirectiveService } from '../ngx-uploader-directive.service'; +import { NgxUploaderDirectiveService } from '../services/ngx-uploader-directive.service'; import { environment } from '../configs/config'; @Directive({ @@ -74,10 +74,6 @@ export class NgxUploaderSelectDirective { this.subscriptions.push( this.uploadService.fileServiceEvents.subscribe((event: IUploadOutput) => { if (event.fileSelectedEventType === 'SELECT' || event.fileSelectedEventType === 'ALL') { - if (this.options.logs && this.devEnv) { - console.info('Output select event', event); - } - if (event.type === 'error' || event.type === 'removedAll') { this.element.files = null; this.element.value = ''; @@ -111,9 +107,6 @@ export class NgxUploaderSelectDirective { fileListener = () => { // tslint:disable-next-line: no-console - if (this.options.logs && this.devEnv) { - console.info('File changes', this.element.files); - } if (this.element.files) { // call service method to handle selected files this.uploadService.handleSelectedFiles(this.element.files, 'SELECT'); diff --git a/projects/ngx-uploader-directive/src/lib/models/ngx-uploader-directive-models.ts b/projects/ngx-uploader-directive/src/lib/models/ngx-uploader-directive-models.ts index 56c07a5..96fbb75 100644 --- a/projects/ngx-uploader-directive/src/lib/models/ngx-uploader-directive-models.ts +++ b/projects/ngx-uploader-directive/src/lib/models/ngx-uploader-directive-models.ts @@ -46,7 +46,6 @@ export interface ISelectedFile { selectedEventType: 'DROP' | 'SELECT'; // Type of selection of file. progress?: IUploadProgress; // File upload Progress. nativeFile?: File; // Native File. - formData?: FormData; // Form data to upload with file. response?: any; // Response for the selected file. } diff --git a/projects/ngx-uploader-directive/src/lib/ngx-uploader-directive.service.ts b/projects/ngx-uploader-directive/src/lib/services/ngx-uploader-directive.service.ts similarity index 81% rename from projects/ngx-uploader-directive/src/lib/ngx-uploader-directive.service.ts rename to projects/ngx-uploader-directive/src/lib/services/ngx-uploader-directive.service.ts index d96eb49..c38ae15 100644 --- a/projects/ngx-uploader-directive/src/lib/ngx-uploader-directive.service.ts +++ b/projects/ngx-uploader-directive/src/lib/services/ngx-uploader-directive.service.ts @@ -23,12 +23,12 @@ // tslint:disable: max-line-length // tslint:disable: no-console -import { Injectable, EventEmitter } from '@angular/core'; -import { ISelectedFile, IUploadOutput, IUploadInput, IUploadProgress } from './models/ngx-uploader-directive-models'; +import { EventEmitter } from '@angular/core'; +import { ISelectedFile, IUploadOutput, IUploadInput, IUploadProgress } from '../models/ngx-uploader-directive-models'; import { Observable, Subscription, Subject } from 'rxjs'; import { finalize, mergeMap, switchMap } from 'rxjs/operators'; import { HttpRequest, HttpClient, HttpEventType, HttpHandler, HttpHeaders, HttpErrorResponse } from '@angular/common/http'; -import { environment } from './configs/config'; +import { environment } from '../configs/config'; // @Injectable({ // providedIn: 'root' @@ -86,10 +86,6 @@ export class NgxUploaderDirectiveService { this.queue = new Array(); this.fileServiceEvents.emit({ type: 'init', fileSelectedEventType: selectedEventType }); - if (this.logs && this.devEnv) { - console.info('Handling selected files', selectedFiles); - } - if (selectedFiles.length > this.maxFileUploads) { this.httpErrorResponse = new HttpErrorResponse({ status: 0, error: 'Maxium ' + this.maxFileUploads + ' files can be upload' }); this.fileServiceEvents.emit({ type: 'error', response: this.httpErrorResponse, fileSelectedEventType: selectedEventType }); @@ -126,9 +122,6 @@ export class NgxUploaderDirectiveService { const totalFilesAdded: Array = new Array(); if (this.maxFilesToAddInSingleRequest === 0 || this.maxFilesToAddInSingleRequest === 1) { - if (this.logs && this.devEnv) { - console.info('Single file or Single Request'); - } const eventId = this.generateRandomeId(); // tslint:disable-next-line: prefer-for-of for (let fileIndex = 0; fileIndex < allowedFiles.length; fileIndex++) { @@ -148,9 +141,6 @@ export class NgxUploaderDirectiveService { this.fileServiceEvents.emit({ type: 'addedToQueue', files: filesAddedToQueue, requestId: selectedFile.requestId, fileSelectedEventType: selectedEventType }); } } else { - if (this.logs && this.devEnv) { - console.info('Multiple file multiple request'); - } // generate id for max files to add in single request. const chunkedArray = this.chunkArray(allowedFiles, this.maxFilesToAddInSingleRequest); let fileIndex = 0; @@ -267,7 +257,9 @@ export class NgxUploaderDirectiveService { if (sub.sub) { sub.sub.unsubscribe(); } - + if (this.logs && this.devEnv) { + console.info('subscriptions ', subs); + } const canceldFileArray = this.queue.filter((uploadFile) => uploadFile.requestId === sub.id); if (canceldFileArray.length > 0) { this.queue.forEach((file, fileIndex, queue) => { @@ -334,7 +326,7 @@ export class NgxUploaderDirectiveService { */ startUpload(upload: { files: Array, event: IUploadInput }): Observable { return new Observable(observer => { - const sub = this.uploadFiles(upload.files, upload.event) + const sub = this.uploadFilesXHRRequest(upload.files, upload.event) .pipe(finalize(() => { if (!observer.closed) { observer.complete(); @@ -361,7 +353,7 @@ export class NgxUploaderDirectiveService { * @param files Array of files input * @param event Upload inout event */ - uploadFiles(files: Array, event: IUploadInput): Observable { + uploadFilesHttpRequest(files: Array, event: IUploadInput): Observable { return new Observable(observer => { const time: number = new Date().getTime(); @@ -376,7 +368,7 @@ export class NgxUploaderDirectiveService { } if (fileList.length > 0) { - let formData: FormData = new FormData(); + const formData: FormData = new FormData(); if (event.data !== undefined) { Object.keys(event.data).forEach(key => formData.append(key, event.data[key])); @@ -399,7 +391,12 @@ export class NgxUploaderDirectiveService { observer.next({ type: 'start', requestId: files[0].requestId, files, fileSelectedEventType: files[0].selectedEventType }); - this.httpRequest(event.method, event.url, formData, new HttpHeaders(headers)).subscribe( + const req = new HttpRequest(event.method, event.url, formData, { + headers: new HttpHeaders(headers), + reportProgress: true + }); + + this.httpClient.request(req).subscribe( // tslint:disable-next-line: no-shadowed-variable (data) => { switch (data.type) { @@ -452,6 +449,7 @@ export class NgxUploaderDirectiveService { observer.complete(); } ); + } else { this.httpErrorResponse = new HttpErrorResponse({ status: 0, error: 'Files not available for upload', statusText: 'Invalid Input' }); observer.next({ type: 'error', requestId: files[0].requestId, response: this.httpErrorResponse }); @@ -460,6 +458,124 @@ export class NgxUploaderDirectiveService { }); } + uploadFilesXHRRequest(files: Array, event: IUploadInput): Observable { + return new Observable(observer => { + const url = event.url || ''; + const method = event.method || 'POST'; + const data = event.data || {}; + const headers = event.headers || {}; + + const xhr = new XMLHttpRequest(); + const time: number = new Date().getTime(); + let progressStartTime: number = (files[0].progress.data && files[0].progress.data.startTime) || time; + let speed = 0; + let eta: number | null = null; + + xhr.upload.addEventListener('progress', (e: ProgressEvent) => { + if (e.lengthComputable) { + const percentage = Math.round((e.loaded * 100) / e.total); + const diff = new Date().getTime() - time; + speed = Math.round(e.loaded / diff * 1000); + progressStartTime = (files[0].progress.data && files[0].progress.data.startTime) || new Date().getTime(); + eta = Math.ceil((e.total - e.loaded) / speed); + + const fileProgress: IUploadProgress = { + status: 'Uploading', + data: { + percentage, + speed, + speedHuman: `${this.humanizeBytes(speed)}/s`, + startTime: progressStartTime, + endTime: null, + eta, + etaHuman: this.secondsToHuman(eta) + } + }; + + observer.next({ type: 'uploading', requestId: files[0].requestId, files, progress: fileProgress, fileSelectedEventType: files[0].selectedEventType }); + } + }, false); + + xhr.upload.addEventListener('error', (e: Event) => { + observer.error(e); + observer.complete(); + }); + + xhr.onreadystatechange = () => { + if (xhr.readyState === XMLHttpRequest.DONE) { + let totalSize = 0; + files.forEach((file, index) => { + totalSize += file.nativeFile.size; + }); + const speedAverage = Math.round(totalSize / (new Date().getTime() - progressStartTime) * 1000); + const progress: IUploadProgress = { + status: 'Done', + data: { + percentage: 100, + speed: speedAverage, + speedHuman: `${this.humanizeBytes(speedAverage)}/s`, + startTime: progressStartTime, + endTime: new Date().getTime(), + eta, + etaHuman: this.secondsToHuman(eta || 0) + } + }; + + try { + files.forEach((file, index, filesArray) => { + filesArray[index].response = xhr.status; + }); + } catch (e) { + files.forEach((file, index, filesArray) => { + filesArray[index].response = xhr.status; + }); + } + + // file.responseHeaders = this.parseResponseHeaders(xhr.getAllResponseHeaders()); + + observer.next({ type: 'done', requestId: files[0].requestId, response: data.body, progress, fileSelectedEventType: files[0].selectedEventType, files }); + + observer.complete(); + } + }; + + xhr.open(method, url, true); + // xhr.withCredentials = event.withCredentials ? true : false; + + try { + const uploadIndex = this.queue.findIndex(outFile => outFile.requestId === files[0].requestId); + + if (this.queue[uploadIndex].progress.status === 'Cancelled') { + observer.complete(); + } + + Object.keys(headers).forEach(key => xhr.setRequestHeader(key, headers[key])); + + const bodyToSend: FormData = new FormData(); + + Object.keys(data).forEach(key => bodyToSend.append(key, data[key])); + if (files.length > 1) { + for (let fileIndex = 0; fileIndex < files.length; fileIndex++) { + const element = files[fileIndex]; + bodyToSend.append('file_' + (fileIndex + 1), files[fileIndex].nativeFile, files[fileIndex].name); + } + } else { + bodyToSend.append(event.fieldName || 'file', files[0].nativeFile, files[0].name); + } + + this.fileServiceEvents.emit({ type: 'start', requestId: files[0].requestId, files, fileSelectedEventType: files[0].selectedEventType }); + xhr.send(bodyToSend); + + } catch (e) { + observer.complete(); + } + + return () => { + xhr.abort(); + }; + }); + } + /** * Http Request to upload file(s). * @param requestMethod Request method POST | GET diff --git a/projects/ngx-uploader-directive/src/public-api.ts b/projects/ngx-uploader-directive/src/public-api.ts index f9014a5..331c576 100644 --- a/projects/ngx-uploader-directive/src/public-api.ts +++ b/projects/ngx-uploader-directive/src/public-api.ts @@ -6,5 +6,5 @@ export * from './lib/configs/config'; export * from './lib/models/ngx-uploader-directive-models'; export * from './lib/directives/ngx-uploader-drop.directive'; export * from './lib/directives/ngx-uploader-select.directive'; -export * from './lib/ngx-uploader-directive.service'; +export * from './lib/services/ngx-uploader-directive.service'; export * from './lib/ngx-uploader-directive.module'; diff --git a/src/app/app.component.html b/src/app/app.component.html index ba2aa44..deb4cac 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -53,6 +53,9 @@ + diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 95b4101..4a6ba2c 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -24,7 +24,7 @@ export class AppComponent { * Default Constructor */ constructor() { - this.options = { requestConcurrency: 3, maxFilesToAddInSingleRequest: 2, maxFileUploads: 10, maxFileSize: 10000000, logs: false }; + this.options = { requestConcurrency: 3, maxFilesToAddInSingleRequest: 2, maxFileUploads: 10, maxFileSize: 10000000, logs: true }; this.files = new Array(); this.uploadInput = new EventEmitter(); } @@ -60,7 +60,7 @@ export class AppComponent { break; case 'uploading': this.files = this.updateFiles(this.files, output.files, output.progress, 'UPDATE'); - console.log(this.files); + // console.log(this.files); break; case 'removed': this.files = this.updateFiles(this.files, output.files, output.progress, 'REMOVE'); @@ -68,7 +68,7 @@ export class AppComponent { break; case 'removedAll': this.files = new Array(); - console.log(this.files); + // console.log(this.files); break; case 'dragOver': this.dragOver = true; @@ -80,7 +80,7 @@ export class AppComponent { case 'done': // The files are uploaded this.files = this.updateFiles(this.files, output.files, output.progress, 'UPDATE'); - console.log(this.files); + // console.log(this.files); break; case 'error': console.log(output); @@ -168,4 +168,8 @@ export class AppComponent { removeAllFiles(): void { this.uploadInput.emit({ type: 'removeAll', inputReferenceNumber: Math.random() }); } + + cancellAllFiles(): void { + this.uploadInput.emit({ type: 'cancelAll', inputReferenceNumber: Math.random() }); + } } diff --git a/src/index.html b/src/index.html index 79a1579..de3ad51 100644 --- a/src/index.html +++ b/src/index.html @@ -1,14 +1,17 @@ + - NgFileUploader + Ngx File Uploader Directive + - + + \ No newline at end of file