Creating a Live Auction App with Angular 2, Node.js and Socket.IO

Angular 2 Auction App
Angular 2 Auction App

In this tutorial we’re going to build a very cool Live Auction Application, my goal is to demonstrate how Angular 2, Node.js and Socket.io work together, and as everyone knows Angular 2 will soon come out so I thought it would be great to write a tutorial involving it. Of course our app won’t have all the features a real auction app has, this is just an example to demonstrate how to integrate the frameworks I mentioned.

Setting up the Environment

The first step is to configure the environment, let’s get started by creating our Node.js project, I’m going to use express to do it, if you don’t have it open your terminal and run the command npm install express-generator -g to install, then you can create the project with the command expess {project name} (replace {project name} with your actual project name).

Now you have the basic project structure created for you, but we need to make some changes in order add Angular 2 and Socket.io to the project, first open your package.json and paste the following code:

{
  "name": "auction",
  "version": "0.0.0",
  "private": true,
  "scripts": {
    "postinstall": "npm run typings install",
    "tsc": "tsc",
    "tsc:w": "tsc -w",
    "start": "concurrent \"node ./bin/www\" \"npm run tsc:w\"",
    "typings" : "typings"
  },
   "license": "ISC",
  "dependencies": {
    "body-parser": "~1.13.2",
    "cookie-parser": "~1.3.5",
    "debug": "~2.2.0",
    "express": "~4.13.1",
    "jade": "~1.11.0",
    "morgan": "~1.6.1",
    "serve-favicon": "~2.3.0",
    "angular2": "2.0.0-beta.6",
    "systemjs": "0.19.20",
    "es6-promise": "^3.0.2",
    "es6-shim": "^0.33.3",
    "reflect-metadata": "0.1.2",
    "rxjs": "5.0.0-beta.0",
    "zone.js": "0.5.14",
    "socket.io":"1.4.5"
  },
  "devDependencies": {
    "concurrently": "^1.0.0",
    "lite-server": "^2.0.1",
    "typescript": "^1.7.5",
    "typings":"^0.6.8"
  }
}

Now create another file called tsconfig.json in the project root folder and put the following into the file:

{
  "compilerOptions": {
    "target": "es5",
    "module": "system",
    "moduleResolution": "node",
    "sourceMap": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "removeComments": false,
    "noImplicitAny": false
  },
  "exclude": [
    "node_modules",
    "typings/main",
    "typings/main.d.ts"
  ]
}

Also in the root folder create the typings.json and copy/paste the following code into it:

{
  "ambientDependencies": {
    "es6-shim": "github:DefinitelyTyped/DefinitelyTyped/es6-shim
/es6-shim.d.ts#6697d6f7dadbf5773cb40ecda35a76027e0783b2"
  }
}

Ok, we have now created all the configuration files, but before we continue you have to install the dependencies by running the command npm install on the terminal.

If you open your app.js you’ll see that express has already put some code in there, in order to make it as simple as possible let’s delete the code we don’t need, after the modification your app.js should look like this:

var express = require('express');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
var routes = require('./routes/index');
var app = express();
 
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
 
app.use('/', routes);
 
// catch 404 and forward to error handler
app.use(function(req, res, next) {
  var err = new Error('Not Found');
  err.status = 404;
  next(err);
});
 
module.exports = app;

By default express sets jade as the view engine, we’ve already deleted the lines responsible for that in the app.js, but if you check the folder /views you’ll see some .jade files, you can delete them if you want, later we’re going to place our Angular 2 HTML pages in this folder, but for now you can just create and leave an empty index.html in there.

After getting rid of the .jade files we have to tell Node.js that we want to use our index.html instead, we can do that by changing a little bit the code in the routes/index.js file, just open the file and replace its content with the following code:

var express = require('express');
var router = express.Router();
var path = require('path');
 
/* GET home page. */
router.get('/', function(req, res, next) {
  res.sendFile(path.join(__dirname, '../', 'views', 'index.html'));
});
 
module.exports = router;

To make sure it’s working run your project with the command npm start then enter the url http://localhost:3000/ in your browser, it should display an empty page, write something in the index.html you created and refresh the page, if it appears on the browser you’ve done everything right! Now we’re ready to actually start writing some code.

Introducing Socket.IO

