Full-Stack Engine

Creating a new project with Angular (4/5) and Sails.js

Update Nov 17, 2017: Added live update and reload for development environment using angular-cli's ng serve. Also added sails.js debug scripts.

Update Nov 6, 2017: Updated to Angular 5 version, replaced systemjs with angular cli for bundling and ahead of time compilation.

I wanted to create a new project using one of my favorites front-end frameworks: Angular 4 and in the same time learn a new back-end framework in Node.js, for what I chose Sails.js, because of its many cool features that make life easier, like the cli assistant to create new APIs. However, I found the process of making both working together, correctly, not so straightforward as one might think.

Searching the web I came across with an excellent tutorial and starter app from Sharpten blog: Integrating Angular (4) with Sails.js. I'm taking it as a base for my starter template and adding some more features, like how to use the Angular 4/5 router with Sails.js in order any overrides the other, or how to get the Angular cli working together with sails cli.

The full source repo is here.

If you prefer React js, I added another guide using Sails.js too; you can read int this post.

I'm going to start from the basic project app you get when running sails cli to create a new project (sails new project) and then add the plumbing work to get Angular set up.

The first step is getting the npm additional packages (In addition to the basic sails.js setup) we need to get angular working alongside with Sails:

  • angular: (version 5) Angular libraries
  • express: Used to get static file access to the node_nodules directory
  • sails-hook-auto reload: Extra nice feature to auto-reload on server code changes
  • typescript: Transpiler for our typescript code conversion into ES5
  • angular/cli: The actual Angular CLI tool
  • angular/compiler-cli: The module loader and bundler (based on webpack) for our typescript classes and assets.

The complete package.json is below.

  "name": "sails-angular-starter",
  "private": true,
  "version": "0.2.0",
  "description": "Sails + Angular 4 sample application",
  "keywords": [],
  "dependencies": {
    "@angular/common": "5.0.0",
    "@angular/core": "5.0.0",
    "@angular/forms": "5.0.0",
    "@angular/http": "5.0.0",
    "@angular/platform-browser": "5.0.0",
    "@angular/platform-browser-dynamic": "5.0.0",
    "@angular/router": "5.0.0",
    "@angular/upgrade": "5.0.0",
    "angular-in-memory-web-api": "~0.5.1",
    "core-js": "^2.4.1",
    "ejs": "2.5.7",
    "es6-promise": "^4.1.1",
    "es6-shim": "^0.35.3",
    "express": "^4.14.0",
    "grunt": "1.0.1",
    "grunt-contrib-clean": "1.1.0",
    "grunt-contrib-coffee": "2.0.0",
    "grunt-contrib-concat": "1.0.1",
    "grunt-contrib-copy": "1.0.0",
    "grunt-contrib-cssmin": "2.2.1",
    "grunt-contrib-jst": "1.0.0",
    "grunt-contrib-less": "1.4.1",
    "grunt-contrib-uglify": "3.1.0",
    "grunt-contrib-watch": "1.0.0",
    "grunt-sails-linker": "~1.0.4",
    "grunt-sync": "0.7.0",
    "include-all": "~4.0.3",
    "npm-run-all": "^4.1.1",
    "rc": "1.2.2",
    "reflect-metadata": "^0.1.2",
    "rxjs": "5.5.2",
    "sails": "~0.12.13",
    "sails-disk": "~0.10.9",
    "sails-hook-autoreload": "^1.0.0",
    "zone.js": "0.8.18"
  },
  "scripts": {
    "start": "npm-run-all --parallel open:client dev",
    "start:debug" : "npm-run-all --parallel open:client debug",
    "open:client" : "ng serve --open",
    "build": "npm run build:prod",
    "build:dev": "ng build --deploy-url=dist",
    "build:prod": "ng build --prod --deploy-url=dist",
    "build:aot": "ng build --aot --deploy-url=dist",
    "clean": "rimraf assets/dist",
    "prod": "sails lift --prod",
    "dev": "sails lift --dev",
    "debug": "node --inspect app.js"
  },
  "main": "app.js",
  "repository": {
    "type": "git",
    "url": "git://github.com/sepineda/sails-angular-starter.git"
  },
  "devDependencies": {
    "@angular/cli": "^1.3.2",
    "@angular/compiler": "^5.0.0",
    "@angular/compiler-cli": "^5.0.0",
    "@types/core-js": "^0.9.41",
    "npm-run-all": "^4.1.1",
    "typescript": "~2.4.2"
  },
  "author": "sepineda",
  "license": ""
}

