How to upload files in Angular
Last edited:
Nikola ĐuzaIn today's interconnected digital landscape, file uploads have become essential to creating web applications. Whether you're building a social media platform, an e-commerce site, or a collaborative document editing tool, enabling users to upload files seamlessly is crucial.
Angular has been around since 2010, and according to the State of JS research, it is still the second most popular front-end framework. However, you can often take time and effort to implement a reliable and user-friendly file upload functionality with Angular. Fortunately, with the collaboration of the right tools, you can simplify the file upload process and provide a delightful user experience.
This guide will explore how to handle file uploading with Angular. We'll build a simple application with a user-friendly interface to upload single and multiple files. We'll also figure out how to streamline the development workflow using the Uploadcare widget and implement file-uploading functionality in just a few minutes.
So, without further ado, let's discover how to take your file upload functionality to the next level!
Oh, wait! Here's what our result is going to look like! Amazing, huh?
Setting up the Project
To begin, let's create a new Angular project using the Angular CLI. If you haven't installed it yet, you can do so by following these steps:
- Open your terminal or command prompt.
- Run the command
npm install -g @angular/cli@16
to install the Angular CLI globally on your system. - Run
ng new angular-file-upload
to generate a new Angular project called "angular-file-upload".
After the project is generated, navigate into the project directory using cd angular-file-upload
.
Implementing the Single File Upload Component
Let's generate a new component called "single-file-upload" to handle the single-file upload functionality:
ng generate component single-file-upload
This command will create several files for us, including single-file-upload.component.html
and single-file-upload.component.ts
, which we'll focus on in this blog post.
Open the single-file-upload.component.html
file and add the following code:
<h2>Single File Upload</h2>
<input type="file" class="file-input" (change)="onChange($event)" #fileUpload />
<div *ngIf="file">
<section class="file-info">
File details:
<ul>
<li>Name: {{file.name}}</li>
<li>Type: {{file.type}}</li>
<li>Size: {{file.size}} bytes</li>
</ul>
</section>
<button (click)="onUpload()">Upload the file</button>
<section [ngSwitch]="status">
<p *ngSwitchCase="'uploading'">⏳ Uploading...</p>
<p *ngSwitchCase="'success'">✅ Done!</p>
<p *ngSwitchCase="'fail'">❌ Error!</p>
<p *ngSwitchDefault>😶 Waiting to upload...</p>
</section>
</div>
In this code snippet, we've added an input field for file selection, a section to display file details, a button to initiate the file upload, and a status section to indicate the progress of the upload.
Next, open the single-file-upload.component.ts
file and replace its contents with the following code:
import { Component } from "@angular/core";
import { HttpClient } from "@angular/common/http";
import { throwError } from 'rxjs';
@Component({
selector: "app-single-file-upload",
templateUrl: "./single-file-upload.component.html",
styleUrls: ["./single-file-upload.component.css"],
})
export class SingleFileUploadComponent {
status: "initial" | "uploading" | "success" | "fail" = "initial"; // Variable to store file status
file: File | null = null; // Variable to store file
constructor(private http: HttpClient) {}
ngOnInit(): void {}
// On file Select
onChange(event: any) {
const file: File = event.target.files[0];
if (file) {
this.status = "initial";
this.file = file;
}
}
onUpload() {
// we will implement this method later
}
}
With this TypeScript code, we have defined the SingleFileUploadComponent
class.
It includes properties to track the upload status and the selected file.
The onChange
function is triggered when a file is selected, updating the file
property with the selected file.
The onUpload
function is called when clicking the "Upload the file" button.
Within this function, we'll create a new FormData
object, append the selected file to it, and send a POST request using Angular's HTTP client.
Finally, import the module and its dependencies in src/app/app/module.ts:
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { HttpClientModule } from '@angular/common/http';
import { AppComponent } from './app.component';
import { SingleFileUploadComponent } from './single-file-upload/single-file-upload.component';
@NgModule({
declarations: [
AppComponent,
SingleFileUploadComponent
],
imports: [
BrowserModule,
HttpClientModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
To visualize the single file upload component, open the src/app/app.component.html file and add the following code:
<!-- Other HTML -->
<h1>Angular File Upload</h1>
<app-single-file-upload></app-single-file-upload>
<!-- Other HTML -->
Now, you can run the project by executing ng serve
in the project's root directory. The application will be accessible at http://localhost:4200
, and you should see the single file upload component displayed.
We can run ng serve
to view what we built at the project's root. The result should look like this:
At this point, we can select a file for upload, and the component will display its details. However, clicking the "Upload the file" button won't trigger any action.
We still need a crucial component - the actual file-uploading functionality. To address this, we will leverage the power of FormData and Angular's HTTP client capabilities.
Utilizing Angular's HTTP Client and FormData
Angular provides an exceptional feature set, including its built-in HTTP client, which enables effortless request handling. In our case, we will combine this capability with FormData to accomplish our file-uploading objectives.
To begin, let's incorporate the uploading logic into the onUpload
method located in single-file-upload.component.ts
:
onUpload() {
if (this.file) {
const formData = new FormData();
formData.append('file', this.file, this.file.name);
const upload$ = this.http.post("https://httpbin.org/post", formData);
this.status = 'uploading';
upload$.subscribe({
next: () => {
this.status = 'success';
},
error: (error: any) => {
this.status = 'fail';
return throwError(() => error);
},
});
}
}
In this code snippet, we first validate whether a file has been selected for uploading.
If a file is present, we create a new instance of FormData
using new FormData()
and assign it to the variable formData
.
It enables us to append our file to the formData
object using the formData.append(file)
method.
Next, we call this.http.post
with a URL designated for file uploads. In our example, we utilize https://httpbin.org/post
. However, you will likely employ a backend route or a suitable third-party service such as Uploadcare. Within the POST request, we include the formData
object as follows:
const upload$ = this.http.post('https://httpbin.org/post', formData);
Angular's this.http.post method
seamlessly integrates with FormData
, streamlining the file upload process.
To ensure the request is sent, we call the subscribe
method on the upload$
observable.
The $
(dollar) suffix is a convention used for observables in Angular. For further information on Angular's HTTP Client and its RxJS observables, please refer to the official documentation on HTTP server communication. In summary, invoking upload$.subscribe(...)
triggers the execution of the request and initiates its transmission. Additionally, we pass an object containing two handlers, next
and error
, to the upload$.subscribe()
method. These handlers allow us to update the upload status whenever they are invoked. By following these steps, we ensure the seamless delivery of the file to the target URL.
Here's the complete code for the component:
import { Component } from "@angular/core";
import { HttpClient } from "@angular/common/http";
import { throwError } from "rxjs";
@Component({
selector: "app-single-file-upload",
templateUrl: "./single-file-upload.component.html",
styleUrls: ["./single-file-upload.component.css"],
})
export class SingleFileUploadComponent {
status: "initial" | "uploading" | "success" | "fail" = "initial";
file: File | null = null;
constructor(private http: HttpClient) {}
ngOnInit(): void {}
onChange(event: any) {
const file: File = event.target.files[0];
if (file) {
this.status = "initial";
this.file = file;
}
}
onUpload() {
if (this.file) {
const formData = new FormData();
formData.append("file", this.file, this.file.name);
const upload$ = this.http.post("https://httpbin.org/post", formData);
this.status = "uploading";
upload$.subscribe({
next: () => {
this.status = "success";
},
error: (error: any) => {
this.status = "fail";
return throwError(() => error);
},
});
}
}
}
Here's an illustration of how these ties work together:
We select a file, view its details, and click the "Upload the file" button. Throughout the process, we can track the upload status. Upon pressing the "Upload the file" button, the status indicates that the file is currently being uploaded. Once the upload is complete, a "Done" message is displayed.
Now that we have covered the fundamentals of displaying a file input, presenting file details, and uploading a single file in Angular, let's explore the process of uploading multiple files.
We select a file, view its details and click the "Upload the file" button. All the time, we can follow the status of the upload. Once we press the "Upload the file" button, the status shows that the file is uploading. Once it finishes, we see the "Done" message show.
Great, now that we covered the basics on how to show file input, selected file details, and upload a single file in Angular - let's show how to upload multiple files.
Uploading Multiple Files in Angular
Most of the code will remain the same as in the previous section, where we demonstrated how to upload a single file. The only part that will change is the section where users can choose multiple files to upload and how we handle them using FormData
. Let's generate a separate component for multiple file uploads using the ng
CLI tool:
ng generate component multiple-file-upload
Next, we'll begin populating the markup in multiple-file-upload.component.html
:
<h2>Multiple File Upload</h2>
<input type="file" class="file-input" multiple (change)="onChange($event)" />
<div *ngIf="files.length" class="file-section">
<section *ngFor="let file of files">
File details:
<ul>
<li>Name: {{file.name}}</li>
<li>Type: {{file.type}}</li>
<li>Size: {{file.size}} bytes</li>
</ul>
</section>
<button (click)="onUpload()" class="upload-button">Upload the file</button>
<section [ngSwitch]="status">
<p *ngSwitchCase="'uploading'">⏳ Uploading...</p>
<p *ngSwitchCase="'success'">✅ Done!</p>
<p *ngSwitchCase="'fail'">❌ Error!</p>
<p *ngSwitchDefault>😶 Waiting to upload...</p>
</section>
</div>
The difference from the single file upload is including the multiple
attribute in the input
element. Additionally, we have an ngIf
section where we iterate over the selected files and display their information. Now, let's proceed to the component logic in multiple-file-upload.component.ts
:
import { Component } from "@angular/core";
import { HttpClient } from "@angular/common/http";
import { throwError } from "rxjs";
@Component({
selector: "app-multiple-file-upload",
templateUrl: "./multiple-file-upload.component.html",
styleUrls: ["./multiple-file-upload.component.css"],
})
export class MultipleFileUploadComponent {
status: "initial" | "uploading" | "success" | "fail" = "initial";
files: File[] = [];
constructor(private http: HttpClient) {}
ngOnInit(): void {}
onChange(event: any) {
const files = event.target.files;
if (files.length) {
this.status = "initial";
this.files = files;
}
}
onUpload() {
if (this.files.length) {
const formData = new FormData();
[...this.files].forEach((file) => {
formData.append("file", file, file.name);
});
const upload$ = this.http.post("https://httpbin.com/post", formData);
this.status = "uploading";
upload$.subscribe({
next: () => {
this.status = "success";
},
error: (error: any) => {
this.status = "fail";
return throwError(() => error);
},
});
}
}
}
In this code, we perform a similar process to the SingleFileUploaderComponent
. However, in this case, we save the selected files
and iterate over them, appending each file to the formData
object as follows:
[...this.files].forEach((file) => {
formData.append("file", file, file.name);
});
We deconstruct this.files
to convert the FileList
to an array, enabling us to utilize array methods like map
or forEach
.
Finally, replace the HTML tag of the component in app.component.html:
<!-- Other HTML -->
<h1>Angular File Upload</h1>
<app-multiple-file-upload></app-multiple-file-upload>
<!-- Other HTML -->
Now that we have everything set up for multiple file uploading let's see how it works in action:
That is all nice and dandy, but what if we simplify the implementation further? In the next section, we'll explore the Uploadcare File Uploader, which can make things even easier.
Uploading Files in Angular with Uploadcare File Uploader
Let's enhance the file uploading experience using the Uploadcare File Uploader. It is a highly customizable widget that allows managing files from various sources, storing them via external CDN, and optimizing them for performance needs.
To get started, you need to install the Uploadcare File Uploader package using the following command:
npm install @uploadcare/file-uploader
Additionally, you may need to add an option to tsconfig.json
to enable the default import from @uploadcare/file-uploader
. You can easily skip this step.
{
// ...
"compilerOptions": {
// ...
"allowSyntheticDefaultImports": true
// ...
}
// ...
}
Next, in the app.module.ts
file, add the CUSTOM_ELEMENTS_SCHEMA
so that we can use the @uploadcare/file-uploader
custom elements to render the uploader.
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from "@angular/core";
import { BrowserModule } from "@angular/platform-browser";
import { HttpClientModule } from "@angular/common/http";
import { AppComponent } from "./app.component";
// others imports...
@NgModule({
declarations: [
AppComponent,
// ...
],
imports: [BrowserModule, HttpClientModule],
providers: [],
bootstrap: [AppComponent],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class AppModule {}
Now, let's generate a component to handle the Uploadcare logic using the ng generate
command:
ng generate component uploadcare-upload
Inside uploadcare-upload.component.html
, add the necessary markup to render the uploader:
<h2>Uploadcare File Uploader</h2>
<div class="wrapper">
<uc-config
ctx-name="my-uploader"
pubkey="demopublickey"
></uc-config>
<uc-file-uploader-regular
ctx-name="my-uploader"
></uc-file-uploader-regular>
<uc-upload-ctx-provider
#ctxProvider
ctx-name="my-uploader"
></uc-upload-ctx-provider>
<div class="output">
<img *ngFor="let file of files" src="{{ file.cdnUrl }}" width="300" />
</div>
</div>
Here, we have tree custom elements: uc-config
, uc-file-uploader-regular
and uc-upload-ctx-provider
.
The uc-file-uploader-regular
element provides the UI for uploading files, while uc-data-output
allows us to track the uploaded and removed files in the UI via event handlers.
The uc-config
is used for File Uploader configuration.
The pubkey
attribute on uc-config
is where you should place your Uploadcare project API key. We use the demo public key in this example, but you should replace it with your public key.
Next, let's add the logic to uploadcare-upload.component.ts
:
import { Component, ElementRef, ViewChild } from '@angular/core';
import * as UC from "@uploadcare/file-uploader";
import { OutputFileEntry } from '@uploadcare/file-uploader';
UC.defineComponents(UC);
@Component({
selector: 'app-uploadcare-upload',
templateUrl: './uploadcare-upload.component.html',
styleUrls: ['./uploadcare-upload.component.css'],
})
export class UploadcareUploadComponent {
@ViewChild('ctxProvider', { static: true }) ctxProviderRef!: ElementRef<
InstanceType<UC.UploadCtxProvider>
>;
files: any[] = [];
ngOnInit() {
this.ctxProviderRef.nativeElement.addEventListener(
'change',
this.handleChangeEvent
);
}
ngOnDestroy() {
this.ctxProviderRef.nativeElement.removeEventListener('change', this.handleChangeEvent);
}
handleChangeEvent = (e: UC.EventMap['change']) => {
this.files = e.detail.allEntries.filter(f => f.status === 'success') as OutputFileEntry<'success'>[];
};
}
In this code, we import UC
from @uploadcare/file-uploader
and call defineComponents(UC)
to register the components used within UploadcareUploadComponent
.
Then we initiate (and destroy) the change
event handler — hanleUploadeEvent
function.
This function stores the uploaded files in the component so that we can render and display them to the user.
And, as usual, do not forget to update imports in app.module.ts and markup in app.component.html.
Now that we have added all the necessary logic let's see how it works in action:
And that's basically it!
Uploadcare takes care of the uploading process, accelerating it by using its uploading network (similar to a CDN), tracking the uploading progress, processing files on the fly, and even more.
You can check the documentation on File Uploader and see how to configure it and tailor it to your needs.
Conclusion
So, we covered various aspects of file uploading in Angular 2+:
- How to display a proper file input and show selected file details;
- How to work with FormData interface and Angular's HTTP client for uploading single or multiple files;
- How to show a simple uploading status indicator in the UI;
- How to iterate over FileList with a neat trick;
- Hnd how to efficiently use Uploadcare File Uploader to simplify file management.
Hope you enjoyed this guide. You can find all the source code in the GitHub and even try them out in the live playground.
Thanks for reading, and catch you in the next one.