Forked from stuartaccent/file-uploader.component.html
Created
January 27, 2021 11:17
-
-
Save space11/da9f1f1f6f3cf06a29574deaaedf08d6 to your computer and use it in GitHub Desktop.
Angular 7 file uploader with queue and progress using HttpClient, example https://stackblitz.com/edit/angular-7-file-upload-queue
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment