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 can be achieved with the HttpClient
available in Angular.
Running the sample application will give you the 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.
The 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 more 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 what 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 wrap 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 the best practice.
To get the progress report of an 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 an 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 subscribed to this source in the ngOnInit
hook 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.
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