File Upload with Angular 2 and Node.js

October 24, 2016
by Rahil Shaikh

File Upload is one of those things that is not as straight forward but gets easier once we get our heads around it. In this tutorial, we will see how we can implement File Upload using Angular 2 and Node.js. We have already covered Angular 1 variation of this topic in one of our previous tutorial.

This tutorial comprises of two parts.

  • Back-end with NodeJS/ ExpressJS and Multer
  • Browser Application with Angular 2 and ng2-File-Upload

Without further ado, let’s get started.

Back-end with NodeJS/ ExpressJS and Multer

The back-end implementation for File Upload with Node.js is more or less similar to the one we did in our post for file upload with Angular 1 and Node. There is only one change and that is we will be enabling CORS on our Node server. So here I’ll just display the code, for a detailed explanation please visit this link.

DEMODOWNLOAD

Let’s start by creating a project directory.

mkdir file-upload-demo

 
Now, let’s navigate into the working directory and create a directory for our Node.js application.

cd file-upload-demo
mkdir node-app

cd node-app
package.json

{
  "name": "expapp",
  "version": "1.0.0",
  "description": "",
  "main": "app.js",
  "scripts": {
    "test": "echo "Error: no test specified" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "gulp": "3.9.0",
    "gulp-develop-server": "0.5.0",
    "gulp-jshint": "1.12.0"
  },
  "dependencies": {
    "body-parser": "1.14.1",
    "express": "4.13.3",
    "fs": "0.0.2",
    "multer": "1.1.0"
  }
}
app.js

    var express = require('express');
    var app = express();
    var bodyParser = require('body-parser');
    var multer = require('multer');

    app.use(function(req, res, next) { //allow cross origin requests
        res.setHeader("Access-Control-Allow-Methods", "POST, PUT, OPTIONS, DELETE, GET");
        res.header("Access-Control-Allow-Origin", "http://localhost:3000");
        res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
        res.header("Access-Control-Allow-Credentials", true);
        next();
    });

    /** Serving from the same express Server
    No cors required */

    app.use(express.static('../client'));
    app.use(bodyParser.json());  

    var storage = multer.diskStorage({ //multers disk storage settings
        destination: function (req, file, cb) {
            cb(null, './uploads/');
        },
        filename: function (req, file, cb) {
            var datetimestamp = Date.now();
            cb(null, file.fieldname + '-' + datetimestamp + '.' + file.originalname.split('.')[file.originalname.split('.').length -1]);
        }
    });

    var upload = multer({ //multer settings
                    storage: storage
                }).single('file');

    /** API path that will upload the files */
    app.post('/upload', function(req, res) {
        upload(req,res,function(err){
            console.log(req.file);
            if(err){
                 res.json({error_code:1,err_desc:err});
                 return;
            }
             res.json({error_code:0,err_desc:null});
        });
    });

    app.listen('3001', function(){
        console.log('running on 3001...');
    });

 
We are using gulp as a task runner for our Node.js app.

gulpfile.js

var gulp   = require( 'gulp' ),
    server = require( 'gulp-develop-server' )
    jshint = require('gulp-jshint');
   
gulp.task('lint', function() {
  return gulp.src('app.js')
    .pipe(jshint())
    .pipe(jshint.reporter('default'));
});
   
    // run server
gulp.task( 'server:start', function() {
    server.listen( { path: './app.js' } );
});
 
// restart server if app.js changed
gulp.task( 'server:restart', function() {
    gulp.watch( [ './app.js' ], server.restart );
});

gulp.task('default', ['lint','server:start','server:restart']);

 

We need to have a directory where our uploaded files will go.

mkdir uploads

 
Install all dependencies, and also install gulp globally.

npm install

npm install gulp -g

 
We can start up the server with the below command.

gulp

If everything was setup properly you should be able to see the following output on the console.

File Upload with NodeJS

Node.js server

Browser Application with Angular 2 and ng2-File-Upload

Since you are here, and looking to implement file-upload in Angular 2, I think it’s safe to assume that you have a basic Angular 2 application ready and that your machine is setup to run Angular 2 application. So I’ll skip the setup part and start with a basic pre-built Angular 2 application. If you still wish to know the basics in Angular 2 you can visit the below articles.

We will start by cloning the repository into our working directory (in our case it’s /file-upload-demo).

git clone https://github.com/rahil471/angular2-fast-start.git angular2-app

cd angular2-app

