Angular 16 Drag and Drop File Uploading with Multer Tutorial

Last Updated on by in Angular
Angular 16 drag and drop file upload tutorial; In this article; we will learn to upload multiple image files in MongoDB database using Node and Express.In this tutorial we will create a basic Angular app in which we will use to create a custom directive to build Angular drag and drop functionality.

Tutorial Objective

  • Building Angular drag and drop file uploading Layout with HTML/CSS
  • Creating a Node server to upload image files
  • Creating Custom Drag and Drop directive
  • Using Multer for Multiple file uploading
  • Multiple files uploading with progress bar

Angular 16 Drag and Drop File Uploading Example

  • Install Angular App
  • Build Node/Express Server
  • Build File Upload REST API with Multer & Express
  • Configure Node/Express Server
  • Create Angular Drag and Drop File Uploading Directive
  • Create Angular Service
  • Create Drag and Drop File Upload Component

Install Angular App

Let’s start by installing basic Angular app, run the following command:

ng new angular-dragdrop-fileupload

Then, navigate to the newly created Angular project:

cd angular-dragdrop-fileupload

Next, create Angular component for drag and drop file upload.

ng g c drag-drop

Next, run command to install Bootstrap.

npm install bootstrap

Add the Bootstrap CSS in package.json file.

"styles": [
          "node_modules/bootstrap/dist/css/bootstrap.min.css",
          "src/styles.scss"
         ]

No Property Access From Index Signature

To resolve error:

Property ‘xxxName’ comes from an index signature, so it must be accessed with [‘xxxName’]

This setting makes sure profound consistency between accessing a field via the “dot” (obj.key) syntax, and “indexed” (obj["key"]) and the way which the property is declared in the type.

Without this flag, TypeScript will allow you to use the dot syntax to access fields which are not defined:

Make sure to set noPropertyAccessFromIndexSignature property to false under compilerOptions in tsconfig.json file:

"compilerOptions": {
 ...
 ...
   "strict": false,
   "noPropertyAccessFromIndexSignature": false,
 ...
 ...
}

Build Node/Express Server

Build a node server with express js to store the uploaded files on the MongoDB database. We will use Multer to store the image files along with other NPM packages.

Run the command from Angular project’s root to generate backend folder:

mkdir backend && cd backend

Create separate package.json for node server.

npm init -y

Run command to install required NPM packages.

npm install body-parser cors express mongoose multer --save

Also, install nodemon NPM module, it starts the server whenever any change occurs in server code.

npm install nodemon --save-dev

Define Mongoose Schema

Create models folder inside the backend directory, then create a file User.js and place the following code inside of it.

const mongoose = require('mongoose');
const Schema = mongoose.Schema;

// Define Schema
let userSchema = new Schema({
  _id: mongoose.Schema.Types.ObjectId,
  avatar: {
    type: Array
  },
}, {
  collection: 'users'
})

module.exports = mongoose.model('User', userSchema)

Build File Upload REST API with Multer & Express

Let’s first create a folder and name it public inside the backend folder. Here, in this folder where we will store all the uploaded files.

Run the command from the backend folder’s root.

mkdir public

Create a routes folder inside the backend folder. Create a file user.routes.js inside of it.

By using express, multer and mongoose modules we will learn to build REST API for storing multiple files in MongoDB database.

Add the given below code inside the user.routes.js.

let express = require('express'),
  multer = require('multer'),
  mongoose = require('mongoose'),
  router = express.Router();

// Multer File upload settings
const DIR = './public/';

const storage = multer.diskStorage({
  destination: (req, file, cb) => {
    cb(null, DIR);
  },
  filename: (req, file, cb) => {
    const fileName = file.originalname.toLowerCase().split(' ').join('-');
    cb(null, fileName)
  }
});

var upload = multer({
  storage: storage,
  // limits: {
  //   fileSize: 1024 * 1024 * 5
  // },
  fileFilter: (req, file, cb) => {
    if (file.mimetype == "image/png" || file.mimetype == "image/jpg" || file.mimetype == "image/jpeg") {
      cb(null, true);
    } else {
      cb(null, false);
      return cb(new Error('Only .png, .jpg and .jpeg format allowed!'));
    }
  }
});

// User model
let User = require('../models/User');