To get typescript and angular cli command tools working,

npm install typescript @angular/cli --save-dev

and angular compiler cli,

npm install @angular/compiler @angular/compiler-cli --save-dev

Next, we need to install all the dependencies,

npm install

Add the following tsconfig.json settings,

  "compilerOptions": {
    "module": "commonjs",
    "target": "es5",
    "moduleResolution": "node",
    "sourceMap": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "lib": [ "es2015", "dom" ],
    "noImplicitAny": true,
    "suppressImplicitAnyIndexErrors": true
  },
  "exclude": [
      ".tmp",
      "node_modules",
      "typings"
  ]
}

Moreover, these lines to angular-cli.json,

  "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
  "project": {
    "name": "sails-angular-starter"
  },
  "apps": [
    {
      "root": "assets",
      "outDir": "assets/dist/",
      "assets": [
        "assets",
        "favicon.ico"
      ],
      "main": "app/main.ts",
      "polyfills": "app/polyfills.ts",
      "tsconfig": "../tsconfig.json",
      "prefix": "app",
      "scripts": []
    }
  ],
  "e2e": {
    "protractor": {
      "config": "./protractor.conf.js"
    }
  },
  "defaults": {
    "styleExt": "css",
    "component": {}
  }
}

Now, we need to modify the layout.ejs,

<%- body %>

Which is going to include all the contents we specify in the homepage,

<!DOCTYPE html>
<html>

<!-- webpack generate html -->
<%- partial('../assets/dist/index.html') %>

</html>

The partial directive here is loading the index.html generated by the angular-cli compiler using webpack behind the scenes. What it does is taking the original HTML inside assets folder,

<head>
  <base href="/">
  <title>Sails js with Angular 5</title>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
</head>

<body>
  <my-app></my-app>
</body>

Then add the script tags for the bundles generated in the assets compilation process.

You can optionally debug the sails.js code (back-end code) using Chrome DevTools with the scripts "start:debug" and "debug" in the package.json scripts section. For a complete step by step guide, take a look at this guide.

Finally, we're going to set up the sails router at routes.js to redirect an incoming request to our homepage, like this,

module.exports.routes = {
  '/': {
    view: 'homepage',
    skipAssets: true
  },

  '/#*': {
    view: 'homepage',
    skipAssets: true
  }
};

Note that the second route uses hash location strategy, this is because we are going to set our custom routes in angular, specifically in app.module.ts,

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { RouterModule } from '@angular/router';

import { AppComponent } from './app.component';
import { HomeComponent } from './components/home/home.component';

@NgModule({
  declarations: [
    AppComponent,
    HomeComponent
  ],
  imports: [
    BrowserModule,
    RouterModule.forRoot([
      { path: '', redirectTo: 'home', pathMatch: 'full' },
      { path: 'home', component: HomeComponent }
    ], {useHash: true})
  ],
  bootstrap: [AppComponent],
})
export class AppModule { }

Now we're ready to sails lift!.

Here some commands you can use to run the app,

  • npm run start: Build the project's assets (front-end), starts running sails and open a new window with ng server watching code for updates in real time.
  • npm run build: Builds the project's assets in the development environment. Add "build:prod" for production and build:aot for ahead of time compilation.
  • npm run dev: Starts running sails in the dev environment (run "npm run build:dev before).
  • npm run prod: Runs sails in a production environment. You should use this for deploying to a public server (run "npm build:prod" before).
Author image
Costa Rica
Passionate Software Developer with full-stack development experience.