Install all the dependencies.

npm install

 
We will be using ng2-file-upload library to help us with the File Upload for our Angular 2 application. Let’s install it using npm.

npm install ng2-file-upload --save

 

Now we need to configure our module loader to recognize and find ng2-file-upload. The configuration would be specific to the module loader you are using. For this tutorial, we are using Systems.js as our module loader, so we will see the configuration for the same.

systemjs.config.js

/**
 * System configuration for Angular samples
 * Adjust as necessary for your application needs.
 */

(function (global) {
  System.config({
    paths: {
      // paths serve as alias
      'npm:': 'node_modules/'
    },
    // map tells the System loader where to look for things
    map: {
      // our app is within the app folder
      app: 'app',
      // angular bundles
      [email protected]/core': 'npm:@angular/core/bundles/core.umd.js',
      [email protected]/common': 'npm:@angular/common/bundles/common.umd.js',
      [email protected]/compiler': 'npm:@angular/compiler/bundles/compiler.umd.js',
      [email protected]/platform-browser': 'npm:@angular/platform-browser/bundles/platform-browser.umd.js',
      [email protected]/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js',
      [email protected]/http': 'npm:@angular/http/bundles/http.umd.js',
      [email protected]/router': 'npm:@angular/router/bundles/router.umd.js',
      [email protected]/forms': 'npm:@angular/forms/bundles/forms.umd.js',
      // other libraries
      'rxjs':                      'npm:rxjs',
      'angular-in-memory-web-api': 'npm:angular-in-memory-web-api',
      /** Path for ng2-file-upload */
      'ng2-file-upload' : 'npm:ng2-file-upload'
      /** Path for ng2-file-upload */
    },
    // packages tells the System loader how to load when no filename and/or no extension
    packages: {
      app: {
        main: './transpiled-js/main.js',
        defaultExtension: 'js'
      },
      rxjs: {
        defaultExtension: 'js'
      },
      'angular-in-memory-web-api': {
        main: './index.js',
        defaultExtension: 'js'
      },
      /** Configuration for ng2-file-upload */
      'ng2-file-upload' : {
        main: './ng2-file-upload.js',
        defaultExtension: 'js'
      }
      /** Configuration for ng2-file-upload */
    }
  });
})(this);

 
The ng2-file-upload module provides us with a few directives for achieving file upload, we need to import them and add them to declaration of our AppModule before we can use them.

app.module.ts

import { NgModule } from [email protected]/core';
import { BrowserModule } from [email protected]/platform-browser';
import { FileSelectDirective, FileDropDirective } from 'ng2-file-upload';

import { AppComponent }   from './app.component';

@NgModule({
    imports: [BrowserModule],
    exports: [],
    declarations: [AppComponent, FileSelectDirective], /** The FileSelectDirective is what we will require*/
    providers: [],
    bootstrap: [AppComponent]
})
export class AppModule { }

We are importing and adding FileSelectDirective to the declarations, if we also want to implement the drag and drop feature we will have to add the FileDropDirective .

Basic Usage

In our app.component.ts we will import the FileUploader class and create an uploader object of type FileUploader.

app/app.component.ts

import { Component } from [email protected]/core';
import { FileUploader } from 'ng2-file-upload';

@Component({
    selector: 'my-app',
    template: `
              ....
              .....
              `
})
export class AppComponent {
    public uploader:FileUploader = new FileUploader({url:'http://localhost:3001/upload'});
}

We are passing the upload URL of our Node.js application which we created earlier in the tutorial in the argument object of FileUploader.
To make this work in our template we will add a ng2FileSelect directive to an html input of type file, we will also set the [uploader] property to our uploader object.

For a single file, the input should look like below

<div class="form-group">
   <label for="single">single</label>
   <input type="file" class="form-control" name="single" ng2FileSelect [uploader]="uploader" />                                  
</div>

To enable selection of multiple files we only need to add the multiple attribute of HTML5.
The uploader object stores all the selected files in a queue. To upload all the file at once we can call uploader.uploadAll() function or we can call the upload() function availaible on each item of the queue.

<button type="button" class="btn btn-success btn-s"
   (click)="uploader.uploadAll()" [disabled]="!uploader.getNotUploadedItems().length">
    <span class="glyphicon glyphicon-upload"></span> Upload all
</button><br />

This much in your template should be enough to get the basic thing going, but it's not that presentable, so we will add a few more elements and try to make it more interactive. We are using bootstrap for styling, you are free to use a framework of your choice.

