Angular 8 & Express File Upload Tutorial with Reactive Forms

By Digamber Rawat Last updated on
In this Angular 8 MEAN Stack file upload tutorial, we are going to learn how to upload the image from Angular’s frontend using Reactive Forms. And store the image on the MongoDB database by consuming the REST APIs built with Node.js and Express.js.

We will create a basic Angular app and set up a Node.js backend using Node, Express.js, and MongoDB. Then, we will take the help of Multer NPM module to upload and store the files in Node server.

Multer is a Node js middleware, and it helps in uploading the files on the server. Multer makes file uploading easy by adding a body object or a file object to the request object. This NPM module is quite popular and has been downloaded 618,911 times at the time of creating this tutorial exclusively for file uploading purposes.

Tutorial Objective

We will cover the following topics in this Angular image upload tutorial:

  • Setting up MEAN Stack backend.
  • Building REST APIs with Express.js for managing file upload.
  • Working with Angular Reactive Forms and FormData object.
  • Using Multer to store and upload the image files in the Node server.
  • Set File Upload Limit using Multer NPM module.
  • File Upload with Progress Bar using HttpEvent & HttpEventType API.
  • Setting up an Angular app.
  • Creating image upload preview in Angular.
  • Upload specific image type on node server using Multer MIME-type validation

Install & Set up Angular Project

Run following command to install basic Angular project:

ng new mean-stack-file-upload

# ? Would you like to add Angular routing? Yes
# ? Which stylesheet format would you like to use? CSS

Get inside the project folder:

cd mean-stack-file-upload

We will also install Bootstrap 4 by running the following command.

npm install bootstrap

Go to angular.json file and inject the bootstrap style sheet inside the styles array like given below.

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

Run given below commands from your terminal to create Angular components for managing file uploading task in a MEAN stack app.

ng g c create-user
ng g c users-list

Now, your basic Angular project is ready to be served, run the below command to start the Angular app.

ng serve --open

Enable Angular Routing

Let’s enable routing in our Angular app, go to app-routing.module.ts file and add the following code inside of it.

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';

import { Routes, RouterModule } from '@angular/router';
import { CreateUserComponent } from './create-user/create-user.component';
import { UsersListComponent } from './users-list/users-list.component';

const routes: Routes = [
  { path: '', pathMatch: 'full', redirectTo: 'add-user' },
  { path: 'add-user', component: CreateUserComponent },
  { path: 'users-list', component: UsersListComponent }
]

@NgModule({
  declarations: [],
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})

export class AppRoutingModule { }

Now, visit app.component.html file and add the following code to enable routing service in our Angular file uploading demo app.

<ul>
  <li>
    <a routerLinkActive="active" routerLink="/add-user">Create User</a>
  </li>
  <li>
    <a routerLinkActive="active" routerLink="/users-list">Users</a>
  </li>
</ul>

<router-outlet></router-outlet>

Set up Node Server

We will set up a separate node server for managing image uploading in our Angular application. Create a new folder in the root of our Angular application, name it backend.

Run the following command from the root of your Angular app to generate backend folder:

mkdir backend && cd backend

We will be using separate package.json file to manage our node server.

npm init

Install required dependencies to build node and express file uploading server:

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

Then, install nodemon NPM module, it automatically restarts the node application when it detects the change in the server files.

npm install nodemon --save-dev

Set up MongoDB Database

Next, we will set up a mongoDB database connection. In mongoDB NoSQL database, we will store our files or images.

Create a folder in backend folder and name it database also create a file and name it db.js. Here we will define mongoDB database configuration.

Go to backend/database/db.js file and place the following code in it.

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

Set up Mongoose Schema

In the next step, we will declare the Mongoose Schema for our Angular 8 MEAN stack file uploading tutorial.

Create a folder name it models inside the backend folder. Then creates a file and name it User.js and include the following code in it.

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


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

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

Build Express Routes for File Uploading using Multer

Now we will build Express REST API routes for file uploading using Multer. Create a new folder inside backend folder and name it routes, inside this folder also create a new file and name it user.route.js.

Create a new folder by the name of public in the backend folder. When a user makes the HTTP POST request via Express.js route from Angular service then in this folder an image will be stored.

Go to backend/routes/user.route.js file and add the following code.

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


// Multer Mime Type Validation
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');


// POST User
router.post('/create-user', upload.single('avatar'), (req, res, next) => {
  const url = req.protocol + '://' + req.get('host')
  const user = new User({
    _id: new mongoose.Types.ObjectId(),
    name: req.body.name,
    avatar: url + '/public/' + req.file.filename
  });
  user.save().then(result => {
    console.log(result);
    res.status(201).json({
      message: "User registered successfully!",
      userCreated: {
        _id: result._id,
        name: result.name,
        avatar: result.avatar
      }
    })
  }).catch(err => {
    console.log(err),
      res.status(500).json({
        error: err
      });
  })
})


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


