B

In this topic, we will learn how to upload file in angular 2+ applications. We will use HttpClient to send POST request with multi-part form data to the server.

Let's create a service for file upload. code of angular service looks like this:

import { Injectable } from '@angular/core';
import { HttpHeaders, HttpClient, HttpEventType } from '@angular/common/http';
import { map } from 'rxjs/operators';

@Injectable()
export class MediaService {
  fileSizeUnit: number = 1024;
  public isApiSetup = false;

  constructor(private http: HttpClient) { }


  getFileSize(fileSize: number): number {
    if (fileSize > 0) {
      if (fileSize < this.fileSizeUnit * this.fileSizeUnit) {
        fileSize = parseFloat((fileSize / this.fileSizeUnit).toFixed(2))
      }
      else if (fileSize < this.fileSizeUnit * this.fileSizeUnit * this.fileSizeUnit) {
        fileSize = parseFloat((fileSize / this.fileSizeUnit / this.fileSizeUnit).toFixed(2))
      }
    }

    return fileSize;
  }

  getFileSizeUnit(fileSize: number) {
    let fileSizeInWords = 'bytes';

    if (fileSize > 0) {
      if (fileSize < this.fileSizeUnit) {
        fileSizeInWords = 'bytes'
      }
      else if (fileSize < this.fileSizeUnit * this.fileSizeUnit) {
        fileSizeInWords = 'KB'
      }
      else if (fileSize < this.fileSizeUnit * this.fileSizeUnit * this.fileSizeUnit) {
        fileSizeInWords = 'MB'
      }
    }

    return fileSizeInWords;
  }

  uploadMedia(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}`;
      }
    }));
  }
}

Check uploadMedia method in the above code. in http client's post method's third parameter expects options object. The notable properties in our case are reportProgress and observe.

 

In order to get the progress reportProgress must be set to true and observe must be set to 'events'.

 

Inside map function of rxjs, we will check for the event type. here, it's value will be an integer. Rather than using integer values we can use  HttpEventType enum provided by the package @angular/common/http. In our case, only two enum values are useful such as UploadProgress and Response

 

In both cases, I have planned to use the common object having two properties such as status and message.

Status can have two values such as 'progress' and 'completed', later one will be returned from the server which will be handled by case HttpEventType.Response.

 

Let's see how to use this service into our component. Let's have a look at the code for this:

 

typescript:

import { Component, OnInit } from '@angular/core';
import { Subject } from 'rxjs'
import { takeUntil } from 'rxjs/operators';
import { MediaService } from '../media.service'

@Component({
  selector: 'app-media',
  templateUrl: './media.component.html',
  styleUrls: ['./media.component.css']
})
export class MediaComponent implements OnInit {
  uploadedMedia: Array<any> = [];

  constructor(private mediaService: MediaService) { }

  ngOnInit() {
  }

  onFileBrowse(files) {
    this.processFiles(files);
  }
  processFiles(files) {
    for (const file of files) {
      var reader = new FileReader();
      reader.readAsDataURL(file); // read file as data url
      reader.onload = (event: any) => { // called once readAsDataURL is completed

        this.uploadedMedia.push({
          'FileName': file.name,
          'FileSize': this.mediaService.getFileSize(file.size) + ' ' + this.mediaService.getFileSizeUnit(file.size),
          'FileType': file.type,
          'FileUrl': event.target.result,
          'FileProgessSize': 0,
          'FileProgress': 0,
          'ngUnsubscribe': new Subject<any>()
        })

        this.startProgress(file, this.uploadedMedia.length - 1);
      }
    }
  }

  async startProgress(file, index) {
    let filteredFile = this.uploadedMedia.filter((u, index) => index === index).pop();
    
    if (filteredFile != null) {
      let fileSize = this.mediaService.getFileSize(file.size);
      let fileSizeInWords = this.mediaService.getFileSizeUnit(file.size);
      if (this.mediaService.isApiSetup) {
        let formData = new FormData();
        formData.append('File', file);

        this.mediaService.uploadMedia(formData)
          .pipe(takeUntil(file.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("file upload error")
            console.log(error);
          });
      }
      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(ms: number) {
    return new Promise((resolve) => {
      setTimeout(resolve, ms);
    });
  }

  removeImage(idx: number) {
    this.uploadedMedia = this.uploadedMedia.filter((u, index) => index !== idx);
  }
}

  

Html:

<h2>Angular 2+ File Upload</h2>


<input  type="file" accept=".jpg,.jpeg,.png"  (change)="onFileBrowse($event.target.files)" />


<div class="media-upload-table-container" *ngIf="uploadedMedia.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 media of uploadedMedia; let i = index">
          <td>
            <div class="d-flex flex-row align-items-center">
              <!-- <div style="margin-right: 8px;">
                <img  class="add-media-img" src="{{media.FileUrl}}">
              </div> -->
              <div class="media-file-name">
                <span style="word-wrap: break-word; white-space: pre-line">
                  {{media.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.%': media.FileProgress }" class="second-progress">

                  </div>
                </div>
              </div>
              <div class="text-center">
                {{ media.FileProgessSize }} of {{ media.FileSize }}
              </div>
            </div>
          </td>
          <td style="vertical-align:middle;text-align: right;">
            <div class="media-upload-check">
              <span *ngIf="media.FileProgress === 100">
                Completed</span>
            </div>
          </td>
          <td style="vertical-align:middle;">
            <a class="remove-media-txt" (click)="removeImage(i)">
              Remove
            </a>
          </td>
        </tr>
      </tbody>
    </table>

  </div>

 

If you see in above typescript code there is a method startProgress. We are using uploadedMedia array to keep track of the media uploaded by the user and we will show the progress of each in the grid on user interface.

 

If you notice, our media service has one boolean flag isApiSetup. 

 

Using API:

  • If isApiSetup is true, we will use the file upload on the server. here when 'progress' or 'completed' status received we will calculate file size and update it in the relevant object in the list.
  • Also, file object in the list has ngUnsubscribe property which is used in takeUntil operator of rxjs. So once, the user removes the file before 100% completion, we will send some value to it. So, file upload on the server will be canceled.

 

Using Fake Waitor:

 

  • If isApiSetup is false then we will use fake waiter to emulate the file upload rather than uploading the file on the server. It is useful if there is no api developed for file upload but still you want to check and modify and design of progress bar, percentage, file size etc. so, you don't have to rely on the api to test and modify the design.
  • Here, fakeWaitor method will return the promise which will be resolved after a certain amount of time in parameter and this will be continuously done until for loop variable has reached to value of original file size.

That's it. To view the full code click here.

Tags:
  • File Upload
  • Progress

Comments

Leave a Reply