The complete template our app.component.ts would look like this.

app/app.component.ts

import { Component } from [email protected]/core';
import { FileUploader } from 'ng2-file-upload';

@Component({
    selector: 'my-app',
    template: `<nav class="navbar navbar-default">
                    <div class="container-fluid">
                        <div class="navbar-header">
                        <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
                            <ul class="nav navbar-nav">
                            <li><a>File Upload</a></li>
                            </ul>
                        </div>
                        </div>
                    </div>
                </nav>
                <div class="container">
                    <div class="row">
                        <div class="col-md-4">
                            <form>
                                <div class="form-group">
                                    <label for="multiple">Multiple</label>
                                    <input type="file" class="form-control" name="multiple" ng2FileSelect [uploader]="uploader" multiple  />
                                </div>
                                <div class="form-group">
                                    <label for="single">single</label>
                                    <input type="file" class="form-control" name="single" ng2FileSelect [uploader]="uploader" />                                  
                                </div>            
                            </form>
                        </div>
                        <div class="col-md-8">
                            <h3>File Upload with Angular 2 and Node</h3>
                            Queue length: {{ uploader?.queue?.length }}

                            <table class="table">
                                <thead>
                                <tr>
                                    <th width="50%">Name</th>
                                    <th>Size</th>
                                    <th>Progress</th>
                                    <th>Status</th>
                                    <th>Actions</th>
                                </tr>
                                </thead>
                                <tbody>
                                <tr *ngFor="let item of uploader.queue">
                                    <td><strong>{{ item.file.name }}</strong></td>
                                    <td nowrap>{{ item.file.size/1024/1024 | number:'.2' }} MB</td>
                                    <td>
                                        <div class="progress" style="margin-bottom: 0;">
                                            <div class="progress-bar" role="progressbar" [ngStyle]="{ 'width': item.progress + '%' }"></div>
                                        </div>
                                    </td>
                                    <td class="text-center">
                                        <span *ngIf="item.isSuccess"><i class="glyphicon glyphicon-ok"></i></span>
                                        <span *ngIf="item.isCancel"><i class="glyphicon glyphicon-ban-circle"></i></span>
                                        <span *ngIf="item.isError"><i class="glyphicon glyphicon-remove"></i></span>
                                    </td>
                                    <td nowrap>
                                        <button type="button" class="btn btn-success btn-xs"
                                                (click)="item.upload()" [disabled]="item.isReady || item.isUploading || item.isSuccess">
                                            <span class="glyphicon glyphicon-upload"></span> Upload
                                        </button>
                                        <button type="button" class="btn btn-warning btn-xs"
                                                (click)="item.cancel()" [disabled]="!item.isUploading">
                                            <span class="glyphicon glyphicon-ban-circle"></span> Cancel
                                        </button>
                                        <button type="button" class="btn btn-danger btn-xs"
                                                (click)="item.remove()">
                                            <span class="glyphicon glyphicon-trash"></span> Remove
                                        </button>
                                    </td>
                                </tr>
                                </tbody>
                            </table>

                            <div>
                                <div>
                                    Queue progress:
                                    <div class="progress" style="">
                                        <div class="progress-bar" role="progressbar" [ngStyle]="{ 'width': uploader.progress + '%' }"></div>
                                    </div>
                                </div>
                                <button type="button" class="btn btn-success btn-s"
                                        (click)="uploader.uploadAll()" [disabled]="!uploader.getNotUploadedItems().length">
                                    <span class="glyphicon glyphicon-upload"></span> Upload all
                                </button>
                                <button type="button" class="btn btn-warning btn-s"
                                        (click)="uploader.cancelAll()" [disabled]="!uploader.isUploading">
                                    <span class="glyphicon glyphicon-ban-circle"></span> Cancel all
                                </button>
                                <button type="button" class="btn btn-danger btn-s"
                                        (click)="uploader.clearQueue()" [disabled]="!uploader.queue.length">
                                    <span class="glyphicon glyphicon-trash"></span> Remove all
                                </button>
                            </div>
                        </div>
                    </div>
                </div>`
})
export class AppComponent {
    public uploader:FileUploader = new FileUploader({url:'http://localhost:3001/upload'});
}