// GET User
router.get("/:id", (req, res, next) => {
  User.findById(req.params.id).then(data => {
    if (data) {
      res.status(200).json(post);
    } else {
      res.status(404).json({
        message: "User not found!"
      });
    }
  });
});


module.exports = router;
  • We imported the express, multer, and mongoose NPM modules to make the REST APIs routes.
  • Declare the Dir variable and define the public directory path, where all the images or files will be stored.
  • We are using multer disktorage middleware. In this method, we used destination and filename methods. Multer’s destination method stores files in the public folder. The filename method takes req, file, cb arguments, and helps in defining the name of the file.
  • Setting up file upload limit and file type validation is easy by using Multer NPM Module. In the above example, we used limits key to defining fileSize, and file upload limit is upto 5mb.
  • Multer’s fileFilter method allows MIME-type validation, we implemented specific file type validation in which we can upload images with particular file types such as .png, .jpg, and .jpeg format.
  • We created the express route by the name of /create-user, this middleware takes req, res, next arguments. We can define the Multer’s upload object directly with the express route. Whenever this API is being called, then the file will be saved in the public directory.

Final Node Server Configuration

Next, we will create server.js file in the backend folder’s root. Here we will define the server configurations such as mongoDB database, Express routes, Express server setup, Express Static Path, Server PORT and Error handling methods:

Go to backend/server.js file and add the following code inside of it.

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 "public" Folder Publicly Available
app.use('/public', express.static('public'));


// API Route
app.use('/api', userRoute)


// Error favicon.ico
app.get('/favicon.ico', (req, res) => res.status(204));


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

We used the express.static() method. This method is essential and makes a public folder to publicly available. So when we access files from Angular’s frontend, then we can access these files easily.

Start the MEAN Stack Server

  • `cd backend` to enter into the backend folder
  • `nodemon server` to start the nodemon server
  • `mongod` to start the mongoDB shell

You can check out uploaded data on the following URL: http://localhost:4000/api

Create Angular File Uploading Service

In this step, we will create an Angular 8 service to handle node server REST APIs for our file upload tutorial.

But before that create a folder and name it shared inside Angular’s src/app folder.

Inside the src/app/shared folder create user.ts class, and define the following code inside of it.

export class User {
    id: string;
    name: string;
    avatar: string;
}

Next, we will import the HttpClientModule service in app.module.ts file:

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

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

export class AppModule { }

Then, go to src/app/shared folder and create file-upload.service.ts file, and place the given below code in it.

import { Injectable } from '@angular/core';
import { User } from './user';
import { Observable, throwError } from 'rxjs';
import { HttpHeaders, HttpErrorResponse, HttpClient } from '@angular/common/http';

@Injectable({
  providedIn: 'root'
})

export class FileUploadService {

  baseURL = "http://localhost:4000/api";
  headers = new HttpHeaders().set('Content-Type', 'application/json');

  constructor(private http: HttpClient) { }

  // Get Users
  getUsers() {
    return this.http.get(this.baseURL)
  }

  // Create User
  addUser(name: string, profileImage: File): Observable<any> {
    var formData: any = new FormData();
    formData.append("name", name);
    formData.append("avatar", profileImage);

    return this.http.post<User>(`${this.baseURL}/create-user`, formData, {
      reportProgress: true,
      observe: 'events'
    })
  }

  // Error handling 
  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);
  }

}

We created the Angular service for the file uploading task. In this service file, we defined the methods such as getUsers to retrieve the user’s data from the mongoDB database and addUser method to upload the user data such as name and profile image to the mongoDB database. In order to use this service we have to import this service and inject inside the component’s constructor method in Angular’s component.

To upload the file or image in the mongoDB database via node server, we are using the FormData object. The FormData interface provides a way to easily construct a set of key/value pairs describing form fields and their values. We passed the name and profileImage as an argument. Then we declared the FormData object and created a formData instance from it. After that, we used the formData.append() method to inject the values retrieved from the Reactive form.

Next, we are using the Http POST method to send the user data to the server. We passed the two arguments in the POST method; first, we passed the REST API route, and the second argument is the fromData created with FormData object. We also defined reportProgress: true and observe: ‘events’ value because we want to track the Http request’s progress.

Angular 8 File Upload System with Reactive Forms

In this segment, we will learn to create an Angular 8 file uploading system with Reactive Forms.

Go to app.module.ts file and import the ReactiveFormsModule service.

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

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

export class AppModule { }

Go to src/app/create-user.component.ts file and add the following code.