router.post('/create-user', upload.array('avatar', 6), (req, res, next) => {
  const reqFiles = []
  const url = req.protocol + '://' + req.get('host')
  for (var i = 0; i < req.files.length; i++) {
    reqFiles.push(url + '/public/' + req.files[i].filename)
  }

  const user = new User({
    _id: new mongoose.Types.ObjectId(),
    avatar: reqFiles
  });
  user.save().then(result => {
    console.log(result);
    res.status(201).json({
      message: "Done upload!",
      userCreated: {
        _id: result._id,
        avatar: result.avatar
      }
    })
  }).catch(err => {
    console.log(err),
      res.status(500).json({
        error: err
      });
  })
})

router.get("/", (req, res, next) => {
  User.find().then(data => {
    res.status(200).json({
      message: "User list retrieved successfully!",
      users: data
    });
  });
});

module.exports = router;

We used Multer’s upload.array() method to upload the multiple files on the server.

This method takes 2 arguments, first we pass the file name which we will be using to store the file values.

Second parameter relates to the number of file we can upload at a time. Then we defined the reqFiles array here we will store the uploaded file’s path with full URL.

Configure Node/Express Server

Create index.js file inside the backend folder. Then, place the following code inside the index.js file.

const express = require("express");
const mongoose = require("mongoose");
const cors = require("cors");
const bodyParser = require("body-parser");

// Routes to Handle Request
const userRoute = require("./routes/user.routes");

// Connecting MongoDB
async function mongoDbConnection() {
  await mongoose.connect(
    "mongodb://127.0.0.1:27017/test",
    {
      useNewUrlParser: true,
      useUnifiedTopology: true,
    },
    6000
  );
}
mongoDbConnection().then(() => {
  console.log("MongoDB successfully connected.");
}),
  (err) => {
    console.log("Could not connected to database : " + err);
  };

// Setup Express.js
const app = express();
app.use(bodyParser.json());
app.use(
  bodyParser.urlencoded({
    extended: false,
  })
);
app.use(cors());

// Make Images "Uploads" Folder Publicly Available
app.use("/public", express.static("public"));

// API Route
app.use("/api", userRoute);

const port = process.env.PORT || 4000;
const server = app.listen(port, () => {
  console.log("Connected to port " + port);
});

// Error
app.use((req, res, next) => {
  // Error goes via `next()` method
  setImmediate(() => {
    next(new Error("Something went wrong"));
  });
});

app.use(function (err, req, res, next) {
  console.error(err.message);
  if (!err.statusCode) err.statusCode = 500;
  res.status(err.statusCode).send(err.message);
});

Start Node Server

Make sure to setup and start the MongoDB community and MongoDB Compass GUI database in your local system.

Also, pass the MongoDB database URL to the mongoose.connect(‘…’) method in the backend/index.js file.

Then, open another terminal and run following command.

npx nodemon server

Next, you can checkout node server running on the following Url: http://localhost:4000/api

API Method URL
GET http://localhost:4000/api
POST /api/create-user

You can test out Angular file uploading REST APIs Url in Postmen:

Angular Drag and Drop File UploadA

Create Angular Drag and Drop File Uploading Directive

In this step, we will create HostBinding and HostListeners to manage the drag and drop functionality for Angular file upload task.

Run command to create directive in Angular project.

ng g d drag-drop-file-upload

In the drag-drop-file-upload.directive.ts file, we will define 3 HostListners such as Dragover, Dragleave and Drop along with HostBinding for background-color.

import {
  Directive,
  EventEmitter,
  Output,
  HostListener,
  HostBinding,
} from '@angular/core';

@Directive({
  selector: '[appDragDropFileUpload]',
})
export class DragDropFileUploadDirective {
  @Output() fileDropped = new EventEmitter<any>();

  @HostBinding('style.background-color') private background = '#ffffff';

  // Dragover Event
  @HostListener('dragover', ['$event']) dragOver(event: any) {
    event.preventDefault();
    event.stopPropagation();
    this.background = '#e2eefd';
  }

  // Dragleave Event
  @HostListener('dragleave', ['$event']) public dragLeave(event: any) {
    event.preventDefault();
    event.stopPropagation();
    this.background = '#ffffff';
  }

  // Drop Event
  @HostListener('drop', ['$event']) public drop(event: any) {
    event.preventDefault();
    event.stopPropagation();
    this.background = '#ffffff';
    const files = event.dataTransfer.files;
    if (files.length > 0) {
      this.fileDropped.emit(files);
    }
  }
}

Create Angular Service

We need to create Angular service, here in this file we will create a method in which we will make HTTP POST request to store the uploaded files in the mongoDB database.

Use JavaScript’s FormData() method to store the Reactive Forms value in the database via Reactive Form. To track the file upload progress define the reportProgress and observe values in Http method.

First, create service file using given command in angular project.

ng g s drag-drop

Next, open the src/app/drag-drop.service.ts file.

import { Injectable } from '@angular/core';
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';
import { HttpErrorResponse, HttpClient } from '@angular/common/http';

@Injectable({
  providedIn: 'root'
})

export class DragdropService {

  constructor(private http: HttpClient) { }

  addFiles(images: File) {
    var arr = []
    var formData = new FormData();
    arr.push(images);

    arr[0].forEach((item, i) => {
      formData.append('avatar', arr[0][i]);
    })

    return this.http.post('http://localhost:4000/api/create-user', formData, {
      reportProgress: true,
      observe: 'events'
    }).pipe(
      catchError(this.errorMgmt)
    )
  }

  errorMgmt(error: HttpErrorResponse) {
    let errorMessage = '';
    if (error.error instanceof ErrorEvent) {
      // Get client-side error
      errorMessage = error.error.message;
    } else {
      // Get server-side error
      errorMessage = `Error Code: ${error.status}\nMessage: ${error.message}`;
    }
    console.log(errorMessage);
    return throwError(errorMessage);
  }

}

Create Drag and Drop File Upload Component

Now, we will create the layout for drag and drop file upload component. In this tutorial we will be using Reactive Forms to store the files and Node server to store the files into the mongoDB database.

Import ReactiveFormsModule and HttpClientModule in app.module.ts file to enable the service.

import { ReactiveFormsModule } from '@angular/forms';
import {HttpClientModule} from '@angular/common/http';

@NgModule({
  declarations: [...],
  imports: [
    ReactiveFormsModule,
    HttpClientModule
  ],
  bootstrap: [...]
})

export class AppModule { }

Next, add the code inside the app/drag-drop.component.html file.

<div class="container fileUploadWrapper">
  <form [formGroup]="form">
    <div class="row">
      <!-- Progress Bar -->
      <div class="col-md-12" *ngIf="progress">
        <div class="progress form-group">
          <div
            class="progress-bar progress-bar-striped bg-success"
            role="progressbar"
            [style.width.%]="progress"
          ></div>
        </div>
      </div>

      <div class="col-md-12">
        <div
          class="fileupload"
          appDragDropFileUpload
          (click)="fileField.click()"
          (fileDropped)="upload($event)"
        >
          <span class="ddinfo">Choose a file or drag here</span>
          <input
            type="file"
            name="avatars"
            #fileField
            (change)="upload($any($event).target.files)"
            hidden
            multiple
          />
        </div>
      </div>

      <div class="col-md-12">
        <div class="image-list" *ngFor="let file of fileArr; let i = index">
          <div class="profile">
            <img [src]="sanitize(file['url'])" alt="" />
          </div>
          <p>{{ file.item.name }}</p>
        </div>
        <p class="message">{{ msg }}</p>
      </div>
    </div>
  </form>
</div>

Apply design to Angular drag and drop file uploading component, navigate to styles.scss and paste the following code.

* {
    box-sizing: border-box;
}

body {
    margin: 0;
    padding: 25px 0 0 0;
    background: #291464;
}

.container {
    margin-top: 30px;
    max-width: 500px;
}

.progress {
    margin-bottom: 30px;
}

.fileupload {
    background-image: url("./assets/upload-icon.png");
    background-repeat: no-repeat;
    background-size: 100px;
    background-position: center;
    background-color: #ffffff;
    height: 200px;
    width: 100%;
    cursor: pointer;
    /* border: 2px dashed #0f68ff; */
    border-radius: 6px;
    margin-bottom: 25px;
    background-position: center 28px;
}

.ddinfo {
    display: block;
    text-align: center;
    padding-top: 130px;
    color: #a0a1a2;
}

.image-list {
    display: flex;
    width: 100%;
    background: #C2DFFC;
    border: 1px solid;
    border-radius: 3px;
    padding: 10px 10px 10px 15px;
    margin-bottom: 10px;
}