As I said earlier each file is stored in a queue. Hence, we are repeating through the queue using the ngFor directive. Each item has the following important properties.

  • file- File object which contains details related to respective file, including name, size and more.
  • progress- Progress of the item beign uploaded in percentage.
  • upload()- Method to upload the file.
  • cancel()- Cancels and ongoing upoad.
  • remove()- Removes item from queue.

And there are a few more properties. Have a closer look at the template to understand them. Apart from the properties and methods on each item we also have methods that operate on the entire queue. For eg: uploadAll(), cancelAll(), removeAll().

Also, please note when we are uploading multiple files together, it does not send all files at once, instead the ng2-file-uploader calls the upload API multiple times depending on the number of items, uploading one at a time.

To start the application run the below command.

npm start

If you've followed along properly you should see the following screen on your browser running at localhost:3000.

File Upload Angular 2

Before Upload

Considering you have the Node.js app running, you should be able to upload files.

File Upload Angular 2

After Upload

Quick Setup

We know this has been a long tutorial and there are chances you might have slipped at one or two places. No worries, you can quickly get the demo running by following the below steps.

  • git clone https://github.com/rahil471/File-upload-Angular2-Nodejs.git file-upload
  • Navigate into the node app cd file-upload/node-app
  • Install Dependencies npm install
  • Install gulp globally npm install gulp -g
  • To start the node server gulp
  • Open a new terminal window.
  • Navigate into /angular2-app/
  • Install all dependencies npm install
  • In some cases you might have to isntall lite-server globally npm i lite-server -g
  • Run the Angular 2 app using npm start

DEMODOWNLOAD

Conclusion

File Upload is one of the most common requirements for any web-application and sometimes it can become a hiccup, but not after this tutorial. In this tutorial, we learned how we can implement File Upload using Node.js and Angular 2 with ease.

More Angular 2 Stuff

  1. Angular 2 Official Documentation
  2. Learn Angular 2 From our Free Video Course on YouTube
  3. Learn Angular 2 by building 12 apps
  4. Angular 2 by Istvan Novak

About

Engineer. Blogger. Thinker. Loves programming and working with emerging tech. We can also talk on Football, Gaming, World Politics, Monetary Systems.

Free PDF

Subscribe and get AngularJS Tips PDF. We never spam!
First Name:
Email:

17 comments

  1. mohit
    |

    Great Tutorial Rahil…How can we maintain a list of who has uploaded what and then give the user a chance to delete the uploaded file as well?

    • |

      You will need to persist the metadata in some db. And display it when user demands.
      Metadata might include (path to image or blob, size, type, resolution, etc etc)

  2. |

    Hi,

    excelente post.

    How can I get the response from the upload?

    to have the file name or the path

    • |

      where would you want the path and filename? At the backend or in Angular2 app?

      • |

        Yes, I want to store in a record in the db with other data.

        • |

          The req.file object will have all the details!

          • |

            ok , but How can I can get the req from the uploader? (I’m using the single upload)

          • |

            Have a look at this peice of code.

            
            
            app.post('/upload', function(req, res) {
                    upload(req,res,function(err){
                        console.log(req.file);
                        if(err){
                             res.json({error_code:1,err_desc:err});
                             return;
                        }
                         res.json({error_code:0,err_desc:null});
                    });
                });
  3. |

    ok , but I’ve to implement a function in a service with an observable to map the response , isn’t it?

    maybe I miss something

    sorry

    • |

      I think you want to have the data at the client side then? Basically as a response from the upload api?
      Try this piece of code then.

       this.uploader.onSuccessItem = (item:FileItem, response:string, status:number, headers:ParsedResponseHeaders) => {
          console.log("onSuccessItem " + status, item);
        }
  4. |

    great tutorial. but i tried uploading using “ONCHANGE” event and gives me error of “can not read property upload of undefined”. want your help sir

  5. kamal
    |

    Get this error :

    Error: Template parse errors:
    Can’t bind to ‘uploader’ since it isn’t a known property of ‘input’. (“l for=”single”>single
    ][uploader]=”uploader” />

  6. Alan
    |

    thank you for the tutorial but how can i save the file in a local folder? i tried to change the url:’/public’ and i created a public folder in app folder and the error of 404 i get

  7. Amitava
    |

    Hi Rahil ,
    is it necessary to have backend server ? I am with angular 4 and i don’t need to transfer the file to a remote back-end server . In my use case after i click on Upload button i want to parse the file data and if it’s has valid content then i want to save/store the file in my angular client server only.

    What is your advice to achieve that ?

Leave a Comment