As we know, an auction app must be real-time, which means that when a user places a bid all the other connected users must instantly see it, that’s where Socket.IO comes in, it allows Node.js to receive and broadcast events to all connected clients, so when a user places a bid Socket.IO will emit an event to the server which will update the product price and broadcast it to the other users.

Now let’s see how that looks like in code, copy the following code and paste bellow the var app = express(); in your app.js:

var http = require('http');
var server = http.createServer(app);
var io = require('socket.io').listen(server);
server.listen(8000);
io.set("origins", "*:*");
 
var currentPrice = 99;
 
io.on('connection', function (socket) {
	socket.emit('priceUpdate',currentPrice);
	socket.on('bid', function (data) {
		currentPrice = parseInt(data);
		socket.emit('priceUpdate',currentPrice);
		socket.broadcast.emit('priceUpdate',currentPrice);
	});
});

In the first 5 lines I’m just creating the server that Socket.IO will use to send/receive events, then I’m creating a listener to the connection event which will be emitted every time a user connects (we’ll get to that in a moment), you can use the function io.on(event, callback) to create a listener to any event you want, you just need to pass the event as the fist argument and the callback function as the second, when it receives the event the callback will be executed.

In this case when a user connects I’m immediately emitting the event priceUpdate passing the current price, that’s because at this moment the client-side doesn’t have the current price yet, so that’s the first thing we want to send. After that we have to create a listener to the bid event that will update the price and broadcast to the other users. Note that I’m calling socket.emit() and socket.broadcast.emit(), the first one will return the updated price to the user that triggered the event, and the second will do the same to all the other users.

Writing our First Angular 2 Component

Now that we have our server-side ready we can get started with Angular 2, let’s create a folder called app inside public/javascripts/ on our project, that’s where we’re going to put all our Angular 2 code. Now create a file and name it app.component.ts, then paste the following code into it:

import {Component} from 'angular2/core';
 
@Component({
    selector: 'auction-app',
    templateUrl: 'templates/product.html'
})
 
export class AppComponent {
    price: number = 0.0;
    socket = null;
    bidValue = '';
 
    constructor(){
        this.socket = io('http://localhost:8000');
        this.socket.on('priceUpdate', function(data){
            this.price = data;
        }.bind(this));    
    }
 
    bid(){
        this.socket.emit('bid', this.bidValue);
        this.bidValue = '';
    }
}

When declaring a @Component, you have to pass an object containing two attributes selector and templateUrl (or just template), the selector is the name we’re going to use to call our component, in this case it’s auction-app, and the templateUrl is the HTML implementation, you can put your HTML code directly on the object or you can put it in a separated file like I did.

We also have in this code the AppComponent class, that’s kind of the ‘controller’ of our component, in the constructor I’m connecting to the Socket.IO from the server using the method io('http://localhost:8000'), this will emit the connection event we saw before, then I’m creating a listener to the priceUpdate event, the server will emit it right after the connection, remember? When Angular receives the updated price it will assign it to the price variable. This class also contains the bid() function, it will just emit the bid event passing the bidValue.

I forgot to mention that I created the /template folder inside /views, the problem is that our component doesn’t have access to it, to solve this problem we have to make this folder a static route on our server, open your app.js and add this line:

app.use('/templates', express.static(__dirname + '/views/templates/'));

Now let’s take a look on our template, I’ll not show the full HTML code here, just the lines that interact with our AppComponent class.

<h1>$ {{price}}</h1>	        
<input  [(ngModel)]="bidValue" >
<button (click)="bid()">Place Bid</button>

There is not much to explain here, the syntax resembles a lot the first Angular, our input uses the attribute [(ngModel)] to reference the bidValue variable from our class, and the button uses the (click) to call the function bid().

To be able to launch our application we have to create a file called main.ts on the public/javascripts/ folder, just copy/paste the following into the file:

import {bootstrap}    from 'angular2/platform/browser'
import {AppComponent} from './app.component.js'
 
bootstrap(AppComponent);

Finally we can write our index.html, you have already created it in the beginning, remember? It’s located on the /views folder, open it and add the following code:

<html>
  <head>
    <title>Live Auction</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">   
    <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/1.4.5/socket.io.min.js"></script> 
    <script src="scripts/es6-shim/es6-shim.min.js"></script>
    <script src="scripts/systemjs/dist/system-polyfills.js"></script>
    <script src="scripts/angular2/bundles/angular2-polyfills.js"></script>
    <script src="scripts/systemjs/dist/system.src.js"></script>
    <script src="scripts/rxjs/bundles/Rx.js"></script>
    <script src="scripts/angular2/bundles/angular2.dev.js"></script>
    <script src="javascripts/jquery.js"></script>
    <script src="javascripts/bootstrap.min.js"></script>
    <link href="stylesheets/bootstrap.min.css" rel="stylesheet">
    <link href="stylesheets/portfolio-item.css" rel="stylesheet">
    <script>
      System.config({
        packages: {        
          app: {
            format: 'register',
            defaultExtension: 'js'
          }
        }
      });
      System.import('javascripts/app/main.js')
            .then(null, console.error.bind(console));
    </script>
  </head>
  <body>
    <auction-app>Loading...</auction-app>
  </body>
</html>

Once again we have to edit the app.js file to be able to load the scripts directly from the node_modules folder, add the following line like we did before:

app.use('/scripts', express.static(__dirname + '/node_modules/'));

This file is pretty simple, we have a script that configures System.js to load our application, and in the body we can call our component by the selector we specified before: auction-app.

Now we’re done!! You can run your application by opening your terminal and running the command npm start, the result should similar to the gif in the beginning of this tutorial.

Just leave a comment if you have any problems, I’ll be glad to help!!

Recommended for you

