Angular File Upload Example

By Arvind Rai, March 14, 2020
This page will walk through Angular single and multiple file upload example with upload progress. To select file we need to create HTML input with type="file". After file selection we can access file content using event.target.files on change event. We can also use document.getElementById to get file contents.
To post data, we can use HttpClient.post method and pass its options as observe: 'events' and reportProgress: true so that we can track upload progress.
On this page we will create reactive form and use FormArray to provide UI for multiple file upload. On form submit we will display file upload progress for every selected file. Now let us discuss file upload example step-by-step.

1. Technologies Used

Find the technologies being used in our example.
1. Angular 9.0.2
2. Node.js 12.5.0
3. NPM 6.9.0

2. File Input

In HTML 5, we can create file input to select or drag and drop files.
<input type="file"> 
By default we can select only one file. To select multiple files, we need to use multiple attribute as following.
<input type="file" multiple> 
To read the content of those selected files, we can use following.
1. Using $event.target.files with onChange event.
<input type="file" (change)="upload($event.target.files)"> 
2. Using document.getElementById . Suppose we have a file input with an id.
<input type="file" id="myFile"> 
Now we can use document.getElementById to read file contents as following.
const selectedFileList = (<HTMLInputElement>document.getElementById('myFile')).files; 
We get the array of selected files. We know that by default we can select only one file if we are not using multiple attributes in file input. To get the file use array index.
const file = selectedFileList.item(0); 
File name can be obtained by file.name and file size can be obtained by file.size.

3. Using FormData

The FormData is a web API to represent HTML form data as key/value. It is ceated as following.
const formData = new FormData(); 
The FormData has a method as append() that appends key/value to the FormData. We will append our File object with key.
formData.append("file", file); 

4. Post Data with Progress

We can post our FormData as following.
uploadWithProgress(formData: FormData): Observable<any> {
   return this.http.post(this.url, formData, { observe: 'events',  reportProgress: true });
} 
The observe defines how we want response, for example, response, body or events. The events is for response with events. To get the progress, use reportProgress as true.
When we subscribe the Observable instance, we can get progress and upload success as following.
this.fuService.uploadWithProgress(formData)
  .subscribe(event => {
	if (event.type === HttpEventType.UploadProgress) {
	  this.percentCompleted = Math.round(100 * event.loaded  / event.total);
	} else if (event instanceof HttpResponse) {
	  this.isUploaded = true;
	}
  }); 
HttpEventType.UploadProgress : This event type says that an upload progress event has been received. To show upload progress, we can calculate percentage using event.loaded and event.total.

HttpResponse: It represents a full HTTP response. If event is the instance of HttpResponse, it means posting of data is completed.

HttpUploadProgressEvent
While uploading the content, the event in subscribe() is of HttpUploadProgressEvent type. The HttpUploadProgressEvent is an upload progress event. It has properties as following.
type: Event type such as HttpEventType.UploadProgress.
loaded: Amount uploaded at a time.
total: Total amount to upload.

5. Single File Upload

Find the steps to upload a single file.
1. Create a file input. We will upload file on change event.
<input type="file" (change)="upload($event.target.files)">

<div *ngIf="isSingleUploaded" class="success">
     File uploaded successfully with Url: <b>{{urlAfterUpload}}</b>
</div> 
2. When we select the file, following method will be called.
upload(files: File[]) {
    const formData = new FormData();
    formData.append("file", file);
    this.fuService.uploadWithProgress(formData)
      .subscribe(event => {
        if (event.type === HttpEventType.UploadProgress) {
          this.percentCompleted = Math.round(100 * event.loaded / event.total);
        } else if (event instanceof HttpResponse) {
          this.isSingleUploaded = true;
          this.urlAfterUpload = event.body.link;
        }
      });
} 
On successful upload of file, a success message with uploaded file URL will be displayed.

6. Multiple File Upload with Reactive Form

1. To upload multiple files, we will create reactive form. In the FormGroup, we will create FormArray.
uploadForm = this.formBuilder.group({
   ------
   filesToUpload: this.formBuilder.array([
     this.formBuilder.control('', [Validators.required])
   ])
}); 
2. Create the HTML form and use formArrayName. We can add more form control to upload multiple files.
<form [formGroup]="uploadForm" (ngSubmit)="onFormSubmit()">
------
  <div formArrayName="filesToUpload">
      <div *ngFor="let f of filesToUpload.controls; index as i">
        <input [formControlName]="i" type="file" id="file{{i}}">
        {{percentUploaded[i]}}%
        <button type="button" (click)="deleteFile(i)">Delete</button>
      </div>
  </div>
  <button type="button" (click)="addMoreFiles()">Add More Files</button>
  <button type="submit">Submit</button>
