This article shows how to upload a file with a progress bar that displays the number of bytes or percentage of the upload that has been finished.
Develop an Angular application
Let’s use the angular cli to construct an angular application.
npm install -g @angular/cli
By typing your name into the command line like this, you may create a new project with the name of your choice:
ng new MulFileUploadProgress
Let’s build a service for uploading files. The angular service’s code seems as follows:
import { Injectable } from '@angular/core'; import { HttpHeaders, HttpClient, HttpEventType } from '@angular/common/http'; import { map } from 'rxjs/operators'; @Injectable({ providedIn: 'root' }) export class FileUploadProgressService { fileUnitSize: number = 1024; public isApiSetup = false; constructor(private http: HttpClient) {} getSelectFileSize(fSize: number): number { if (fSize > 0) { if (fSize < this.fileUnitSize * this.fileUnitSize) { fSize = parseFloat((fSize / this.fileUnitSize).toFixed(2)); } else if ( fSize < this.fileUnitSize * this.fileUnitSize * this.fileUnitSize ) { fSize = parseFloat( (fSize / this.fileUnitSize / this.fileUnitSize).toFixed(2) ); } } return fSize; } getSelectFileSizeUnit(fSize: number) { let fileSizeInWords = 'bytes'; if (fSize > 0) { if (fSize < this.fileUnitSize) { fileSizeInWords = 'bytes'; } else if (fSize < this.fileUnitSize * this.fileUnitSize) { fileSizeInWords = 'KB'; } else if ( fSize < this.fileUnitSize * this.fileUnitSize * this.fileUnitSize ) { fileSizeInWords = 'MB'; } } return fileSizeInWords; } uploadFile(formData: any) { const headers = new HttpHeaders().set('Content-Type', 'application/json'); return this.http .post(`http://yourapiurl`, formData, { headers, reportProgress: true, observe: 'events', }) .pipe( map((event) => { switch (event.type) { case HttpEventType.UploadProgress: const progress = Math.round((100 * event.loaded) / event.total!); return { status: 'progress', message: progress }; case HttpEventType.Response: return event.body; default: return `Unhandled event: ${event.type}`; } }) ); } }
Please review the code above for the uploadMedia function. The third parameter of the post function for HttpClient expects an options object. In our situation, reportProgress and observe are noteworthy qualities.
To obtain the development report
Both progress and observation must be set to “events.”
Here, we will examine the event type in switch case statements inside the map function of rxjs. We may use the HttpEventType enum provided by the package @angular/common/http in place of utilising integer values to compare. Only two enum entries, such as UploadProgress and Response, are applicable in our situation.
I’ve chosen to utilise the same object in both situations, which has two attributes like status and message.
status can be either “progress” or “finished,” and one of these values will be used later.
import { Component, OnInit } from '@angular/core'; import { Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; import { FileUploadProgressService } from '../file-upload-progress.service'; @Component({ selector: 'app-file-upload-progress', templateUrl: './file-upload-progress.component.html', styleUrls: ['./file-upload-progress.component.scss'] }) export class FileUploadProgressComponent implements OnInit { mediaArray: Array<any> = []; constructor(private _Service: FileUploadProgressService) {} ngOnInit() {} onFileBrowse(event: Event) { const select = event.target as HTMLInputElement; this.ProcFiles(select.files); } ProcFiles(files:any) { for (const file of files) { var fileReader = new FileReader(); fileReader.readAsDataURL(file); fileReader.onload = (event: any) => { this.mediaArray.push({ FileName: file.name, FileSize:this._Service.getSelectFileSize(file.size) + ' ' +this._Service.getSelectFileSizeUnit(file.size), FileType: file.type, FileUrl: event.target.result, FileProgessSize: 0, FileProgress: 0, ngUnsubscribe: new Subject<any>(), }); this.startProgress(file, this.mediaArray.length - 1); }; } } async startProgress(e:any, i:number) { let filteredFile = this.mediaArray.filter((u, i) => i === i).pop(); if (filteredFile != null) { let fileSize = this._Service.getSelectFileSize(e.size); let fileSizeInWords = this._Service.getSelectFileSizeUnit(e.size); if (this._Service.isApiSetup) { let formData = new FormData(); formData.append('File', e); this._Service.uploadFile(formData).pipe(takeUntil(e.ngUnsubscribe)).subscribe((res: any) => { if (res.status === 'progress') { let completedPercentage = parseFloat(res.message); filteredFile.FileProgessSize = `${( (fileSize * completedPercentage) / 100 ).toFixed(2)} ${fileSizeInWords}`; filteredFile.FileProgress = completedPercentage; } else if (res.status === 'completed') { filteredFile.Id = res.Id; filteredFile.FileProgessSize = fileSize + ' ' + fileSizeInWords; filteredFile.FileProgress = 100; } }, (error: any) => { console.log('a file upload failure'); } ); } else { for ( var f = 0; f < fileSize + fileSize * 0.0001; f += fileSize * 0.01 ) { filteredFile.FileProgessSize = f.toFixed(2) + ' ' + fileSizeInWords; var percentUploaded = Math.round((f / fileSize) * 100); filteredFile.FileProgress = percentUploaded; await this.fakeWaiter(Math.floor(Math.random() * 35) + 1); } } } } fakeWaiter(e: number) { return new Promise((resolve) => { setTimeout(resolve, e); }); } removeImage(id: number) { this.mediaArray = this.mediaArray.filter((u, index) => index !== id); } }
Add the following code to the HTML file for this component now:
<section class="main"> <div> <h2>Uploading a file with a progress bar</h2> </div> <input type="file" accept=".jpg,.jpeg,.png" (change)="onFileBrowse($event)" /> <div *ngIf="mediaArray.length > 0"> <table class="media-upload-table table table-borderless"> <thead> <tr> <th style="width: 246px"></th> <th class="media-progress-bar"></th> <th style="width: 100px;"></th> </tr> </thead> <tbody> <tr *ngFor="let item of mediaArray; let i = index"> <td> <div class="d-flex flex-row align-items-center"> <div class="media-file-name"> <span style="word-wrap: break-word; white-space: pre-line"> {{ item.FileName }} </span> </div> </div> </td> <td style="vertical-align:middle;"> <div class="d-flex flex-column" style="margin-top: 18px;"> <div> <div class="first-progress"> <div [ngStyle]="{ 'width.%': item.FileProgress }" class="second-progress" ></div> </div> </div> <div class="text-center"> {{ item.FileProgessSize }} of {{ item.FileSize }} </div> </div> </td> <td style="vertical-align:middle;text-align: right;"> <div> <span *ngIf="item.FileProgress === 100"> Completed</span> </div> </td> <td style="vertical-align:middle;" class="pointer"> <a (click)="removeImage(i)"> Remove </a> </td> </tr> </tbody> </table> </div> </section>
All done. To test the charm, issue the following command.
ng serve