Angular2 Gulp, Typescript, NodeJS and Express

Introduction

Basic Angular seed application created with Quick start application given on angular website (https://angular.io/docs/ts/latest/quickstart.html). It uses Typescript, Gulp for tasks like build, compile, install etc and ExpressJS as server. The application basically involve both client(Angular2) and server(Express and Node).

Github Directory - https://github.com/moizKachwala/angular2-express

Getting Started

Before we start make sure you have NodeJS installed in your machine.If not then you can download and install NodeJS from (https://nodejs.org/en/)

You need to install typings globally by following command.

npm install --global typings

Project Setup

Create a new directory and install dependencies. This will create a folder and will add the package.json in the folder.

mkdir Angular2-express
cd Angular2-express
npm init -y

Now Add dependencies to your package.json.

"dependencies": {
    "angular2": "^2.0.0-beta.17",
    "es6-promise": "^3.2.1",
    "es6-shim": "^0.35.1",
    "express": "^4.13.4",
    "gulp-concat": "^2.6.0",
    "reflect-metadata": "^0.1.2",
    "rxjs": "^5.0.0-beta.6",
    "systemjs": "^0.19.29",
    "zone": "^0.3.4"
  }

And your dev dependencies.

 "devDependencies": {
    "concurrently": "^2.1.0",
    "del": "^2.2.0",
    "gulp": "^3.9.1",
    "gulp-concat": "^2.6.0",
    "gulp-nodemon": "^2.1.0",
    "gulp-sourcemaps": "^1.6.0",
    "gulp-tslint": "^4.3.3",
    "gulp-typescript": "^2.13.6",
    "gulp-typings": "^2.0.0",
    "run-sequence": "^1.2.1",
    "tslint": "^3.5.0"
  }

Now install all the dependencies by opening the created folder in command prompt and fire following command.

npm install

Once you have done npm install, you will find a node_modules folder in your directory. This holds all the dependencies that we added in package.json. Now create a file gulpfile.ts and two folders in your project client and server.

Our folder structure will look something like this.

angular2-express
      |-- node_modules
      |-- client
      |-- server
      gulpfile.ts
      package.json

Now as we have taken gulpfile.ts we need to tell how to compile the ts file by adding tsconfig.json. This also set the outDir which we will be handling later. We have also added exclude option so we don't want typescript to go and compile those directives/files.

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

So now we have done with the basic structure we will start implementing Server for our application.

Server Setup

First thing we need to do is create a tsconfig.json inside server folder.This will tell our typescript compiler how to compile our .ts extension files.

Let's create tsconfig.json in /server folder with following content.

{
  "compilerOptions": {
    "target": "ES5",
    "module": "commonjs",
    "sourceMap": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "removeComments": false,
    "noImplicitAny": false
  },
  "exclude": [
    "typings"
  ]
}

In our server directory, We need to add typings.json. Typings is the simple way to manage and install TypeScript definitions.

{
  "globalDependencies": {
    "express": "registry:dt/express#4.0.0+20160317120654",
    "express-serve-static-core": "registry:dt/express-serve-static-core#0.0.0+20160602151406",
    "mime": "registry:dt/mime#0.0.0+20160316155526",
    "node": "registry:dt/node#6.0.0+20160608083720",
    "serve-static": "registry:dt/serve-static#0.0.0+20160606155157"
  }
}

Now go to command prompt, and navigate to server folder and execute the following command. This will install the typescript definations for our server.

typings install

You will notice that it created a directory typings inside /server directory

Now lets create server.ts inside /server directory.

import express = require('express');
import path = require('path');
var port: number = process.env.PORT || 3000;
var app = express();

app.use('/app', express.static(path.resolve(__dirname, 'app')));
app.use('/libs', express.static(path.resolve(__dirname, 'libs')));

var renderIndex = (req: express.Request, res: express.Response) => {
    res.sendFile(path.resolve(__dirname, 'index.html'));
}

app.get('/*', renderIndex);

var server = app.listen(port, function() {
    var host = server.address().address;
    var port = server.address().port;
    console.log('This express app is listening on port:' + port);
});

We are done with the server side work. Our directory structure will look like this.

angular2-express
      |-- node_modules
      |-- client
      |-- server
           |-- typings
           |-- server.ts
           |-- tsconfig.json
           |-- typings.json
      gulpfile.ts
      package.json
      tsconfig.json

Client Setup

Let's drive the Client Side Application with Angular2.

In /client directory add index.html

<html>

  <head>
    <title>Angular 2 Quickstart with Express</title>
    <base href="/"/>

    <!-- 1. Load libraries -->
    <script src="libs/angular2/bundles/angular2-polyfills.js"></script>
    <script src="libs/systemjs/dist/system.src.js"></script>
    <script src="libs/rxjs/bundles/Rx.js"></script>
    <script src="libs/angular2/bundles/angular2.dev.js"></script>
    <script src="libs/angular2/bundles/router.dev.js"></script>

    <!-- 2. Configure SystemJS -->
    <script>
      System.config({
        packages: {        
          app: {
            format: 'register',
            defaultExtension: 'js'
          }
        }
      });
      System.import('app/main')
            .then(null, console.error.bind(console));
    </script>

  </head>
  <!-- 3. Display the application -->
  <body>
    <my-app>Loading...</my-app>
  </body>

</html>

As we are developing our application in Typescript, we need to add tsconfig.json

{
  "compilerOptions": {
    "target": "ES5",
    "module": "system",
    "moduleResolution": "node",
    "sourceMap": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "removeComments": false,
    "noImplicitAny": false
  },
  "exclude": [
    "typings"
  ]
}

We will also need some typings to be installed for our application. Add typings.json like we added for server

{
  "globalDependencies": {
    "es6-shim": "registry:dt/es6-shim#0.31.2+20160602141504"
  }
}

Once you have added typings.json we need to install typings for our client application. Navigate to client folder in command prompt and run following command.

typings install

Angular2 Code

Now let's start adding our angular code. create a directory named app inside /client folder.

Note : I am just going to cover the basic angular code from quick start application. The main agenda for this post is to use angular with Express server. Please go to my github to get the finished angular Quick start application.

Now in /client/app, Create our first component app.component.ts. This is our root component for our application

import {Component} from 'angular2/core';
@Component({
  selector: 'my-app',
  template: '<h1>My First Angular 2 App</h1>'
})
export class AppComponent { }

Now we need to add a bootstrap class which will initialize our application. Add main.ts inside /app directory.

import {bootstrap} from 'angular2/platform/browser';

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

bootstrap(AppComponent);

Till this point our project structure should look like this.

angular2-express
      |-- node_modules
      |-- client
           |-- app
                |-- app.component.ts
                |-- main.ts
           |-- typings
           |-- index.html
           |-- tsconfig.json
           |-- typings.json
      |-- server
           |-- typings
           |-- server.ts
           |-- tsconfig.json
           |-- typings.json
      gulpfile.ts
      package.json
      tsconfig.json

GulpFile (Task Management)

Now we have our client and server setup we need to add a root file which would clean, compile and build our application. Modify gulpfile.ts

"use strict";

const gulp = require("gulp"),
        del = require("del"),
        tsc = require("gulp-typescript"),
        sourcemaps = require('gulp-sourcemaps'),
        tsProject = tsc.createProject("tsconfig.json"),
        tslint = require('gulp-tslint'),
        concat = require('gulp-concat'),
        runSequence = require('run-sequence'),
        nodemon = require('gulp-nodemon'),
        gulpTypings = require("gulp-typings");

/**
 * Remove build directory.
 */
gulp.task('clean', (cb) => {
    return del(["build"], cb);
});

/**
 * Build Express server
 */
gulp.task('build:server', function () {
    var tsProject = tsc.createProject('server/tsconfig.json');
    var tsResult = gulp.src('server/**/*.ts')
        .pipe(sourcemaps.init())
        .pipe(tsc(tsProject))
    return tsResult.js
        .pipe(concat('server.js'))
        .pipe(sourcemaps.write())
        .pipe(gulp.dest('build'))
});

gulp.task('build:client', function(){
    var tsProject = tsc.createProject('client/tsconfig.json');
    var tsResult = gulp.src('client/**/*.ts')
        .pipe(sourcemaps.init())
        .pipe(tsc(tsProject))
    return tsResult.js
        .pipe(sourcemaps.write())
        .pipe(gulp.dest('build'))
});

/**
 * Lint all custom TypeScript files.
 */
gulp.task('tslint', () => {
    return gulp.src("client/app/**/*.ts")
        .pipe(tslint())
        .pipe(tslint.report('prose'));
});


/**
 * Compile TypeScript sources and create sourcemaps in build directory.
 */
gulp.task("compile", ["tslint"], () => {
    let tsResult = gulp.src("client/**/*.ts")
        .pipe(sourcemaps.init())
        .pipe(tsc(tsProject));
    return tsResult.js
        .pipe(sourcemaps.write("."))
        .pipe(gulp.dest("build"));
});

/**
 * Copy all resources that are not TypeScript files into build directory.
 */
gulp.task("resources", () => {
    return gulp.src(["client/**/*", "!**/*.ts"])
        .pipe(gulp.dest("build"));
});

/**
 * Copy all required libraries into build directory.
 */
gulp.task("libs", () => {
    return gulp.src([
        'angular2/bundles/angular2-polyfills.js',
        'systemjs/dist/system.src.js',
        'rxjs/bundles/Rx.js',
        'angular2/bundles/angular2.dev.js',
        'angular2/bundles/router.dev.js'
    ], {cwd: "node_modules/**"}) /* Glob required here. */
        .pipe(gulp.dest("build/libs"));
});

/**
 * Watch for changes in TypeScript, HTML and CSS files.
 */
gulp.task('watch', function () {
    gulp.watch(["client/**/*.ts"], ['compile']).on('change', function (e) {
        console.log('TypeScript file ' + e.path + ' has been changed. Compiling.');
    });
    gulp.watch(["client/**/*.html", "client/**/*.css"], ['resources']).on('change', function (e) {
        console.log('Resource file ' + e.path + ' has been changed. Updating.');
    });
});

/**
 * Install typings for server and client.
 */
gulp.task("installTypings",function(){
    var stream = gulp.src(["./server/typings.json","./client/typings.json"])
        .pipe(gulpTypings(null)); //will install all typingsfiles in pipeline.
    return stream; // by returning stream gulp can listen to events from the stream and knows when it is finished.
});

/**
 * Start the express server with nodemon
 */
gulp.task('start', function () {
    nodemon({ script: 'build/server.js'
        , ext: 'html js'
        , ignore: ['ignored.js']
        , tasks: ['tslint'] })
        .on('restart', function () {
            console.log('restarted!')
        });
});

/**
 * Build the project.
 * 1. Clean the build directory
 * 2. Build Express server
 * 3. Build the Angular app
 * 4. Copy the resources
 * 5. Copy the dependencies.
 */

gulp.task("build", function (callback) {
    runSequence('clean', 'build:server', 'build:client', 'resources', 'libs', callback);
});

Running the code

npm install
npm build
npm start

Ts Lint

Create a file tslint.json in the root directory.

{
  "rules": {
    "class-name": true,
    "curly": true,
    "eofline": false,
    "forin": true,
    "indent": [
      true,
      4
    ],
    "label-position": true,
    "label-undefined": true,
    "max-line-length": [
      true,
      200
    ],
    "no-arg": true,
    "no-bitwise": true,
    "no-console": [
      true,
      "debug",
      "info",
      "time",
      "timeEnd",
      "trace"
    ],
    "no-construct": true,
    "no-debugger": true,
    "no-duplicate-key": true,
    "no-duplicate-variable": true,
    "no-empty": false,
    "no-eval": true,
    "no-string-literal": false,
    "no-trailing-whitespace": true,
    "no-unused-variable": false,
    "no-unreachable": true,
    "no-use-before-declare": true,
    "one-line": [
      true,
      "check-open-brace",
      "check-catch",
      "check-else",
      "check-whitespace"
    ],
    "radix": true,
    "semicolon": true,
    "triple-equals": [
      true,
      "allow-null-check"
    ],
    "variable-name": false,
    "whitespace": [
      true,
      "check-branch",
      "check-decl",
      "check-operator",
      "check-separator"
    ]
  }
}

The final project structure will look like this.

angular2-express
      |-- node_modules
      |-- client
           |-- app
                |-- app.component.ts
                |-- main.ts
           |-- typings
           |-- index.html
           |-- tsconfig.json
           |-- typings.json
      |-- server
           |-- typings
           |-- server.ts
           |-- tsconfig.json
           |-- typings.json
      gulpfile.ts
      package.json
      tsconfig.json
      tslint.json

Finally

Stay tuned for adding mongoDB support in this application.