Uploading files is a common feature in modern web applications, especially with user-generated content, image galleries, or document submissions. Angular makes it straightforward to handle file uploads using HttpClient
, and with some extra tools, we can enhance the experience with drag-and-drop functionality and a visual progress bar.
In this tutorial, you’ll learn how to build a complete file upload feature in Angular that includes:
-
A drag-and-drop upload area
-
File type and size validation
-
A progress bar to show upload status
-
A Node.js + Express backend to handle uploads
Whether you're building a content management system, a user dashboard, or a simple form with file input, this tutorial provides a solid foundation for integrating file uploads into your Angular app.
Technologies Used
-
Angular 20
-
Angular Material (optional for UI)
-
Node.js + Express (backend)
-
Multer (for handling file uploads on the server)
Setting Up the Angular Project
Let’s start by creating a new Angular project and installing the required dependencies.
1. Create the Angular App
Open your terminal and run:
ng new angular-file-upload
✔ Do you want to create a 'zoneless' application without zone.js (Developer
Preview)? No
✔ Which stylesheet format would you like to use? Sass (SCSS) [
https://sass-lang.com/documentation/syntax#scss ]
✔ Do you want to enable Server-Side Rendering (SSR) and Static Site Generation
(SSG/Prerendering)? No
cd angular-file-upload
2. Install Angular Material (Optional but useful for UI)
ng add @angular/material
✔ Choose a prebuilt theme name, or "custom" for a custom theme: Azure/Blue
[Preview: https://material.angular.dev?theme=azure-blue]
✔ Set up global Angular Material typography styles? No
Choose a theme and select 'yes' to include global typography and animations.
3. Install Dependencies
Install ngx-file-drop
for a simple drag-and-drop directive (optional but helpful):
npm install ngx-file-drop
Alternatively, we can build the drag-and-drop area manually with native dragenter
, dragover
, and drop
events—I'll show both options.
4. Serve the App
Run the Angular development server to make sure everything is working:
ng serve
Navigate to http://localhost:4200/
— you should see the default Angular welcome screen.
Creating the File Upload Component
In this section, we’ll create a standalone Angular component to handle file uploads. We'll include both traditional file input and drag-and-drop functionality.
1. Generate the Component
Use Angular CLI to create a standalone component:
ng generate component components/file-upload --standalone --flat --skip-tests
Update the file-upload.ts
:
import { CommonModule } from '@angular/common';
import { HttpClient } from '@angular/common/http';
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
@Component({
selector: 'app-file-upload',
imports: [CommonModule, FormsModule],
templateUrl: './file-upload.html',
styleUrl: './file-upload.scss'
})
export class FileUpload {
files: File[] = [];
uploading = false;
uploadProgress: number[] = [];
constructor(private http: HttpClient) { }
onFileSelected(event: Event) {
const input = event.target as HTMLInputElement;
if (input.files) {
this.files = Array.from(input.files);
}
}
onDrop(event: DragEvent) {
event.preventDefault();
if (event.dataTransfer?.files) {
this.files = Array.from(event.dataTransfer.files);
}
}
onDragOver(event: DragEvent) {
event.preventDefault();
}
uploadFiles() {
this.uploading = true;
this.uploadProgress = [];
this.http.post('http://localhost:3000/upload', formData, {
reportProgress: true,
observe: 'events'
})
.subscribe({
next: event => {
if (event.type === 1 && event.total) {
this.uploadProgress[index] = Math.round(100 * event.loaded / event.total);
}
}, error: err => {
console.error('Upload error:', err);
this.uploadProgress[index] = 0;
}
});
}
}
2. Add the HTML Template
Update file-upload.html
:
<div class="upload-container"
(drop)="onDrop($event)"
(dragover)="onDragOver($event)">
<p>Drag and drop files here, or click to select</p>
<input type="file" multiple (change)="onFileSelected($event)" />
</div>
<ul *ngIf="files.length">
<li *ngFor="let file of files; let i = index">
{{ file.name }} ({{ file.size / 1024 | number:'1.0-2' }} KB)
<div *ngIf="uploadProgress[i] >= 0">
<progress [value]="uploadProgress[i]" max="100"></progress>
{{ uploadProgress[i] }}%
</div>
</li>
</ul>
<button (click)="uploadFiles()" [disabled]="!files.length || uploading">Upload</button>
3. Add Styles (SCSS)
Update file-upload.scss
:
.upload-container {
border: 2px dashed #999;
padding: 2rem;
text-align: center;
cursor: pointer;
margin-bottom: 1rem;
transition: background-color 0.3s;
&:hover {
background-color: #f0f0f0;
}
input[type="file"] {
display: none;
}
}
ul {
list-style: none;
padding: 0;
li {
margin-bottom: 0.5rem;
}
progress {
width: 100%;
margin-top: 0.25rem;
}
}
4. Add the Component to App
Edit app.html
:
<app-file-upload></app-file-upload>
And HttpClient in app.config.ts
:
import { ApplicationConfig, provideBrowserGlobalErrorListeners, provideZoneChangeDetection } from '@angular/core';
import { provideRouter } from '@angular/router';
import { routes } from './app.routes';
import { provideHttpClient } from '@angular/common/http';
export const appConfig: ApplicationConfig = {
providers: [
provideBrowserGlobalErrorListeners(),
provideZoneChangeDetection({ eventCoalescing: true }),
provideRouter(routes),
provideHttpClient()
]
};
Add the FileUpload import in app.ts
:
import { Component } from '@angular/core';
import { FileUpload } from './components/file-upload';
@Component({
selector: 'app-root',
imports: [FileUpload],
templateUrl: './app.html',
styleUrl: './app.scss'
})
export class App {
protected title = 'angular-file-upload';
}
Backend Setup with Node.js and Express
We’ll create a simple Express server that handles file uploads using multer
, a popular middleware for handling multipart/form-data
.
1. Initialize the Backend Project
Create a new directory and initialize a Node.js project:
mkdir backend
cd backend
npm init -y
2. Install Required Packages
npm install express multer cors
-
express
: Web framework -
multer
: Middleware for file uploads -
cors
: Enables cross-origin requests from the Angular frontend
3. Create the Express Server
Create a file named server.js
:
const express = require('express');
const multer = require('multer');
const cors = require('cors');
const path = require('path');
const fs = require('fs');
const app = express();
const PORT = 3000;
// Enable CORS
app.use(cors());
app.use(express.static('uploads'));
// Ensure upload directory exists
const uploadDir = path.join(__dirname, 'uploads');
if (!fs.existsSync(uploadDir)) {
fs.mkdirSync(uploadDir);
}
// Set up multer storage
const storage = multer.diskStorage({
destination: (req, file, cb) => cb(null, 'uploads/'),
filename: (req, file, cb) => cb(null, Date.now() + '-' + file.originalname)
});
const upload = multer({ storage });
// File upload endpoint
app.post('/upload', upload.single('file'), (req, res) => {
if (!req.file) return res.status(400).send('No file uploaded.');
res.status(200).json({ filename: req.file.filename });
});
// Start server
app.listen(PORT, () => {
console.log(`Server started at http://localhost:${PORT}`);
});
4. Run the Backend
From the backend/
directory:
node server.js
The backend server will run at http://localhost:3000
.
5. Update Angular API Endpoint
In your Angular FileUpload component
, change the upload URL to match the backend:
this.http.post('http://localhost:3000/upload', formData, {
reportProgress: true,
observe: 'events'
})
You should now be able to:
-
Select or drag files into the upload area
-
See file details and upload progress
-
Successfully upload files to the backend
-
View the uploaded files in the
backend/uploads/
directory
Adding Image Previews and File Validation
In this section, we’ll:
-
Show a thumbnail preview for image files
-
Validate file size and type before uploading
1. Enhance the Component Logic
Update the FileUploadComponent
to store file previews and add validation:
file-upload.ts
import { CommonModule } from '@angular/common';
import { HttpClient, HttpEventType } from '@angular/common/http';
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
@Component({
selector: 'app-file-upload',
imports: [CommonModule, FormsModule],
templateUrl: './file-upload.html',
styleUrl: './file-upload.scss'
})
export class FileUpload {
files: File[] = [];
previews: string[] = [];
uploadProgress: number[] = [];
errorMessages: string[] = [];
uploading = false;
constructor(private http: HttpClient) { }
onFileSelected(event: Event) {
const input = event.target as HTMLInputElement;
if (input.files) {
this.handleFiles(Array.from(input.files));
}
}
onDrop(event: DragEvent) {
event.preventDefault();
if (event.dataTransfer?.files) {
this.handleFiles(Array.from(event.dataTransfer.files));
}
}
onDragOver(event: DragEvent) {
event.preventDefault();
}
handleFiles(selectedFiles: File[]) {
this.files = [];
this.previews = [];
this.errorMessages = [];
selectedFiles.forEach(file => {
if (!file.type.startsWith('image/')) {
this.errorMessages.push(`${file.name} is not an image.`);
return;
}
if (file.size > 5 * 1024 * 1024) { // 5MB
this.errorMessages.push(`${file.name} exceeds 5MB limit.`);
return;
}
this.files.push(file);
const reader = new FileReader();
reader.onload = (e: ProgressEvent<FileReader>) => {
if (e.target?.result) {
this.previews.push(e.target.result as string);
}
};
reader.readAsDataURL(file);
});
}
uploadFiles() {
this.uploading = true;
this.uploadProgress = [];
this.files.forEach((file, index) => {
const formData = new FormData();
formData.append('file', file);
this.http.post('http://localhost:3000/upload', formData, {
reportProgress: true,
observe: 'events'
}).subscribe({
next: event => {
if (event.type === HttpEventType.UploadProgress && event.total) {
this.uploadProgress[index] = Math.round(100 * event.loaded / event.total);
} else if (event.type === HttpEventType.Response) {
console.log('Upload complete:', event.body);
}
}, error: error => {
console.error('Upload error:', error);
this.uploadProgress[index] = 0;
}
});
});
}
}
2. Update the Template
file-upload.html
<div class="upload-container"
(drop)="onDrop($event)"
(dragover)="onDragOver($event)">
<p>Drag and drop image files here, or click to select</p>
<input type="file" multiple accept="image/*" (change)="onFileSelected($event)" />
</div>
<div *ngIf="errorMessages.length">
<ul class="errors">
<li *ngFor="let error of errorMessages">{{ error }}</li>
</ul>
</div>
<ul *ngIf="files.length">
<li *ngFor="let file of files; let i = index">
<img *ngIf="previews[i]" [src]="previews[i]" alt="Preview" width="100" />
<div>
{{ file.name }} ({{ file.size / 1024 | number:'1.0-2' }} KB)
</div>
<div *ngIf="uploadProgress[i] >= 0">
<progress [value]="uploadProgress[i]" max="100"></progress>
{{ uploadProgress[i] }}%
</div>
</li>
</ul>
<button (click)="uploadFiles()" [disabled]="!files.length || uploading">Upload</button>
3. Style the Previews
file-upload.scss
.upload-container {
border: 2px dashed #999;
padding: 2rem;
text-align: center;
cursor: pointer;
margin-bottom: 1rem;
input[type="file"] {
display: none;
}
&:hover {
background-color: #f8f8f8;
}
}
ul {
list-style: none;
padding: 0;
li {
display: flex;
align-items: center;
margin-bottom: 1rem;
gap: 1rem;
img {
border-radius: 8px;
border: 1px solid #ddd;
}
}
}
.errors {
color: red;
margin-bottom: 1rem;
}
Now your file upload component supports:
-
Drag-and-drop or manual file selection
-
Preview of selected images
-
Validation for image type and file size
-
Progress bar during upload
Conclusion and Deployment Tips
In this tutorial, you’ve learned how to build a complete file upload feature in Angular with drag-and-drop support, image previews, file validation, and progress tracking. You also created a simple Node.js + Express backend to handle and store the uploaded files.
🔄 What We Covered:
-
Setting up an Angular 20 project with standalone components
-
Creating a drag-and-drop file upload interface
-
Showing image previews and validating file types/sizes
-
Using
HttpClient
to track upload progress with a visual bar -
Creating a backend with Node.js, Express, and
multer
for file handling
Deployment Tips
To deploy this file upload feature to production, consider the following:
🔒 Security Best Practices
-
Sanitize and validate uploaded files on the backend
-
Use a file type whitelist (e.g., only allow images, PDFs)
-
Rename files to avoid collisions or malicious file names
-
Store uploads outside the public web root if they’re sensitive
🌐 Cross-Origin and Hosting
-
Set appropriate CORS headers for cross-origin API calls
-
Serve the frontend (Angular) and backend (Node.js) from the same domain or via a reverse proxy (like Nginx)
-
Use a cloud service (e.g., AWS S3, Firebase Storage) for storing and serving uploaded files at scale
📁 Storing Files in the Cloud (Optional)
For production apps, consider offloading storage to a cloud bucket (Amazon S3, Google Cloud Storage) and returning the file URL after upload.
You can get the full working source code on our GitHub.
If you don’t want to waste your time designing your front-end or your budget to spend by hiring a web designer, then Angular Templates is the best place to go. So, speed up your front-end web development with premium Angular templates. Choose your template for your front-end project here.
That's just the basics. If you need more deep learning about Angular, you can take the following cheap course:
- Angular Crash Course - Learn Angular And Google Firebase
- Real-time Communication using Socket. IO 3.x and Angular 11.x
- Angular Progressive Web Apps (PWA) MasterClass & FREE E-Book
- Enterprise-Scale Web Apps with Angular
- Angular Forms In Depth (Angular 20)
- Microservicios Spring Boot y Angular MySql Postgres
-
Angular Developer Interview Questions Practice Test Quiz
Happy Coding!