-
-
Save stuartaccent/51afc6b17d89d4dc6f3968ede5d789b6 to your computer and use it in GitHub Desktop.
| <div class="row"> | |
| <div class="col-md-3"> | |
| <h3>Select files</h3> | |
| <input type="file" #fileInput multiple (change)="addToQueue()" /> | |
| </div> | |
| <div class="col-md-9"> | |
| <h3>Upload queue</h3> | |
| <table class="table-headed table-striped"> | |
| <thead> | |
| <tr> | |
| <th class="text-left">Name</th> | |
| <th class="text-right">Size</th> | |
| <th class="text-left">Progress</th> | |
| <th class="text-left">Status</th> | |
| <th class="text-right">Actions</th> | |
| </tr> | |
| </thead> | |
| <tbody> | |
| <tr *ngFor="let item of queue | async"> | |
| <td>{{ item?.file?.name }}</td> | |
| <td class="text-right">{{ item?.file?.size/1024/1024 | number:'.2' }} MB</td> | |
| <td>{{ item.progress + ' %' }}</td> | |
| <td> | |
| <span *ngIf="item.isPending()" class="tag tag-default"></span> | |
| <span *ngIf="item.isSuccess()" class="tag tag-success"></span> | |
| <span *ngIf="item.inProgress()" class="tag tag-warning"></span> | |
| <span *ngIf="item.isError()" class="tag tag-danger"></span> | |
| </td> | |
| <td class="text-right"> | |
| <a tooltip="Upload file" (click)="item.upload()" *ngIf="item.isUploadable()"> | |
| <i class="fa fa-upload"></i> | |
| </a> | |
| <a tooltip="Cancel upload" (click)="item.cancel()" *ngIf="item.inProgress()"> | |
| <i class="fa fa-times-circle"></i> | |
| </a> | |
| <a tooltip="Remove from queue" (click)="item.remove()" *ngIf="!item.inProgress()"> | |
| <i class="fa fa-trash"></i> | |
| </a> | |
| </td> | |
| </tr> | |
| </tbody> | |
| </table> | |
| <div> | |
| <a class="button" (click)="uploader.clearQueue()">Clear queue</a> | |
| <a class="button button-primary" (click)="uploader.uploadAll()">Upload all</a> | |
| </div> | |
| </div> | |
| </div> |
| import { Component, EventEmitter, OnInit, Output, ViewChild } from '@angular/core'; | |
| import { FileQueueObject, FileUploaderService } from './file-uploader.service'; | |
| import { Observable } from 'rxjs'; | |
| @Component({ | |
| selector: 'file-uploader, [file-uploader]', | |
| templateUrl: 'file-uploader.component.html', | |
| styleUrls: ['./file-uploader.component.css'] | |
| }) | |
| export class FileUploaderComponent implements OnInit { | |
| @Output() onCompleteItem = new EventEmitter(); | |
| @ViewChild('fileInput') fileInput; | |
| queue: Observable<FileQueueObject[]>; | |
| constructor(public uploader: FileUploaderService) { } | |
| ngOnInit() { | |
| this.queue = this.uploader.queue; | |
| this.uploader.onCompleteItem = this.completeItem; | |
| } | |
| completeItem = (item: FileQueueObject, response: any) => { | |
| this.onCompleteItem.emit({ item, response }); | |
| } | |
| addToQueue() { | |
| const fileBrowser = this.fileInput.nativeElement; | |
| this.uploader.addToQueue(fileBrowser.files); | |
| } | |
| } |
| import * as _ from 'lodash'; | |
| import { HttpClient, HttpErrorResponse, HttpHeaders, HttpRequest, HttpResponse } from '@angular/common/http'; | |
| import { Injectable, Output } from '@angular/core'; | |
| import { BehaviorSubject, Subscription } from 'rxjs'; | |
| import { HttpEventType } from '@angular/common/http'; | |
| export enum FileQueueStatus { | |
| Pending, | |
| Success, | |
| Error, | |
| Progress | |
| } | |
| export class FileQueueObject { | |
| public file: any; | |
| public status: FileQueueStatus = FileQueueStatus.Pending; | |
| public progress: number = 0; | |
| public request: Subscription = null; | |
| public response: HttpResponse<any> | HttpErrorResponse = null; | |
| constructor(file: any) { | |
| this.file = file; | |
| } | |
| // actions | |
| public upload = () => { /* set in service */ }; | |
| public cancel = () => { /* set in service */ }; | |
| public remove = () => { /* set in service */ }; | |
| // statuses | |
| public isPending = () => this.status === FileQueueStatus.Pending; | |
| public isSuccess = () => this.status === FileQueueStatus.Success; | |
| public isError = () => this.status === FileQueueStatus.Error; | |
| public inProgress = () => this.status === FileQueueStatus.Progress; | |
| public isUploadable = () => this.status === FileQueueStatus.Pending || this.status === FileQueueStatus.Error; | |
| } | |
| // tslint:disable-next-line:max-classes-per-file | |
| @Injectable() | |
| export class FileUploaderService { | |
| public url: string = 'https://jsonplaceholder.typicode.com/posts'; | |
| private _queue: BehaviorSubject<FileQueueObject[]>; | |
| private _files: FileQueueObject[] = []; | |
| constructor(private http: HttpClient) { | |
| this._queue = <BehaviorSubject<FileQueueObject[]>>new BehaviorSubject(this._files); | |
| } | |
| // the queue | |
| public get queue() { | |
| return this._queue.asObservable(); | |
| } | |
| // public events | |
| public onCompleteItem(queueObj: FileQueueObject, response: any): any { | |
| return { queueObj, response }; | |
| } | |
| // public functions | |
| public addToQueue(data: any) { | |
| // add file to the queue | |
| _.each(data, (file: any) => this._addToQueue(file)); | |
| } | |
| public clearQueue() { | |
| // clear the queue | |
| this._files = []; | |
| this._queue.next(this._files); | |
| } | |
| public uploadAll() { | |
| // upload all except already successfull or in progress | |
| _.each(this._files, (queueObj: FileQueueObject) => { | |
| if (queueObj.isUploadable()) { | |
| this._upload(queueObj); | |
| } | |
| }); | |
| } | |
| // private functions | |
| private _addToQueue(file: any) { | |
| const queueObj = new FileQueueObject(file); | |
| // set the individual object events | |
| queueObj.upload = () => this._upload(queueObj); | |
| queueObj.remove = () => this._removeFromQueue(queueObj); | |
| queueObj.cancel = () => this._cancel(queueObj); | |
| // push to the queue | |
| this._files.push(queueObj); | |
| this._queue.next(this._files); | |
| } | |
| private _removeFromQueue(queueObj: FileQueueObject) { | |
| _.remove(this._files, queueObj); | |
| } | |
| private _upload(queueObj: FileQueueObject) { | |
| // create form data for file | |
| const form = new FormData(); | |
| form.append('file', queueObj.file, queueObj.file.name); | |
| // upload file and report progress | |
| const req = new HttpRequest('POST', this.url, form, { | |
| reportProgress: true, | |
| }); | |
| // upload file and report progress | |
| queueObj.request = this.http.request(req).subscribe( | |
| (event: any) => { | |
| if (event.type === HttpEventType.UploadProgress) { | |
| this._uploadProgress(queueObj, event); | |
| } else if (event instanceof HttpResponse) { | |
| this._uploadComplete(queueObj, event); | |
| } | |
| }, | |
| (err: HttpErrorResponse) => { | |
| if (err.error instanceof Error) { | |
| // A client-side or network error occurred. Handle it accordingly. | |
| this._uploadFailed(queueObj, err); | |
| } else { | |
| // The backend returned an unsuccessful response code. | |
| this._uploadFailed(queueObj, err); | |
| } | |
| } | |
| ); | |
| return queueObj; | |
| } | |
| private _cancel(queueObj: FileQueueObject) { | |
| // update the FileQueueObject as cancelled | |
| queueObj.request.unsubscribe(); | |
| queueObj.progress = 0; | |
| queueObj.status = FileQueueStatus.Pending; | |
| this._queue.next(this._files); | |
| } | |
| private _uploadProgress(queueObj: FileQueueObject, event: any) { | |
| // update the FileQueueObject with the current progress | |
| const progress = Math.round(100 * event.loaded / event.total); | |
| queueObj.progress = progress; | |
| queueObj.status = FileQueueStatus.Progress; | |
| this._queue.next(this._files); | |
| } | |
| private _uploadComplete(queueObj: FileQueueObject, response: HttpResponse<any>) { | |
| // update the FileQueueObject as completed | |
| queueObj.progress = 100; | |
| queueObj.status = FileQueueStatus.Success; | |
| queueObj.response = response; | |
| this._queue.next(this._files); | |
| this.onCompleteItem(queueObj, response.body); | |
| } | |
| private _uploadFailed(queueObj: FileQueueObject, response: HttpErrorResponse) { | |
| // update the FileQueueObject as errored | |
| queueObj.progress = 0; | |
| queueObj.status = FileQueueStatus.Error; | |
| queueObj.response = response; | |
| this._queue.next(this._files); | |
| } | |
| } |
added ability to cancel a request and a few minor tweaks
changed statuses to enums
very useful, thank u.
@stuartaccent I have a question. Is multipart/form-data supported by your code?
I trying to call a server built with https://github.com/expressjs/multer
I'm sorry for the stupid question. It's working also with multipart without to change anything. Thanks 👍
hey @Ks89, np, glad u found it useful.
Very helpful, thank you!
I had to add this to the @component : providers: [FileUploaderService],
and in the service.ts I had to add: import { Subscription } from 'rxjs/Subscription';
and I ended up importing HttpClientModule in my main app to solve a StaticInjectorError.
To help beginners like myself you might also considering adding the stylesheet and the global vars file so it works right out of the box.
Have added an example at https://stackblitz.com/edit/angular-5-file-upload-queue
Good stuff - many thanks! Plugged into an app and worked perfectly OOTB
Thank you! Very helpful.
If the size of the selected files exceed 100 MB, it does not work - the file selection will not even list the files. Is there a limitation or restriction?
Hi @ganeshmuthuvelu,
sorry for the massive delay. Did u ever find out what this was? there is no limit that i know of.
The only thing that springs to mind that does have a 100mg limit for a request is Cloudflare.
Hi everyone...I am a beginner... Thanks a lot to Mr. Stuartaccent for your awesome code.. I have download your code and it works fine... When i change the URL to my cloud domain's URL it is showing this error.... "HttpErrorResponse {headers: {…}, status: 0, statusText: "Unknown Error",".. I have tried with localhost which has xamp server too. Do i need to run any code in my server part? If so what code should i run? Thanks in advance....
You may have to fiddle with the request headers depending on your api