24 comments on “Creating a Live Auction App with Angular 2, Node.js and Socket.IO

  1. Gabogabu

    In your System.config (index.html) I believe you can replace

    app: {
         format: 'register',
         defaultExtension: 'js'
    }

    To

    javascript: {
         format: 'register',
         defaultExtension: 'js'
    }

    This way you don’t have to write the “js” extension everytime you import a component.

    Ex:

    System.import('javascripts/app/main')

    instead of:

    System.import('javascripts/app/main.js')

    or

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

    instead of:

    import {AppComponent} from './app.component.js'
  2. Nicholas Diaz

    Hey man thank you for the tutorial. I have ran through about 10 of these tonight (No Joke) but always the same outcome… I have some error that keeps me from the end goal.

    This is seems to be the most simple and easiest to build on so I would really like to get this one working. I went through your tutorial about 5 times and I still have these same errors.

    When I run npm start the loaded page just says loading as in the html file. In the console I get,

    localhost/:12 GET http://localhost:3000/javascripts/jquery.js 
    localhost/:14 GET http://localhost:3000/stylesheets/bootstrap.min.css 
    localhost/:13 GET http://localhost:3000/javascripts/bootstrap.min.js 
    localhost/:15 GET http://localhost:3000/stylesheets/portfolio-item.css 
    angular2-polyfills.js:1152 DEPRECATION WARNING: 'enqueueTask' is no longer supported and will be removed in next major release. Use addTask/addRepeatingTask/addMicroTask
    system.src.js:1068 GET http://localhost:3000/traceur 404 (Not Found)
    angular2-polyfills.js:1243 Error: XHR error (404 Not Found) loading http://localhost:3000/traceur(…)

    and in the term

    GET / 304 7.672 ms - -
    GET /scripts/es6-shim/es6-shim.min.js 304 4.093 ms - -
    GET /scripts/systemjs/dist/system-polyfills.js 304 3.990 ms - -
    GET /scripts/angular2/bundles/angular2-polyfills.js 304 4.223 ms - -
    GET /scripts/systemjs/dist/system.src.js 304 4.262 ms - -
    GET /scripts/rxjs/bundles/Rx.js 304 4.249 ms - -
    GET /scripts/angular2/bundles/angular2.dev.js 304 4.225 ms - -
    GET /stylesheets/bootstrap.min.css 404 2.297 ms - 1093
     
     
    GET /scripts/es6-shim/es6-shim.map 304 0.560 ms - -
    GET /scripts/systemjs/dist/system-polyfills.js.map 304 0.568 ms - -
    GET /javascripts/app/main.js 404 0.604 ms - 1093

    Any ideas?

    1. Vicheanak

      has anyone got any solutions to this problem yet? I have ran across the same issue here.
      Thanks

  3. Nicholas Diaz

    I think my problem here is that the typescript in all of the applications I have made tonight are not being compiled to js.

    But why are your compiling when you run npm start and mine are not after following your project verbatim?

        1. Nicholas Diaz

          I changed this,

             "scripts": {
              "postinstall": "npm run typings install",
              "tsc": "tsc",
              "tsc:w": "tsc -w",
              "start": "concurrent \"node ./bin/www\" \"npm run tsc:w\"",
              "typings" : "typings"
            },

          I now I get this

           wunortd@0.0.0 start /Applications/AMPPS/www/wunortd
          &gt; concurrent "node ./bin/www" "npm run tsc:w"
           
          [1]
          [1] &gt; wunortd@0.0.0 tsc:w /Applications/AMPPS/www/wunortd
          [1] &gt; tsc -w
          [1]
           
           
          node_modules/angular2/src/core/change_detection/parser/locals.d.ts(3,14): error TS2304: Cannot find name 'Map'.
          [1] node_modules/angular2/src/core/change_detection/parser/locals.d.ts(4,42): error TS2304: Cannot find name 'Map'.
          [1] node_modules/angular2/src/core/debug/debug_node.d.ts(14,13): error TS2304: Cannot find name 'Map'.
          [1] node_modules/angular2/src/core/debug/debug_node.d.ts(24,17): error TS2304: Cannot find name 'Map'.
          [1] node_modules/angular2/src/core/debug/debug_node.d.ts(25,17): error TS2304: Cannot find name 'Map'.
          [1] node_modules/angular2/src/core/di/provider.d.ts(436,103): error TS2304: Cannot find name 'Map'.
          [1] node_modules/angular2/src/core/di/provider.d.ts(436,135): error TS2304: Cannot find name 'Map'.
          [1] node_modules/angular2/src/core/render/api.d.ts(13,13): error TS2304: Cannot find name 'Map'.
          [1] node_modules/angular2/src/core/render/api.d.ts(14,84): error TS2304: Cannot find name 'Map'.
          [1] node_modules/angular2/src/facade/collection.d.ts(1,25): error TS2304: Cannot find name 'MapConstructor'.
          [1] node_modules/angular2/src/facade/collection.d.ts(2,25): error TS2304: Cannot find name 'SetConstructor'.
          [1] node_modules/angular2/src/facade/collection.d.ts(4,27): error TS2304: Cannot find name 'Map'.
          [1] node_modules/angular2/src/facade/collection.d.ts(4,39): error TS2304: Cannot find name 'Map'.
          [1] node_modules/angular2/src/facade/collection.d.ts(7,9): error TS2304: Cannot find name 'Map'.
          [1] node_modules/angular2/src/facade/collection.d.ts(8,30): error TS2304: Cannot find name 'Map'.
          [1] node_modules/angular2/src/facade/collection.d.ts(11,43): error TS2304: Cannot find name 'Map'.
          [1] node_modules/angular2/src/facade/collection.d.ts(12,27): error TS2304: Cannot find name 'Map'.
          [1] node_modules/angular2/src/facade/collection.d.ts(14,23): error TS2304: Cannot find name 'Map'.
          [1] node_modules/angular2/src/facade/collection.d.ts(15,25): error TS2304: Cannot find name 'Map'.
          [1] node_modules/angular2/src/facade/collection.d.ts(95,41): error TS2304: Cannot find name 'Set'.
          [1] node_modules/angular2/src/facade/collection.d.ts(96,22): error TS2304: Cannot find name 'Set'.
          [1] node_modules/angular2/src/facade/collection.d.ts(97,25): error TS2304: Cannot find name 'Set'.
          [1] node_modules/angular2/src/facade/lang.d.ts(13,17): error TS2304: Cannot find name 'Map'.
          [1] node_modules/angular2/src/facade/lang.d.ts(14,17): error TS2304: Cannot find name 'Set'.
          [1] node_modules/angular2/src/facade/lang.d.ts(78,59): error TS2304: Cannot find name 'Map'.
          [1] node_modules/angular2/src/facade/promise.d.ts(1,10): error TS2304: Cannot find name 'Promise'.
          [1] node_modules/angular2/src/facade/promise.d.ts(3,14): error TS2304: Cannot find name 'Promise'.
          [1] node_modules/angular2/src/facade/promise.d.ts(8,32): error TS2304: Cannot find name 'Promise'.
          [1] node_modules/angular2/src/facade/promise.d.ts(9,38): error TS2304: Cannot find name 'Promise'.
          [1] node_modules/angular2/src/facade/promise.d.ts(10,35): error TS2304: Cannot find name 'Promise'.
          [1] node_modules/angular2/src/facade/promise.d.ts(10,93): error TS2304: Cannot find name 'Promise'.
          [1] node_modules/angular2/src/facade/promise.d.ts(11,34): error TS2304: Cannot find name 'Promise'.
          [1] node_modules/angular2/src/facade/promise.d.ts(12,32): error TS2304: Cannot find name 'Promise'.
          [1] node_modules/angular2/src/facade/promise.d.ts(12,149): error TS2304: Cannot find name 'Promise'.
          [1] node_modules/angular2/src/facade/promise.d.ts(13,43): error TS2304: Cannot find name 'Promise'.
          [1] node_modules/angular2/src/platform/browser/browser_adapter.d.ts(75,33): error TS2304: Cannot find name 'Map'.
          [1] node_modules/angular2/src/platform/dom/dom_adapter.d.ts(85,42): error TS2304: Cannot find name 'Map'.
          [1] node_modules/rxjs/CoreOperators.d.ts(22,67): error TS2304: Cannot find name 'Promise'.
          [1] node_modules/rxjs/CoreOperators.d.ts(72,67): error TS2304: Cannot find name 'Promise'.
          [1] node_modules/rxjs/CoreOperators.d.ts(77,31): error TS2304: Cannot find name 'PromiseConstructor'.
          [1] node_modules/rxjs/CoreOperators.d.ts(77,54): error TS2304: Cannot find name 'Promise'.
          [1] node_modules/rxjs/Observable.d.ts(65,67): error TS2304: Cannot find name 'PromiseConstructor'.
          [1] node_modules/rxjs/Observable.d.ts(65,88): error TS2304: Cannot find name 'Promise'.
          [1] node_modules/rxjs/Observable.d.ts(72,84): error TS2304: Cannot find name 'Promise'.
          [1] node_modules/rxjs/Observable.d.ts(77,38): error TS2304: Cannot find name 'Promise'.
          [1] node_modules/rxjs/Observable.d.ts(100,66): error TS2304: Cannot find name 'Promise'.
          [1] node_modules/rxjs/Observable.d.ts(154,66): error TS2304: Cannot find name 'Promise'.
          [1] node_modules/rxjs/Observable.d.ts(159,31): error TS2304: Cannot find name 'PromiseConstructor'.
          [1] node_modules/rxjs/Observable.d.ts(159,54): error TS2304: Cannot find name 'Promise'.
          [1] public/javascripts/app/app.component.ts(14,23): error TS2304: Cannot find name 'io'.
          [1] public/javascripts/main.ts(2,28): error TS2307: Cannot find module './app.component.js'.
          [1] 1:00:19 PM - Compilation complete. Watching for file changes.
    1. Leonardo Jines

      You’re right, I’m sorry, I forgot to mention it on the tutorial, but that is not what caused the compilation problem, the project works fine even without bootstrap and jQuery.

      1. Nicholas Diaz

        Oh I agree. I was just mentioning it because when troubleshooting it is quite confusing when there are broken links in the program on top of whatever other problem I was having. Thanks for all your help. This is actually the cleanest startup example of mean angular2 I could find. I think it would be really awesome if you could clarify the things you forgot as well as an explanation of typescript. I suspect typescript was my problem and it will probably cause a lot of people headaches in the near future.

  4. ferrufino

    Bro, you’ve many things missing that makes it annoying for starters. Make that clear and point to your github repo or add the missing files and details as of product.html.

    Thanks for the tuto

  5. Kaneika

    I built an app and i’m planning to make a real time battle with Angular 2 and laravel. For example, you hit the “attack” button, and your opponent see his life going down in real time.

Leave a Reply

Your email address will not be published. Required fields are marked *

Obs: Use the tag <pre lang="LANGUAGE"> to include code blocks to your comment.
Example: <pre lang="javascript"> console.log('Test'); </pre>