import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup } from "@angular/forms";
import { FileUploadService } from "../shared/file-upload.service";
import { HttpEvent, HttpEventType } from '@angular/common/http';
import { Router } from '@angular/router';

@Component({
  selector: 'app-create-user',
  templateUrl: './create-user.component.html',
  styleUrls: ['./create-user.component.css']
})

export class CreateUserComponent implements OnInit {
  preview: string;
  form: FormGroup;
  percentDone: any = 0;
  users = [];

  constructor(
    public fb: FormBuilder,
    public router: Router,
    public fileUploadService: FileUploadService
  ) {
    // Reactive Form
    this.form = this.fb.group({
      name: [''],
      avatar: [null]
    })
  }

  ngOnInit() { }

  // Image Preview
  uploadFile(event) {
    const file = (event.target as HTMLInputElement).files[0];
    this.form.patchValue({
      avatar: file
    });
    this.form.get('avatar').updateValueAndValidity()

    // File Preview
    const reader = new FileReader();
    reader.onload = () => {
      this.preview = reader.result as string;
    }
    reader.readAsDataURL(file)
  }

  submitForm() {
    this.fileUploadService.addUser(
      this.form.value.name,
      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.percentDone = Math.round(event.loaded / event.total * 100);
          console.log(`Uploaded! ${this.percentDone}%`);
          break;
        case HttpEventType.Response:
          console.log('User successfully created!', event.body);
          this.percentDone = false;
          this.router.navigate(['users-list'])
      }
    })
  }

}

Next, go to src/app/create-user.component.html file and add the following code.

<form [formGroup]="form" (ngSubmit)="submitForm()">
  <!-- Progress Bar -->
  <div class="progress form-group" *ngIf="fileUploadService.percentDone">
    <div class="progress-bar progress-bar-striped bg-success" role="progressbar"
      [style.width.%]="fileUploadService.percentDone">
    </div>
  </div>

  <!-- Image Preview -->
  <div class="form-group">
    <div class="preview" *ngIf="preview && preview !== null">
      <img [src]="preview" [alt]="form.value.name">
    </div>
  </div>

  <!-- File Input -->
  <div class="form-group">
    <input type="file" (change)="uploadFile($event)">
  </div>

  <!-- Name -->
  <div class="form-group input-group-lg">
    <input class="form-control" placeholder="Name" formControlName="name">
  </div>

  <!-- Submit -->
  <div class="form-group">
    <button class="btn btn-danger btn-block btn-lg">Create User</button>
  </div>
</form>
  • We used created the basic form using Bootstrap 4 UI components.
  • We are using Reactive Forms to manage the data.
  • To show image preview in Angular, we declared the uploadFile method and using the FileReader method to create the reader instance. The reader instance will use the readAsDataURL method and convert the base64 image to show the image preview. You can check out this detailed article on Angular image preview with Reactive Forms.
  • Next, access the addUser method from Angular service. This method will take name and avatar values to store the data on the MongoDB database. When we subscribe to this method, then it will also track the File or data upload with progress bar using HttpEvent and HttpEventType services

Angular 8 File Upload with Reactive Forms

Show User Data List

Next, we will show user uploaded data on Angular’s frontend, go to users-list/users-list.component.ts file and add the following code inside of it.

import { Component, OnInit } from '@angular/core';
import { FileUploadService } from "../shared/file-upload.service";

@Component({
  selector: 'app-users-list',
  templateUrl: './users-list.component.html',
  styleUrls: ['./users-list.component.css']
})

export class UsersListComponent implements OnInit {

  Users: any = [];

  constructor(public fileUploadService: FileUploadService) {
    this.getUsers();
  }

  ngOnInit() { }

  getUsers() {
    this.fileUploadService.getUsers().subscribe((res) => {
      this.Users = res['users'];
    })
  }

}

Then, go to users-list/users-list.component.html file and include the given below
code inside of it.

<div class="container user-table">

    <!-- No data message -->
    <div class="alert alert-success text-center" role="alert" *ngIf="Users.length <= 0">
        No Users added yet!
    </div>

    <ul class="list-unstyled">
        <li class="media" *ngFor="let user of Users; let i = index">
            <img [src]="user.avatar" class="mr-3" [alt]="user.name">
            <div class="media-body">
                <h5 class="mt-0 mb-1">{{user.name}}</h5>
                {{user._id}}
            </div>
        </li>
    </ul>

</div>

Conclusion

Finally, we have completed Angular 8 and Express JS File Upload with Reactive Forms tutorial. In this tutorial, we learnt to upload files from Angular app to mongoDB database using node and express server. We learned to show image preview and making image or file upload progress bar using HttpEvent progress API service. We explored about Multer NPM module, and it’s middleware. I hope you enjoyed this article, please consider it sharing with others.

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.