Create An Angular App For File Upload With Progress

In this blog, you are going to learn how to create an Angular app to implement file upload functionality.

We'll be using Angular 5 and Node for creating this web app and the file upload feature. So, let's start setting up our project.

Set up Angular 5 Workspace for File Upload

Run the below commands to install or update your Angular CLI and define your Angular 5 application.
  1. sudo npm install -g @angular/cli
  2. ng new ang5-file-upload
Next, you need to move to the newly created appl folder and get the application started.
  1. cd ./ang5-file-upload
  2. ng serve
The app will run on the default port, i.e., 4200. If you like to change it to, say, 4101, then specify the port and run the command as

ng serve --port 4101

Check now whether the app is running or not. Start the browser and type the URL as "http://localhost:4200". You should see the default Angular web app screen.
 
Angular web app screen 
Integrating Express.js to override the server

We'll be using the Express.js for this web application. It replaces the web and RESTful API server.

Express.js is a Node framework commonly used for web apps. It works as a node module for using with the applications having the server listen for any incoming connection requests.

Install Express JS

You now need to shut down the Angular web app and execute the below command for enabling the Express.js module along with its dependencies.

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

Prepare the HTTP Server for File Upload

Next, create a folder named as "bin" under the root folder and a new file as "www." You may use a different name or change the location if you like.
  1. mkdir bin
  2. touch bin/www
Now, copy the below code and put it in the "www" file.
  1. #!/usr/bin/env node  
  2.   
  3. /**  
  4.  * Load dependencies.  
  5.  */  
  6. var app = require('../app');  
  7. var debug = require('debug')('mean-app:server');  
  8. var http = require('http');  
  9. /**  
  10.  * Read port from the environment  
  11.  */  
  12. var port = normalizePort(process.env.PORT || '3000');  
  13. app.set('port', port);  
  14. /**  
  15.  * Launch the HTTP server.  
  16.  */  
  17. var server = http.createServer(app);  
  18. /**  
  19.  * Listen on the target port, on all network interfaces.  
  20.  */  
  21. server.listen(port);  
  22. server.on('error', onError);  
  23. server.on('listening', onListening);  
  24. /**  
  25.  * Normalize a port into a number, string, or false.  
  26.  */  
  27. function normalizePort(val) {  
  28.     var port = parseInt(val, 10);  
  29.     if (isNaN(port)) {  
  30.         // named pipe  
  31.         return val;  
  32.     }  
  33.     if (port >= 0) {  
  34.         // port number  
  35.         return port;  
  36.     }  
  37.     return false;  
  38. }  
  39. /**  
  40.  * Event listener for HTTP server "error" event.  
  41.  */  
  42. function onError(error) {  
  43.     if (error.syscall !== 'listen') {  
  44.         throw error;  
  45.     }  
  46.     var bind = typeof port === 'string' ? 'Pipe ' + port : 'Port ' + port;  
  47.     // Mapping errors with proper messages  
  48.     switch (error.code) {  
  49.         case 'EACCES':  
  50.             console.error(bind + ' requires elevated privileges');  
  51.             process.exit(1);  
  52.             break;  
  53.         case 'EADDRINUSE':  
  54.             console.error(bind + ' is already in use');  
  55.             process.exit(1);  
  56.             break;  
  57.         default:  
  58.             throw error;  
  59.     }  
  60. }  
  61. /**  
  62.  * Add a Event listener for the HTTP server  
  63.  */  
  64. function onListening() {  
  65.     var addr = server.address();  
  66.     var bind = typeof addr === 'string' ? 'pipe ' + addr : 'port ' + addr.port;  
  67.     debug('Listening on ' + bind);  
  68. }  
One more change you need to make is to update the starting application to Express.js. So, open the "package.json" and modify the "start" value in the "script" block. See here.
  1. "scripts": {  
  2.     "ng""ng",  
  3.     "start""ng build && node ./bin/www",  
  4.     "build""ng build",  
  5.     "test""ng test",  
  6.     "lint""ng lint",  
  7.     "e2e""ng e2e"  
  8. },  
Enable Routing

Add a "server" folder and create a "routes" folder inside it. After that, create the "api.js".
  1. mkdir server  
  2. mkdir server/routes  
  3. touch server/routes/api.js  
Copy the following lines of code and place in the api.js file.
  1. var express = require('express');  
  2. var router = express.Router();  
  3. router.get('/'function(req, res, next) {  
  4.     res.send('Reply from Express JS!');  
  5. });  
  6. module.exports = router;  
Start the Server

npm start

The above command starts the server on port no. 3000 as we specified in a previous step. You can check the application landing page by opening the http://localhost:3000/ URL in the browser.

The browser will request to the server, and the following response will get displayed.

Reply from Express JS!

Set up Angular Component for File Upload

Let's add the Angular 5 component to perform the file upload.
  1. ng g component ang5-file-upload  
Now, modify the "app.component.html" file with the following code.
  1. <app-ang5-file-upload></app-ang5-file-upload>  
Configure the ng2-slim-loading-bar

We will use a light-weight progress bar to indicate the progress.
  1. npm install ng2-slim-loading-bar --save  
Update App Module Config

Add the required import statements into the "app.module.ts" file.
  1. import { HttpClientModule } from '@angular/common/http';  
  2. import { SlimLoadingBarModule } from 'ng2-slim-loading-bar';  
Also, place the below code in the imports array in the @NgModule.
  1. HttpClientModule,  
  2. SlimLoadingBarModule.forRoot()  
Switch to Bootstrap

