Any type of file upload in Angular is not different than a regular file upload using native HTML file input control and a little bit of javascript. POST
ing the file to the server, however, can be achieved with HttpClient
in Angular.
Running the sample application will give you an option to select an image with a file control. You can see the preview of the selected image before you start uploading it to the server. A progress control is also added so that you can see the upload progress in real-time.

Markup for the UI is pretty simple as,
<div class="columns">
<div class="column is-one-third">
<div class="field">
<div class="file has-name is-primary is-fullwidth">
<label class="file-label">
<input
class="file-input"
type="file"
name="file"
#fileInput
(change)="onChange(fileInput.files[0])"
/>
<span class="file-cta">
<span class="file-icon">
<fa-icon [icon]="['fas', 'upload']"></fa-icon>
</span>
<span class="file-label">
Choose a file…
</span>
</span>
<span class="file-name">
{{ fileName }}
</span>
</label>
</div>
</div>
<article class="message" *ngIf="infoMessage">
<div class="message-body">
{{ infoMessage }}
</div>
</article>
<div class="field" *ngIf="!infoMessage">
<div class="control">
<progress
class="progress is-primary"
[attr.value]="progress"
max="100"
></progress>
</div>
</div>
<div class="field">
<div class="control">
<button
class="button is-primary"
(click)="onUpload()"
[attr.disabled]="isUploading ? '' : null"
>
Upload
</button>
</div>
</div>
</div>
<div class="column">
<figure class="image is-128x128">
<img [src]="imageUrl" />
</figure>
</div>
</div>
I'm only concerned with a single upload that's why I passed the first file in the files
array down to the onChange
event.
Notice that there is no form element wrapped around the file input
The component class does nothing interesting than reading the image using FileReader
and setting the preview.
import { Component, OnInit } from "@angular/core";
import { UploaderService } from "../services/uploader.service";
@Component({
selector: "app-user",
templateUrl: "./user.component.html",
styleUrls: ["./user.component.scss"]
})
export class UserComponent implements OnInit {
progress: number;
infoMessage: any;
isUploading: boolean = false;
file: File;
imageUrl: string | ArrayBuffer =
"https://bulma.io/images/placeholders/480x480.png";
fileName: string = "No file selected";
constructor(private uploader: UploaderService) {}
ngOnInit() {
this.uploader.progressSource.subscribe(progress => {
this.progress = progress;
});
}
onChange(file: File) {
if (file) {
this.fileName = file.name;
this.file = file;
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = event => {
this.imageUrl = reader.result;
};
}
}
onUpload() {
this.infoMessage = null;
this.progress = 0;
this.isUploading = true;
this.uploader.upload(this.file).subscribe(message => {
this.isUploading = false;
this.infoMessage = message;
});
}
}
In this scenario, using Reactive Forms control only to check the validity of the attached file control is an overkill. That's why it is ignored.
What's interesting is the uploader service. Here's how it looks like:
import { Injectable } from "@angular/core";
import {
HttpClient,
HttpRequest,
HttpEventType,
HttpEvent
} from "@angular/common/http";
import { map, tap, last } from "rxjs/operators";
import { BehaviorSubject } from "rxjs";
@Injectable({
providedIn: "root"
})
export class UploaderService {
public progressSource = new BehaviorSubject<number>(0);
constructor(private http: HttpClient) {}
upload(file: File) {
let formData = new FormData();
formData.append("avatar", file);
const req = new HttpRequest(
"POST",
"http://localhost:5000/upload",
formData,
{
reportProgress: true
}
);
return this.http.request(req).pipe(
map(event => this.getEventMessage(event, file)),
tap((envelope: any) => this.processProgress(envelope)),
last()
);
}
processProgress(envelope: any): void {
if (typeof envelope === "number") {
this.progressSource.next(envelope);
}
}
private getEventMessage(event: HttpEvent<any>, file: File) {
switch (event.type) {
case HttpEventType.Sent:
return `Uploading file "${file.name}" of size ${file.size}.`;
case HttpEventType.UploadProgress:
return Math.round((100 * event.loaded) / event.total);
case HttpEventType.Response:
return `File "${file.name}" was completely uploaded!`;
default:
return `File "${file.name}" surprising upload event: ${event.type}.`;
}
}
}
You can send the raw file or wrapped it into a FormData
before sending it to the server-side. It depends on your own implementation or the server-side framework you are working with. However, in most cases encrypting the file with FormData
is considered best practice.
To get the progress report of a HTTP request, you have to enable the flag for reportProgress
. An ongoing HTTP request can emit a bunch of events and we can tap into those using the Rxjs tap
operator. When the upload is finished the last value (HttpEventType.Response
) is emitted to the subscriber (onUpload()
function of user.component.ts
). The subscriber then shows the value in a presentable way.
To update the progress bar in real-time, I took the liberty to declare a BehaviorSubjcet
named progressSource
. This subject will get a new value every time a HttpEventType.UploadProgress
event is emitted. This event contains the loaded
and total
property which is used in this scenario to find out the percentage of the uploaded file. I've subscribed to this source in ngOnInit
of the component class.
And that's all about it. Fire up your own server and change the url in the uploader service to your upload API
endpoint.
Don't have time to run the project? Have a GIF and chill!

Repository:
https://github.com/fiyazbinhasan/ng-playground/tree/ng-playground-upload-file
Links:
https://angular.io/api/common/http/HttpRequest
https://rxjs-dev.firebaseapp.com/api/operators/tap

Comments