.image-list p {
    line-height: normal;
    padding: 0;
    margin: 0 0 0 14px;
    display: inline-block;
    position: relative;
    top: -2px;
    color: #150938;
    font-size: 14px;
}

.message {
    text-align: center;
    color: #C2DFFC;
}

.remove {
    background: transparent;
    border: none;
    cursor: pointer;
}

.profile {
    width: 40px;
    height: 40px;
    overflow: hidden;
    border-radius: 4px;
    display: inline-block;
}

.profile img {
    width: 100%;
}

.remove img {
    width: 15px;
    position: relative;
    top: -2px;
}

.fileUploadWrapper .card-body {
    max-height: 330px;
    overflow: hidden;
    overflow-y: auto;
}

@media(max-width: 767px) {
    .container {
        width: 280px;
        margin: 20px auto 100px;
    }
}

in order to remove strict type check warning in typescript set “strict”: false under compilerOptions in tsconfig.json file.

Paste the following code in app/drag-drop.component.ts file:

import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup } from '@angular/forms';
import { DragdropService } from '../drag-drop.service';
import { HttpEvent, HttpEventType } from '@angular/common/http';
import { DomSanitizer } from '@angular/platform-browser';

@Component({
  selector: 'app-drag-drop',
  templateUrl: './drag-drop.component.html',
  styleUrls: ['./drag-drop.component.scss'],
})
export class DragDropComponent implements OnInit {
  fileArr = [];
  imgArr = [];
  fileObj = [];
  form: FormGroup;
  msg: string;
  progress: number = 0;

  constructor(
    public fb: FormBuilder,
    private sanitizer: DomSanitizer,
    public dragdropService: DragdropService
  ) {
    this.form = this.fb.group({
      avatar: [null],
    });
  }

  ngOnInit() {}

  upload(e) {
    const fileListAsArray = Array.from(e);
    fileListAsArray.forEach((item, i) => {
      const file = e as HTMLInputElement;
      const url = URL.createObjectURL(file[i]);
      this.imgArr.push(url);
      this.fileArr.push({ item, url: url });
    });

    this.fileArr.forEach((item) => {
      this.fileObj.push(item.item);
    });

    // Set files form control
    this.form.patchValue({
      avatar: this.fileObj,
    });

    this.form.get('avatar').updateValueAndValidity();

    // Upload to server
    this.dragdropService
      .addFiles(this.form.value.avatar)
      .subscribe((event: HttpEvent<any>) => {
        switch (event.type) {
          case HttpEventType.Sent:
            console.log('Request has been made!');
            break;
          case HttpEventType.ResponseHeader:
            console.log('Response header has been received!');
            break;
          case HttpEventType.UploadProgress:
            this.progress = Math.round((event.loaded / event.total) * 100);
            console.log(`Uploaded! ${this.progress}%`);
            break;
          case HttpEventType.Response:
            console.log('File uploaded successfully!', event.body);
            setTimeout(() => {
              this.progress = 0;
              this.fileArr = [];
              this.fileObj = [];
              this.msg = 'File uploaded successfully!';
            }, 3000);
        }
      });
  }

  // Clean Url
  sanitize(url: string) {
    return this.sanitizer.bypassSecurityTrustUrl(url);
  }
}

Next, go to app.component.html file and remove the current code and add the given component tag into it.

<app-drag-drop></app-drag-drop>

Run command to start your Angular project.

ng serve --open

Conclusion

Multer is a well-known middleware for handling multipart/form-data in Node.js, mainly used with Express.js, which is a popular web application framework.

It allows you to handle file uploads from HTML forms or other clients that support multipart requests.

When a client submits a form with a file upload, Multer intercepts the request before it reaches your route handlers and handles the processing of the uploaded files.

It parses the form data, extracts the files, and provides you with an easy-to-use interface to work with the uploaded files.

Finally, Angular Drag and Drop multiple files uploading tutorial with MongoDB & Multer is completed. You can use the given link to download the sample project.

Git Repo

Digamber - Author positronX.io

Hi, I'm Digamber Singh, a New Delhi-based full-stack developer, tech author, and open-source contributor with 10+ years' experience in HTML, CSS, JavaScript, PHP, and WordPress.