Angular File Upload Tutorial with Drag-and-Drop and Progress Bar

by Didin J. on Jun 28, 2025 Angular File Upload Tutorial with Drag-and-Drop and Progress Bar

Learn how to implement file uploads in Angular with drag-and-drop support, progress bar, and preview using modern Angular features.

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.

Angular File Upload Tutorial with Drag-and-Drop and Progress Bar - angular home


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

Angular File Upload Tutorial with Drag-and-Drop and Progress Bar - pre drag drop

Angular File Upload Tutorial with Drag-and-Drop and Progress Bar - after drop

Angular File Upload Tutorial with Drag-and-Drop and Progress Bar - success upload


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:

Happy Coding!