Angular 8 Drag and Drop File Uploading with MongoDB & Multer

By Digamber Rawat Last updated on
In this Angular 8 drag and drop file uploading tutorial, 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 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

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.css"
         ]

Run command to start your Angular project.

ng serve --open

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

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 MongoDB Database

Create database folder inside the backend folder and also create a file backend/database/db.js in it.

module.exports = {
  db: 'mongodb://localhost:27017/meanfileupload'
}

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. Here we ill import express, multer and mongoose NPM modules. By using these services we will 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 server.js file inside the backend folder. Then, place the following code inside the server.js file.

let express = require('express'),
  mongoose = require('mongoose'),
  cors = require('cors'),
  bodyParser = require('body-parser'),
  dbConfig = require('./database/db');


// Routes to Handle Request
const userRoute = require('../backend/routes/user.route')


// MongoDB Setup
mongoose.Promise = global.Promise;
mongoose.connect(dbConfig.db, {
  useNewUrlParser: true
}).then(() => {
  console.log('Database sucessfully connected')
},
  error => {
    console.log('Database could not be connected: ' + error)
  }
)

// 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

Open terminal and run command to start the MongoDB server.

mongod

Then, open another terminal and run following command.

nodemon server.js

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 8 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) {
    event.preventDefault();
    event.stopPropagation();
    this.background = '#e2eefd';
  }

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

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

}

Create Angular 8 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.

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 in app.module.ts file to enable the service.

import { ReactiveFormsModule } from '@angular/forms';

@NgModule({
  declarations: [...],
  imports: [
    ReactiveFormsModule
  ],
  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($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.css 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;
    }
}

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

import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, FormArray } from "@angular/forms";
import { DragdropService } from "../dragdrop.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.css']
})

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);
  }

}

Conclusion

Finally, Angular 8 Drag and Drop multiple files uploading tutorial with MongoDB & Multer is over.

Git Repo

Digamber Rawat
Digamber Rawat

Full stack developer with a passion for UI/UX design. I create beautiful and useful digital products to solve people’s problem and make their life easy.

Hire Me