</form> 
3. On form submit, we will read all selected files and post them to server to upload them. We will also display upload progress per file.
onFormSubmit() {
  ------
  for (let i = 0; i < this.filesToUpload.length && this.uploadForm.valid; i++) {
     const selectedFileList = (<HTMLInputElement>document.getElementById('file' + i)).files;
     const file = selectedFileList.item(0);
     this.uploadFile(file, i);
  }
}
uploadFile(file: File, fileNum: number) {
    const formData = new FormData();
    formData.append("file", file);
    this.fuService.uploadWithProgress(formData)
      .subscribe(event => {
        if (event.type === HttpEventType.UploadProgress) {
          this.percentUploaded[fileNum] = Math.round(100 * event.loaded / event.total);
        } else if (event instanceof HttpResponse) {
          console.log(file.name + ', Size: ' + file.size + ', Uploaded URL: ' + event.body.link);
          this.fileUploadSuccess();
        }
      }
    );
} 

7. Validation

We can validate file upload such as required file selection and valid file extensions. In reactive form for required validation, we use Validators.required and for valid file extensions, we can create custom validator.
Find the custom validator for valid file extensions.
file-extension-validator.directive.ts
import { ValidatorFn, AbstractControl } from '@angular/forms';

export function fileExtensionValidator(validExt: string): ValidatorFn {
  return (control: AbstractControl): { [key: string]: any } | null => {
    let forbidden = true;
    if (control.value) {
      const fileExt = control.value.split('.').pop();
      validExt.split(',').forEach(ext => {
        if (ext.trim() == fileExt) {
          forbidden = false;
        }
      });
    }
    return forbidden ? { 'inValidExt': true } : null;
  };
} 
Now our FormGroup will be as following.
uploadForm = this.formBuilder.group({
   ------
   filesToUpload: this.formBuilder.array([
     this.formBuilder.control('', [Validators.required, fileExtensionValidator('jpg, png, wav, mp4')])
   ])
}); 
The HTML code will be as following.
<input [formControlName]="i" type="file" id="file{{i}}">
<label *ngIf="filesToUpload.controls[i].errors?.required" class="error">
  Select the file.
</label>
<label *ngIf="filesToUpload.controls[i].errors?.inValidExt 
  && !filesToUpload.controls[i].errors?.required" class="error">
  Invalid file extension.
</label> 


8. Complete Example with Single and Multiple Upload

Find the complete code of our demo application. In this example, we are providing demo for single and multiple upload. For multiple file upload, we will create reactive form. For valid file extension validation, the code for fileExtensionValidator(), has already been given above in the article. Now find the complete code.
file-upload.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, throwError } from 'rxjs';

import { catchError } from 'rxjs/operators';

@Injectable({
    providedIn: 'root'
})
export class FileUploadService {
    url = "https://file.io";
    constructor(private http: HttpClient) { }

    uploadWithProgress(formData: FormData): Observable<any> {
        return this.http.post(this.url, formData, { observe: 'events',  reportProgress: true })
            .pipe(
                catchError(err => this.handleError(err))
            );
    }
    private handleError(error: any) {
        return throwError(error);
    }
} 
file-upload.component.ts
import { Component } from '@angular/core';
import { HttpResponse, HttpEventType } from '@angular/common/http';
import { FormControl, FormArray, FormBuilder, Validators } from '@angular/forms';

import { fileExtensionValidator } from './file-extension-validator.directive';
import { FileUploadService } from './file-upload.service';

