File Upload with Progress bar using Angular 5 and NodeJS

Sabyasachi Patra
6 min readMar 26, 2018

--

TL;DR

This post is about uploading file from your angular application to your node back-end with progress bar.

Steps :

Step 1: We will setup an working environment to work with angular 5 and node.
Step 2: We will create a component with a form and progress bar and submit the from to our node back-end.
Step 3: We will create a function in our back-end with corresponding route to receive the file and save it.

Lets Code…

Step 1 : Lets set up our working environment…
1.1 Install/Update Angular CLI and Create Angular 5 Application

sudo npm install -g @angular/cling new demo-file-upload

1.2 Change Directory into newly created application folder and run the application

cd ./demo-file-uploadng serve

1.3 Now, open the browser then go to http://localhost:4200 you should see this page.

1.4 Replace Web Server with Express.js

Close the running Angular app first by press `ctrl+c` then type this command for adding Express.js modules and it dependencies.

npm install --save express morgan connect-busboy serve-favicon

1.5 Then, add bin folder and www file inside bin folder.

mkdir bin
touch bin/www

1.6 Open and edit www file then add this lines of codes.

#!/usr/bin/env node

/**
* Module dependencies.
*/

var app = require('../app');
var debug = require('debug')('mean-app:server');
var http = require('http');

/**
* Get port from environment and store in Express.
*/

var port = normalizePort(process.env.PORT || '3000');
app.set('port', port);

/**
* Create HTTP server.
*/

var server = http.createServer(app);

/**
* Listen on provided port, on all network interfaces.
*/

server.listen(port);
server.on('error', onError);
server.on('listening', onListening);

/**
* Normalize a port into a number, string, or false.
*/

function normalizePort(val) {
var port = parseInt(val, 10);

if (isNaN(port)) {
// named pipe
return val;
}

if (port >= 0) {
// port number
return port;
}

return false;
}

/**
* Event listener for HTTP server "error" event.
*/

function onError(error) {
if (error.syscall !== 'listen') {
throw error;
}

var bind = typeof port === 'string'
? 'Pipe ' + port
: 'Port ' + port;

// handle specific listen errors with friendly messages
switch (error.code) {
case 'EACCES':
console.error(bind + ' requires elevated privileges');
process.exit(1);
break;
case 'EADDRINUSE':
console.error(bind + ' is already in use');
process.exit(1);
break;
default:
throw error;
}
}

/**
* Event listener for HTTP server "listening" event.
*/

function onListening() {
var addr = server.address();
var bind = typeof addr === 'string'
? 'pipe ' + addr
: 'port ' + addr.port;
debug('Listening on ' + bind);
}

1.7 To make the server run from bin/www, open and edit “package.json” then replace “start” value.

"scripts": {
"ng": "ng",
"start": "ng build && node ./bin/www",
"build": "ng build",
"test": "ng test",
"lint": "ng lint",
"e2e": "ng e2e"
},

1.8 Now, create app.js in the root of project folder.

touch app.js

1.9 Open and edit app.js then add all this lines of codes.

var express = require('express');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var busboy = require('connect-busboy');
var api = require('./server/routes/api');var app = express();
app.use(logger('dev'));
app.use(busboy({ immediate: true }));
app.use(express.static(path.join(__dirname, 'dist')));
app.use('/api', api);// catch 404 and forward to error handler
app.use(function(req, res, next) {
var err = new Error('Not Found');
err.status = 404;
next(err);
});
// error handler
app.use(function(err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
// render the error page
res.status(err.status || 500);
res.render('error');
});
module.exports = app;

1.10 Next, create a server folder and a routes folder inside the server folder then create routes file api.js for apis

mkdir server
mkdir server/routes
touch server/routes/api.js

1.11 Open and edit server/routes/api.js file then add this lines of codes.

var express = require('express');
var router = express.Router();
router.get('/', function(req, res, next) {
res.send('API working!');
});
module.exports = router;

1.12 Now, run the server using this command

npm start

1.13 You will see the previous Angular landing page when you point your browser to http://localhost:3000. When you change the address to http://localhost:3000/api you will see this page.

Note : This article helped me set up the working environment. Thanks to the author.

Step 2 : Lets set up our Angular component…

2.1 Create Angular 5 Component for uploading file

ng g component demo-file-upload

2.2 Edit app.component.html

<app-demo-file-upload></app-demo-file-upload>