Open and modify the "src/index.html". After that, include the Bootstrap CSS and JS library.
  1. <!doctype html>  
  2. <html lang="en">  
  3.   
  4. <head>  
  5.     <meta charset="utf-8">  
  6.     <title>Angular v5 File Upload Demo</title>  
  7.     <base href="/">  
  8.     <meta name="viewport" content="width=device-width, initial-scale=1">  
  9.     <link rel="icon" type="image/x-icon" href="favicon.ico">  
  10.     <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">  
  11.     <script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>  
  12.     <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>  
  13.     <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script>  
  14. </head>  
  15.   
  16. <body>  
  17.     <app-root></app-root>  
  18. </body>  
  19.   
  20. </html>  
Update the CSS style sheet with the following code.
  1. app - ang5 - file - upload {  
  2.     display: flex; - ms - flex - align: center;  
  3.     align - items: center;  
  4.     height: 100 % ;  
  5.     width: 100 % ;  
  6.     position: absolute;  
  7. }.card {  
  8.     margin: 0 auto;  
  9. }.litebar - container {  
  10.     padding: 5 px;  
  11. }  
Now, change the HTML template file by editing the "src/app/ang5-file-upload/ang5-file-upload.html". PasteDemo the code from the below block.
  1. <div class="card" style="width: 18rem;">  
  2.     <div class="card-body">  
  3.         <h5 class="card-title">Ang 5 File Upload</h5>  
  4.         <input type="file" class="form-control-file" (change)="onFileSelected($event)">  
  5.         <div class="litebar-container">  
  6.             <ng2-slim-loading-bar></ng2-slim-loading-bar>  
  7.         </div>  
  8.         <div *ngIf="showMessage">  
  9.             <p>{{message}}</p>  
  10.         </div>  
  11.         <button class="card-link" type="button" (click)="onUpload()">Upload file</button>  
  12.     </div>  
  13. </div>  
Another file is "src/app/ang5-file-upload/ang5-file-upload.component.ts" where you have to place the code given below.
  1. import {  
  2.     Component,  
  3.     OnInit  
  4. } from '@angular/core';  
  5. import {  
  6.     HttpClient,  
  7.     HttpEvent,  
  8.     HttpEventType  
  9. } from '@angular/common/http';  
  10. import {  
  11.     SlimLoadingBarService  
  12. } from 'ng2-slim-loading-bar';  
  13. @Component({  
  14.     selector: 'app-ang5-file-upload',  
  15.     templateUrl: './ang5-file-upload.component.html',  
  16.     styleUrls: ['./ang5-file-upload.component.css']  
  17. })  
  18. export class Ang5FileUploadComponent implements OnInit {  
  19.     selectedFile: File = null;  
  20.     uploadedProgress = 0;  
  21.     showMessage = false;  
  22.     message: String = '';  
  23.     constructor(private slimLoadingBarService: SlimLoadingBarService, private http: HttpClient) {}  
  24.     ngOnInit() {}  
  25.     onFileSelected(event) {  
  26.         this.selectedFile = < File > event.target.files[0];  
  27.     }  
  28.     onUpload() {  
  29.         const fd = new FormData();  
  30.         this.showMessage = false;  
  31.         console.log(this.selectedFile.name);  
  32.         fd.append('file'this.selectedFile, this.selectedFile.name);  
  33.         this.http.post('/api/upload-file', fd, {  
  34.             reportProgress: true,  
  35.             observe: 'events'  
  36.         }).subscribe((event: HttpEvent < any > ) => {  
  37.             switch (event.type) {  
  38.                 case HttpEventType.Sent:  
  39.                     this.slimLoadingBarService.start();  
  40.                     break;  
  41.                 case HttpEventType.Response:  
  42.                     this.slimLoadingBarService.complete();  
  43.                     this.message = "Upload done successfully";  
  44.                     this.showMessage = true;  
  45.                     break;  
  46.                 case 1:  
  47.                     {  
  48.                         if (Math.round(this.uploadedPercentage) !== Math.round(event['loaded'] / event['total'] * 100)) {  
  49.                             this.uploadedProgress = event['loaded'] / event['total'] * 100;  
  50.                             this.slimLoadingBarService.progress = Math.round(this.uploadedProgress);  
  51.                         }  
  52.                         break;  
  53.                     }  
  54.             }  
  55.         }, error => {  
  56.             console.log(error);  
  57.             this.message = "Something went wrong";  
  58.             this.showMessage = true;  
  59.             this.slimLoadingBarService.reset();  
  60.         });  
  61.     }  
  62. }  
Configure Node.js Backend

Add a public folder under the root of your app and a "data" folder inside it.
  1. mkdir public
  2. mkdir public/data
Next, modify the code of "server/routes/api.js" file.
  1. var express = require('express');  
  2. var router = express.Router();  
  3. var fs = require('fs');  
  4. router.post('/upload-file'function(req, res, next) {  
  5.     var fstream;  
  6.     if (req.busboy) {  
  7.         req.busboy.on('file'function(fieldname, file, filename, encoding, mimetype) {  
  8.             fstream = fs.createWriteStream(__dirname + '/../../public/data/' + filename);  
  9.             file.pipe(fstream);  
  10.             fstream.on('close'function() {  
  11.                 console.log('file ' + filename + ' uploaded');  
  12.             });  
  13.         });  
  14.         req.busboy.on('finish'function() {  
  15.             console.log('finish, files upload done! ');  
  16.             res.json({  
  17.                 success: true  
  18.             });  
  19.         });  
  20.         req.pipe(req.busboy);  
  21.     }  
  22. });  
  23. module.exports = router;  
Finally, re-run the "npm start" command to get your Angular v5 file upload demo running.