@Component({
  selector: 'app-upload',
  templateUrl: 'file-upload.component.html'
})
export class FileUploadComponent {
  percentCompleted: number = 0;
  isMultipleUploaded = false;
  isSingleUploaded = false;
  urlAfterUpload = '';
  percentUploaded = [0];
  acceptedExtensions = "jpg, jpeg, bmp, png, wav, mp3, mp4";
  constructor(private formBuilder: FormBuilder, private fuService: FileUploadService) {
  }
  upload(files: File[]) {
    console.log('---Uploading single file---');
    const file = files[0];
    console.log(file.name);
    this.isSingleUploaded = false;
    this.urlAfterUpload = '';

    const formData = new FormData();
    formData.append("file", file);
    this.fuService.uploadWithProgress(formData)
      .subscribe(event => {
        if (event.type === HttpEventType.UploadProgress) {
          this.percentCompleted = Math.round(100 * event.loaded / event.total);
        } else if (event instanceof HttpResponse) {
          this.isSingleUploaded = true;
          this.urlAfterUpload = event.body.link;
        }
      });
  }
  uploadForm = this.formBuilder.group({
    title: ['', Validators.required],
    filesToUpload: this.formBuilder.array([
      this.formBuilder.control('', [Validators.required, fileExtensionValidator(this.acceptedExtensions)])
    ])
  });
  get title(): FormControl {
    return this.uploadForm.get('title') as FormControl;
  }
  get filesToUpload(): FormArray {
    return this.uploadForm.get('filesToUpload') as FormArray;
  }
  addMoreFiles() {
    this.filesToUpload.push(this.formBuilder.control('',
      [Validators.required, fileExtensionValidator(this.acceptedExtensions)]));
    this.percentUploaded.push(0);
  }
  deleteFile(index: number) {
    this.filesToUpload.removeAt(index);
    this.percentUploaded.splice(index, 1);
  }
  onFormSubmit() {
    console.log('---Uploading multiple file---');
    this.isMultipleUploaded = false;
    for (let i = 0; i < this.filesToUpload.length && this.uploadForm.valid; i++) {
      const selectedFileList = (<HTMLInputElement>document.getElementById('file' + i)).files;
      const file = selectedFileList.item(0);
      this.uploadFile(file, i);
    }
    console.log(this.title.value);
  }
  uploadFile(file: File, fileNum: number) {
    const formData = new FormData();
    formData.append("file", file);
    this.fuService.uploadWithProgress(formData)
      .subscribe(event => {
        if (event.type === HttpEventType.UploadProgress) {
          this.percentUploaded[fileNum] = Math.round(100 * event.loaded / event.total);
        } else if (event instanceof HttpResponse) {
          console.log(file.name + ', Size: ' + file.size + ', Uploaded URL: ' + event.body.link);
          this.fileUploadSuccess();
        }
      },
        err => console.log(err)
      );
  }
  fileUploadSuccess() {
    let flag = true;
    this.percentUploaded.forEach(n => {
      if (n !== 100) {
        flag = false;
      }
    });
    if (flag) {
      this.isMultipleUploaded = true;
    }
  }
  formReset() {
    this.uploadForm.reset();
    this.isMultipleUploaded = false;
    for (let i = 0; i < this.percentUploaded.length; i++) {
      this.percentUploaded[i] = 0;
    }
  }
} 
file-upload.component.html
<h3>Single Upload with Progress</h3>
<table>
  <tr>
    <td>
      <input type="file" (change)="upload($event.target.files)">
    </td>
    <td>
      {{percentCompleted}}% Uploaded
    </td>
  </tr>
  <tr>
    <td colspan="2">
      <div *ngIf="isSingleUploaded" class="success">
        File uploaded successfully with Url:
        <b>{{urlAfterUpload}}</b>
      </div>
    </td>
  </tr>
</table>

<h3>Multiple Upload with Progress</h3>
<div *ngIf="isMultipleUploaded" class="success">File uploaded and form submitted successfully.
  <br/>
  <br/>
</div>

<form [formGroup]="uploadForm" (ngSubmit)="onFormSubmit()">
  <table>
    <tr>
      <td colspan="3">
        Title:
        <input formControlName="title">
        <label *ngIf="title.errors?.required" class="error">
          Title required.
        </label>
      </td>
    </tr>
    <div formArrayName="filesToUpload">
      <div *ngFor="let f of filesToUpload.controls; index as i">
        <tr>
          <td>
            <input [formControlName]="i" type="file" id="file{{i}}">
            <label *ngIf="filesToUpload.controls[i].errors?.required" class="error">
              Select the file.
            </label>
            <label *ngIf="filesToUpload.controls[i].errors?.inValidExt 
              && !filesToUpload.controls[i].errors?.required" class="error">
              Invalid file extension.
            </label>
          </td>
          <td>{{percentUploaded[i]}}% </td>
          <td>
            <button type="button" (click)="deleteFile(i)">Delete</button>
          </td>
        </tr>
      </div>
    </div>
    <tr>
      <td colspan="3">
        <button type="submit">Submit</button>
        <button type="button" (click)="addMoreFiles()">Add More Files</button>
        <button type="button" (click)="formReset()">Reset</button>
      </td>
    </tr>
  </table>
</form> 
app.component.ts
import { Component } from '@angular/core';

@Component({
   selector: 'app-root',
   template: `
	<app-upload></app-upload>
    `
})
export class AppComponent { 
} 
styles.css
table {
    border-collapse: collapse;
}
table, th, td {
    border: 1px solid black;
    padding: 10px;
}
.error {
    color: red;
}
.success {
    color: green;
} 
app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { ReactiveFormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';

import { AppComponent } from './app.component';
import { FileUploadComponent } from './file-upload.component';

@NgModule({
    imports: [
        BrowserModule,
        ReactiveFormsModule,
        HttpClientModule
    ],
    declarations: [
        AppComponent,
        FileUploadComponent
    ],
    providers: [
    ],
    bootstrap: [
        AppComponent
    ]
})
export class AppModule { } 

9. Run Application

To run the application, find the steps.
1. Download source code using download link given below on this page.
2. Use downloaded src in your Angular CLI application. To install Angular CLI, find the link.
3. Run ng serve using command prompt.
4. Access the URL http://localhost:4200
Find the print screen using Mozilla Firefox browser.
Angular File Upload Example

10. References

HttpClient
HttpUploadProgressEvent

11. Download Source Code

POSTED BY
ARVIND RAI
ARVIND RAI
LEARN MORE








©2024 concretepage.com | Privacy Policy | Contact Us