2.3 Install ng2-slim-loading-bar

We will use slim loading bar to show the progress bar

npm install ng2-slim-loading-bar --save

2.4 Update app.module.ts

Add the following import statements into app.module.ts

import { HttpClientModule } from '@angular/common/http';
import { SlimLoadingBarModule } from 'ng2-slim-loading-bar';

And add these lines in the imports array in @NgModule

HttpClientModule,
SlimLoadingBarModule.forRoot()

2.5 Open and edit src/index.html then add the Bootstrap CSS and JS library.

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>DemoFileUpload</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script>
</head>
<body>
<app-root></app-root>
</body>
</html>

2.6 Open and edit src/styles.css then add below styles.

app-demo-file-upload {
display: flex;
-ms-flex-align: center;
align-items: center;
height: 100%;
width: 100%;
position: absolute;
}
.card {
margin: 0 auto;
}
.slimbar-container {
padding: 5px;
}

2.7 Open and edit src/app/demo-file-upload/demo-file-upload.html then add the below code.

<div class="card" style="width: 18rem;">
<div class="card-body">
<h5 class="card-title">Demo File Upload</h5>
<input type="file" class="form-control-file" (change)="onFileSelected($event)">
<div class="slimbar-container">
<ng2-slim-loading-bar></ng2-slim-loading-bar>
</div>
<div *ngIf="showMessage">
<p>{{message}}</p>
</div>
<button class="card-link" type="button" (click)="onUpload()">Upload</button>
</div>
</div>

2.8 Open and edit src/app/demo-file-upload/demo-file-upload.component.ts then add the below code.

import { Component, OnInit } from '@angular/core';
import { HttpClient, HttpEvent, HttpEventType } from '@angular/common/http';
import { SlimLoadingBarService } from 'ng2-slim-loading-bar';
@Component({
selector: 'app-demo-file-upload',
templateUrl: './demo-file-upload.component.html',
styleUrls: ['./demo-file-upload.component.css']
})
export class DemoFileUploadComponent implements OnInit {
selectedFile: File = null;
uploadedPercentage = 0;
showMessage = false;
message: String = '';
constructor(private slimLoadingBarService: SlimLoadingBarService, private http: HttpClient) { }

ngOnInit() { }

onFileSelected(event) {
this.selectedFile = <File>event.target.files[0];
}

onUpload() {
const fd = new FormData();
this.showMessage = false;
console.log(this.selectedFile.name);
fd.append('file', this.selectedFile, this.selectedFile.name);
this.http.post(`/api/upload-file`, fd, {
reportProgress: true, observe: 'events'
}).subscribe( (event: HttpEvent<any>) => {
switch (event.type) {
case HttpEventType.Sent:
this.slimLoadingBarService.start();
break;
case HttpEventType.Response:
this.slimLoadingBarService.complete();
this.message = "Uploaded Successfully";
this.showMessage = true;
break;
case 1: {
if (Math.round(this.uploadedPercentage) !== Math.round(event['loaded'] / event['total'] * 100)){
this.uploadedPercentage = event['loaded'] / event['total'] * 100;
this.slimLoadingBarService.progress = Math.round(this.uploadedPercentage);
}
break;
}
}
},
error => {
console.log(error);
this.message = "Something went wrong";
this.showMessage = true;
this.slimLoadingBarService.reset();
});
}

}

Step 3 : Lets set up our nodejs back-end to save the submitted…

3.1 Create a public folder at the root of your application and a my-files folder inside the public folder

mkdir public
mkdir public/my-files

3.2 Open and edit `server/routes/api.js`

var express = require('express');
var router = express.Router();
var fs = require('fs');
router.post('/upload-file', function(req, res, next) {
var fstream;
if (req.busboy) {

req.busboy.on('file', function(fieldname, file, filename, encoding, mimetype) {
fstream = fs.createWriteStream(__dirname + '/../../public/my-files/' + filename);
file.pipe(fstream);
fstream.on('close', function(){
console.log('file ' + filename + ' uploaded');
});
});
req.busboy.on('finish', function(){
console.log('finish, files uploaded ');
res.json({ success : true});
});
req.pipe(req.busboy);
}
});

module.exports = router;

Now, run npm start and see the working app.

This brings us to the end of the article. Kudos to you if you made it this far.

If you would like the completed version of the code, visit the master branch on Github.

Feel free to Comment or Share this post. Thanks for reading!

--

--