File Upload with Angular 2 and Node.js

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.

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.

  • Build a single page application wth Angular 2
  • Angular 2 architecture

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
      '@angular/core': 'npm:@angular/core/bundles/core.umd.js',
      '@angular/common': 'npm:@angular/common/bundles/common.umd.js',
      '@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.js',
      '@angular/platform-browser': 'npm:@angular/platform-browser/bundles/platform-browser.umd.js',
      '@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js',
      '@angular/http': 'npm:@angular/http/bundles/http.umd.js',
      '@angular/router': 'npm:@angular/router/bundles/router.umd.js',
      '@angular/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 '@angular/core';
import { BrowserModule } from '@angular/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 '@angular/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 '@angular/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.

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

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
Facebook
Twitter
LinkedIn
Pinterest

Related posts