Category Archives: Angular

Getting Started with Angular CLI

One of the main things I liked about Rails when I first started using it was its command line interface. You get so much leverage by being able to scaffold a whole resource with just one command.

I was very happy when I learned about Angular CLI, which is currently in beta. I’ve been using Yeoman for Angular 1 but as far as I’ve been able to tell so far the Yeoman generators aren’t quite caught up with Angular 2. It’s nice to have a tool for Angular 2 that helps me understand how to structure my project, how to get tests set up, and all that nuts-and-bolts stuff that I don’t want to have to think about when I’m trying to quickly get familiar with a new technology.

In this tutorial I show you how to spin up a sample Angular 2 project on a Rails backend. Since the focus is on Angular and not Rails, I’ve provided a Rails repo for you so you don’t have to think about the Rails part too much.

Setting up the project

You can use my repo as a starting point. We’ll blow away and then rebuild the Angular portion but keep the Rails portion.

$ git clone git@github.com:jasonswett/dream_cars.git
$ cd dream_cars

CD into the project directory, create a new branch for this tutorial, then blow away the Angular code (which I always keep in the “client” directory as my own personal convention).

$ cd dream_cars
$ git checkout -b tutorial
$ rm -rf client
$ git commit -a -m"Delete contents of client directory."

Install the angular-cli package globally.

$ npm install -g angular-cli

Spin up a new Angular 2 project using ng new.

$ ng new dream-cars

Since for my Angular/Rails projects I always keep my Angular code in client, let’s rename dream-cars to client.

$ mv dream-cars client
$ cd client
$ rm -rf .git

We can run ng serve to start a server on port 4200.

$ ng serve

If you navigate to http://localhost:4200, you should see a message that says, “dream-cars works!”

Adding the Car component

Generate the car component.

$ ng generate route car

Modify your src/app/dream-cars.component.html to look like this. What we’re doing here, of course, is adding some navigation links.

<h1>{{title}}</h1>

<nav>
  <ul>
    <li><a [routerLink]="['/']">Home</a></li>
    <li><a [routerLink]="['/car']">Cars</a></li>
  </ul>
</nav>

<router-outlet></router-outlet>

If you go to your browser and click on the “Cars” link, you should see “car works!”.

Pulling in some data from the server

I already have migrations and seed data set up for you in the Rails project. All you have to do is run rake db:setup and start the rails server.

$ rake db:setup
$ rails server

If you navigate to http://localhost:3000/api/cars.json, you should see some data.

Modify src/app/+car/car.component.ts to look like this.

import { Component, OnInit } from '@angular/core';
import { Http, HTTP_PROVIDERS } from '@angular/http';

@Component({
  moduleId: module.id,
  selector: 'app-car',
  templateUrl: 'car.component.html',
  styleUrls: ['car.component.css']
})
export class CarComponent implements OnInit {
  cars: any;

  constructor(public http: Http) {}

  ngOnInit() {
    this.http.get('/api/cars.json')
      .subscribe(response => this.cars = response.json());
  }

}

Notice the cars: any part. That’s probably not the way that should be done. Frankly, I don’t know the right way to do that yet. I’m learning too.

In order for Http to work, you’ll have to add it to your application’s bootstrap in src/main.ts.

import { bootstrap } from '@angular/platform-browser-dynamic';
import { enableProdMode } from '@angular/core';
import { DreamCarsAppComponent, environment } from './app/';
import { Http, HTTP_PROVIDERS } from '@angular/http';

if (environment.production) {
  enableProdMode();
}

bootstrap(DreamCarsAppComponent, [HTTP_PROVIDERS]);

If you now navigate to http://localhost:4200/car, you’ll get an error. This is because our HTTP request is trying to go to http://localhost:4200/api/cars.json, which doesn’t exist. We need http://localhost:3000/api/cars.json.

To make our Angular server on port 4200 talk to our Rails server on port 3000, we can use a proxy. Kill your Angular server process and run this instead:

$ ng serve --proxy http://localhost:3000

This tells the server to route all XHR requests to port 3000. If you refresh the browser, you should no longer see the error.

There’s another problem, though. If you look at the log output for both the Angular server and the Rails server, you’ll see some wacky errors flying across the screen. This has to do with source map files not being included in /dist. You can fix the problem by modifying angular-cli-build.js in the following way.

/* global require, module */

var Angular2App = require('angular-cli/lib/broccoli/angular2-app');

module.exports = function(defaults) {
  return new Angular2App(defaults, {
    vendorNpmFiles: [
      'systemjs/dist/system-polyfills.js',
      'systemjs/dist/system.src.js',
      'zone.js/dist/*.{js,js.map}',
      'es6-shim/es6-shim.js',
      'reflect-metadata/*.{js,js.map}',
      'rxjs/**/*.{js,js.map}',
      '@angular/**/*.{js,js.map}'
    ]
  });
};

With that out of the way, let’s edit our src/app/+car/car.component.html to allow our car data to be shown.

<ul>
  <li *ngFor="let car of cars">
    {{ car.year }}
    {{ car.make }}
    {{ car.model }}
  </li>
</ul>

<car-form></car-form>

If you navigate to http://localhost:4200/car, you should now see something like the following, with a list of cars.

dream cars

There’s a lot I’ve skipped in this tutorial. There are probably some things I’ve done improperly. This is meant to be a quick and dirty intro to Angular CLI so you can get a real full-stack application functioning so you can start tinkering and making progress with Angular 2.

Angular/Rails CRUD Tutorial Part 1

The idea

I’ve decided to write a very long, comprehensive, multi-part tutorial. I came up with a fictional app called Cadence, a music lesson scheduling app. I wanted to think of something that would be a fitting use case for a single-page application, and I think a calendar-based application would make sense.

The tutorial starts with Angular by itself, no Rails.

The initial version

The initial version of this application will not be very impressive. You’ll be able to create “appointments”, each of which just consists of a date and client name. When you click Save, the appointment details will be added to a list of appointments on the screen. That’s about it. But as we’ll see, just doing that much is involved enough that we wouldn’t really want to bite off more than that for starters.

Since this initial version of the app is just Angular without Rails, we’ll be using localStorage as the data store.

We won’t even worry about tests yet in this initial version. We’ll first write a version without tests, because we already have enough to deal with without bringing tests into the picture, and then we’ll make a second pass with tests.

What you’ll learn

  • How to create an Angular project using Angular CLI
  • How to build components (and nested components)
  • How to use *ngFor to display lists
  • How to use services for inter-component interaction
  • How to use localStorage with Angular

We’re going to initialize this project using Angular CLI. We’re going to install Angular CLI using NPM, so if you don’t have NPM installed yet, you’ll need to install it before proceeding. (If you’re not familiar, NPM is a package manager for JavaScript. It’s kind of analogous to RubyGems for Ruby.)

$ npm install -g @angular/cli

The Angular CLI executable is ng. We can initialize our project with the ng new command, like this:

$ ng new cadence --prefix cadence

The format of the ng new command is ng new <your project name>. In this case we’re calling our project cadence.

Let’s cd into the cadence directory since everything we’ll be doing from this point on will happen inside this directory.

$ cd cadence

Angular CLI can also serve our new Angular application with ng serve.

$ ng serve

After the server spins up, you can visit localhost:4200 and you should see a screen that looks like this:

Creating some components

In a moment we’ll be creating some components. What’s a component? I’ll try to give the simplest answer possible.

Imagine you have an application that has a datepicker thingy that you use in multiple places. Rather than pasting in the full datepicker code wherever you want the datepicker or referring to some include file everywhere you want the datepicker, you could just do something like this:

<my-datepicker></my-datepicker>

If you have an Angular component whose selector matches my-datepicker, Angular will go find the template for that component as well as the TypeScript code that defines the behavior for that component. To me this seems like a pretty natural and convenient way to invoke some behavior.

The Angular Style Guide recommends that we give our selectors application-specific prefixes. The idea is to avoid naming conflicts. So for our app called Cadence, we could put a cadence- prefix on each of our components. For example, we might have cadence-calendar or cadence-datepicker.

The people who built Angular CLI were apparently aware of this style guide recommendation because they built this kind of prefixing into Angular CLI. Angular CLI give us the ability to generate components via the ng generate component command. Each time we do this, Angular CLI will automatically give the component a cadence- prefix.

We can use the ng generate component command to generate our first component. Let’s generate a calendar component. As a reminder, this application we’re creating is a music lesson scheduling app. The calendar component will give us a place to put all our appointments.

$ ng generate component calendar

Now that the component exists let’s get it to actually show up. The selector for the CalendarComponent is cadence-calendar. (If you look at src/app/calendar/calendar.component.ts, you can see the part near the top of the file where the selector for that component is specified.)

If we put
<cadence-calendar></cadence-calendar>
in any template file, it will show us CalendarComponent. Let’s put this selector in the AppComponent template now.

<!-- src/app/app.component.html -->

<cadence-calendar></cadence-calendar>

Now that we’ve made this change we should see a screen like this:

Adding Twitter Bootstrap

I like to use Twitter Bootstrap for most of the apps I create. Let’s just get adding Bootstrap out of the way now so that everything we do from this point on will look somewhat nice.

The first step is to install the Bootstrap NPM package. (The --save means we want NPM to add the bootstrap@next package to package.json so that Bootstrap is an explicit dependency of this project and will get installed anytime anyone does an npm install.)

$ npm install --save bootstrap@next

Next we have to add a few lines to .angular-cli.json. Bootstrap comes with some styles and JavaScripts and we have to tell Angular CLI about these things in order for Bootstrap to get wired up properly.

{
  "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
  "project": {
    "name": "cadence"
  },
  "apps": [
    {
      "root": "src",
      "outDir": "dist",
      "assets": [
        "assets",
        "favicon.ico"
      ],
      "index": "index.html",
      "main": "main.ts",
      "polyfills": "polyfills.ts",
      "test": "test.ts",
      "tsconfig": "tsconfig.app.json",
      "testTsconfig": "tsconfig.spec.json",
      "prefix": "cadence",
      "styles": [
        "../node_modules/bootstrap/dist/css/bootstrap.css",
        "styles.css"
      ],
      "scripts": [
        "../node_modules/jquery/dist/jquery.js",
        "../node_modules/tether/dist/js/tether.js",
        "../node_modules/bootstrap/dist/js/bootstrap.js"
      ],
      "environmentSource": "environments/environment.ts",
      "environments": {
        "dev": "environments/environment.ts",
        "prod": "environments/environment.prod.ts"
      }
    }
  ],
  "e2e": {
    "protractor": {
      "config": "./protractor.conf.js"
    }
  },
  "lint": [
    {
      "project": "src/tsconfig.app.json"
    },
    {
      "project": "src/tsconfig.spec.json"
    },
    {
      "project": "e2e/tsconfig.e2e.json"
    }
  ],
  "test": {
    "karma": {
      "config": "./karma.conf.js"
    }
  },
  "defaults": {
    "styleExt": "css",
    "component": {}
  }
}

After the very important step of restarting your server, you should see a screen like the one below. (To restart the server, kill the ng serve process and start it again.)

It’s also a good idea to put all our application’s content inside a container Let’s also add a header.

<!-- src/app/app.component.html -->

<div class="container">
  <h1>Cadence</h1>

  <cadence-calendar></cadence-calendar>
</div>

Adding NewAppointmentComponent

We haven’t yet put anything into CalendarComponent. The first thing we’ll actually put into it is another component. Angular recommends a component tree application structure where child components are nested inside of parent components. I thought it would make sense to make CalendarComponent contain a list of appointments as well as a component for creating new appointments. We’ll create that component for creating new appointments now.

First, let’s cd into the directory where CalendarComponent lives.

$ cd src/app/calendar

Now we can create NewAppointmentComponent itself.

$ ng generate component new-appointment

Finally, we’ll put the selector for NewAppointmentComponent in the component for CalendarComponent. We should see NewAppointmentComponent‘s template content (presently just “new-appointment works!”) show up on the page.

<!-- src/app/calendar/calendar.component.html -->

<p>
  calendar works!
</p>

<cadence-new-appointment></cadence-new-appointment>

Adding some form controls to the appointment form

Like I said near the beginning of this post, our appointments will only have two attributes for starters: date and client name. Let’s create two text inputs in NewAppointmentComponent‘s template, one for each of these attributes. Right now we’ll just accept arbitrary text and not worry about whether the date provided by the user is a valid date.

<!-- src/app/calendar/new-appointment/new-appointment.component.html -->

<div class="form-group">
  <label for="date">Date</label>
  <input type="text" class="form-control" name="date" id="date">
</div>

<div class="form-group">
  <label for="clientName">Client name</label>
  <input type="text" class="form-control" name="clientName" id="clientName">
</div>

<input type="submit" class="btn btn-primary" value="Save">

The form should look like this:

Make sure you’re in the calendar directory.

If you click Save, nothing happens, of course, We haven’t hooked up the Save button to anything.

The first step in getting the Save button to actually do something is to bind the form we just created to an Appointment object. We’ll bind the form to an Appointment, then when the user clicks Save, we’ll save that Appointment object (or, more precisely, we’ll save a representation of that Appointment object.)

If you’re wondering what this Appointment object is I’m talking about, it’s something that doesn’t exist yet. Let’s use Angular CLI to help us generate an Appointment class.

(We’re still in src/app/calendar, by the way.)

$ ng generate class appointment

Then we’ll open up the Appointment class definition and add date and clientName properties. Why do we have public date?: string instead of public date: string? The question mark means those arguments are optional. So when we create an empty Appointment instance, we can instantiate it by doing new Appointment() instead of having to do new Appointment('', ''), which would feel kind of dumb.

// src/app/calendar/appointment.ts

export class Appointment {
  constructor(public date?: string,
              public clientName?: string) {}
}

After we define the Appointment class we’ll pull it into NewAppointmentComponent.

// src/app/calendar/new-appointment/new-appointment.component.ts

import { Component, OnInit } from '@angular/core';
import { Appointment } from '../appointment';

@Component({
  selector: 'cadence-new-appointment',
  templateUrl: './new-appointment.component.html',
  styleUrls: ['./new-appointment.component.css']
})
export class NewAppointmentComponent implements OnInit {
  model = new Appointment();

  constructor() { }

  ngOnInit() {
  }

  save() {
    console.log(this.model);
  }
}

We’ve created a model property on NewAppointmentComponent and now we’ll bind model.date to the Date input and model.clientName to the Client name input. If you’re interested in learning more about data binding I’d recommend this article.

We’re also adding a form and putting an event on the form. When the form gets submitted, the save() function on NewAppointmentComponent gets invoked. For now we’re just logging the model. I don’t want to try to do anything fancier than that yet because I want to be sure that this step works first.

<!-- src/app/calendar/new-appointment/new-appointment.component.html -->

<form (submit)="save()">

  <div class="form-group">
    <label for="date">Date</label>
    <input type="text"
           class="form-control"
           name="date"
           id="date"
           [(ngModel)]="model.date">
  </div>

  <div class="form-group">
    <label for="clientName">Client name</label>
    <input type="text"
           class="form-control"
           name="clientName"
           id="clientName"
           [(ngModel)]="model.clientName">
  </div>

  <input type="submit" class="btn btn-primary" value="Save">
</form>

If we try to reload the page now we’ll get an error in the console that says, “Can’t bind to ‘ngModel’ since it isn’t a known property of ‘input’.” This is because ngModel needs FormsModule in order to work but we haven’t imported FormsModule.

// src/app/app.module.ts

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';

import { AppComponent } from './app.component';
import { CalendarComponent } from './calendar/calendar.component';
import { NewAppointmentComponent } from './calendar/new-appointment/new-appointment.component';

@NgModule({
  declarations: [
    AppComponent,
    CalendarComponent,
    NewAppointmentComponent
  ],
  imports: [
    BrowserModule,
    FormsModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

You can see in the screenshot below that I’ve filled out the form and clicked Save. In the console appears the Appointment object that corresponds to the values I entered in the form.

Saving data to localStorage

If you’ve never used localStorage before, don’t worry. It’s pretty simple to use and you don’t have to do anything special to use localStorage with Angular.

Below we’re creating array called appointments which, for now, only has one element: the Appointment object we’re saving from the form. Then we’re saving that array to localStorage.

// src/app/calendar/new-appointment/new-appointment.component.ts

import { Component, OnInit } from '@angular/core';
import { Appointment } from '../appointment';

@Component({
  selector: 'cadence-new-appointment',
  templateUrl: './new-appointment.component.html',
  styleUrls: ['./new-appointment.component.css']
})
export class NewAppointmentComponent implements OnInit {
  model = new Appointment();

  constructor() { }

  ngOnInit() {
  }

  save() {
    let appointments = [this.model];
    localStorage.setItem('appointments', JSON.stringify(appointments));
  }
}

If you save an appointment now, then open up the Application tab in the Chrome developer toolbar and go to Local Storage, you should be be able to see the appointment you just saved.

Showing the saved appointments in CalendarComponent

To get the saved appointments out of localStorage and into CalendarComponent, we can just do JSON.parse(localStorage.getItem('appointments')).

// src/app/calendar/calendar.component.ts

import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'cadence-calendar',
  templateUrl: './calendar.component.html',
  styleUrls: ['./calendar.component.css']
})
export class CalendarComponent implements OnInit {
  appointments = [];

  constructor() { }

  ngOnInit() {
    this.appointments = JSON.parse(localStorage.getItem('appointments'));
  }

}

We can show the appointments on the page using an *ngFor loop.

<!-- src/app/calendar/calendar.component.html -->

<div *ngFor="let appointment of appointments">
  {{ appointment.date }}
  {{ appointment.clientName }}
</div>

<cadence-new-appointment></cadence-new-appointment>

Here’s what the page looks like with the appointments added. Not very nice-looking yet but we’ll improve the appearance shortly.

Preserving previously-saved appointments

Until now, NewAppointmentComponent saves an array of appointments that actually just consists of one element, the appointment that’s being saved right now. What it should really do is save an array of appointments consisting of all the existing appointments plus the appointment that’s being saved now. Let’s fix this.

// src/app/calendar/new-appointment/new-appointment.component.ts

import { Component, OnInit } from '@angular/core';
import { Appointment } from '../appointment';

@Component({
  selector: 'cadence-new-appointment',
  templateUrl: './new-appointment.component.html',
  styleUrls: ['./new-appointment.component.css']
})
export class NewAppointmentComponent implements OnInit {
  model = new Appointment();

  constructor() { }

  ngOnInit() {
  }

  save() {
    let appointments = JSON.parse(localStorage.getItem('appointments'));
    appointments.push(this.model);

    localStorage.setItem('appointments', JSON.stringify(appointments));
  }
}

Refactoring the appointment code into a service

The appropriate place to put business logic in Angular is in services. Components shouldn’t do very much. Components should only call services that do things. If there’s some saving of records going on, the component shouldn’t be aware of how that saving happens.

Let’s move our appointment-saving and appointment-retrieving code out of the components and into a service.

We’ll use Angular CLI to generate a service. Before you do so, make sure you’re in the calendar directory.

$ ng generate service appointment

This command will generate a service called AppointmentService. Let’s create a function called save() which accepts an appointment. In the body of our function goes the code we’ve been using inside NewAppointmentComponent so far to save an appointment.

// src/app/calendar/appointment.service.ts

import { Injectable } from '@angular/core';

@Injectable()
export class AppointmentService {

  constructor() { }

  save(appointment) {
    let appointments = JSON.parse(localStorage.getItem('appointments'));
    appointments.push(appointment);

    localStorage.setItem('appointments', JSON.stringify(appointments));
  }
}

Then NewAppointmentComponent can just call the service’s save() function.

// src/app/calendar/new-appointment/new-appointment.component.ts

import { Component, OnInit } from '@angular/core';
import { Appointment } from '../appointment';
import { AppointmentService } from '../appointment.service';

@Component({
  selector: 'cadence-new-appointment',
  templateUrl: './new-appointment.component.html',
  styleUrls: ['./new-appointment.component.css']
})
export class NewAppointmentComponent implements OnInit {
  model = new Appointment();

  constructor(private appointmentService: AppointmentService) { }

  ngOnInit() {
  }

  save() {
    this.appointmentService.save(this.model);
  }
}

We’ll need to add AppointmentService as a provider in order for it to be able to be injected into components.

// src/app/app.module.ts

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';

import { AppComponent } from './app.component';
import { CalendarComponent } from './calendar/calendar.component';
import { NewAppointmentComponent } from './calendar/new-appointment/new-appointment.component';
import { AppointmentService } from './calendar/appointment.service';

@NgModule({
  declarations: [
    AppComponent,
    CalendarComponent,
    NewAppointmentComponent
  ],
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule
  ],
  providers: [AppointmentService],
  bootstrap: [AppComponent]
})
export class AppModule { }

Similar to how we moved the saving code from the component to the service, we can move the retrieval code as well. Let’s create a getList() function in AppointmentService.

// src/app/calendar/appointment.service.ts

import { Injectable } from '@angular/core';

@Injectable()
export class AppointmentService {

  constructor() { }

  getList() {
    return JSON.parse(localStorage.getItem('appointments'));
  }

  save(appointment) {
    let appointments = JSON.parse(localStorage.getItem('appointments'));
    appointments.push(appointment);

    localStorage.setItem('appointments', JSON.stringify(appointments));
  }
}

Then CalendarComponent can call getList().

// src/app/calendar/calendar.component.ts

import { Component, OnInit } from '@angular/core';
import { AppointmentService } from './appointment.service';

@Component({
  selector: 'cadence-calendar',
  templateUrl: './calendar.component.html',
  styleUrls: ['./calendar.component.css']
})
export class CalendarComponent implements OnInit {
  appointments = [];

  constructor(private appointmentService: AppointmentService) { }

  ngOnInit() {
    this.appointments = this.appointmentService.getList();
  }
}

Now that the getList() function exists, we can remove some duplication between the getList() function and the save() function by just calling getList() to get the list of appointments.

// src/app/calendar/appointment.service.ts

import { Injectable } from '@angular/core';

@Injectable()
export class AppointmentService {

  constructor() { }

  getList() {
    return JSON.parse(localStorage.getItem('appointments'));
  }

  save(appointment) {
    let appointments = this.getList();
    appointments.push(appointment);

    localStorage.setItem('appointments', JSON.stringify(appointments));
  }
}

Getting the components to talk to each other

At this point, saving appointments works and getting appointments back out works but we have to refresh the page in order for our appointments to show up. This is of course not what we ultimately want. We want the saved appointments to show up on the page immediately.

We need to get NewAppointmentComponent and CalendarComponent to talk to each other. The Angular docs describe a method of inter-component communication where component A sends some data to a service, then component B gets that data out of the same service. At a high level, this is a simple enough concept to understand.

Where things start to get somewhat complicated is the fact that the example from the Angular docs use observables to achieve this communication. I’d recommend you take a look at my blog post Observables Made Simple before proceeding. It took me a long time to grasp observables. I think that post will help you understand them a little more quickly than I first did.

The way we’re going to use observables in this case is the following: when an appointment is saved, AppointmentService will emit that appointment. When the appointment is emitted, CalendarComponent will become aware of it because CalendarComponent has subscribed to the observable that emits appointments.

The first step is to add the observable to AppointmentService.

// src/app/calendar/appointment.service.ts

import { Injectable } from '@angular/core';
import { Subject } from 'rxjs/Subject';

@Injectable()
export class AppointmentService {

  private appointmentCreatedSource = new Subject<string>();

  appointmentCreated$ = this.appointmentCreatedSource.asObservable();

  constructor() { }

  getList() {
    return JSON.parse(localStorage.getItem('appointments'));
  }

  save(appointment) {
    let appointments = this.getList();
    appointments.push(appointment);

    localStorage.setItem('appointments', JSON.stringify(appointments));
    this.appointmentCreatedSource.next(appointment);
  }
}

Then modify CalendarComponent so it subscribes to the appointment-emitting service and reloads the appointment list when it knows a new appointment has been saved.

// src/app/calendar/calendar.component.ts

import { Component, OnInit } from '@angular/core';
import { AppointmentService } from './appointment.service';

@Component({
  selector: 'cadence-calendar',
  templateUrl: './calendar.component.html',
  styleUrls: ['./calendar.component.css']
})
export class CalendarComponent implements OnInit {
  appointments = [];

  constructor(private appointmentService: AppointmentService) {
    appointmentService.appointmentCreated$.subscribe(appointment => {
      this.loadAppointments();
    });
  }

  loadAppointments() {
    this.appointments = this.appointmentService.getList();
  }

  ngOnInit() {
    this.loadAppointments();
  }
}

Now when you save an appointment it will show up immediately.

Making it look nicer

It would be nice if the appointment form actually cleared itself when we save an appointment. We can do this by calling reset() on the form when the submit button is clicked.

<!-- src/app/calendar/new-appointment/new-appointment.component.html -->

<form #appointmentForm="ngForm" (submit)="save(); appointmentForm.reset()">

  <div class="form-group">
    <label for="date">Date</label>
    <input type="text"
           class="form-control"
           name="date"
           id="date"
           [(ngModel)]="model.date">
  </div>

  <div class="form-group">
    <label for="clientName">Client name</label>
    <input type="text"
           class="form-control"
           name="clientName"
           id="clientName"
           [(ngModel)]="model.clientName">
  </div>

  <input type="submit" class="btn btn-primary" value="Save">
</form>

A little padding would also help.

/* src/styles.css */

body {
  padding-top: 30px;
}

Lastly, we’ll give the appointment form a containing gray box. First we’ll give it a class name.

<!-- src/app/calendar/new-appointment/new-appointment.component.html -->

<div class="new-appointment-form-container">
  <form #appointmentForm="ngForm" (submit)="save(); appointmentForm.reset()">

    <div class="form-group">
      <label for="date">Date</label>
      <input type="text"
             class="form-control"
             name="date"
             id="date"
             [(ngModel)]="model.date">
    </div>

    <div class="form-group">
      <label for="clientName">Client name</label>
      <input type="text"
             class="form-control"
             name="clientName"
             id="clientName"
             [(ngModel)]="model.clientName">
    </div>

    <input type="submit" class="btn btn-primary" value="Save">
  </form>
</div>

Then we’ll add the styles.

/* src/app/calendar/new-appointment/new-appointment.component.css */

.new-appointment-form-container {
  background-color: #DDD;
  padding: 30px;
}

Then we’ll change the markup of the calendar page to make it look a little nicer.

<!-- src/app/calendar/calendar.component.html -->

<div class="row">
  <div class="col-lg-6">
    <cadence-new-appointment></cadence-new-appointment>
  </div>

  <div class="col-lg-6">
    <div *ngFor="let appointment of appointments">
      <hr>
      {{ appointment.date }}
      {{ appointment.clientName }}
    </div>
  </div>
</div>

Your application should now roughly match the screenshot below.

How to Deploy an Angular 2/Rails 5 App to Heroku

Here’s how to deploy an Angular 2 and Rails 5 app to Heroku. There’s more than one possible way this sort of thing could be structured. Before I describe how to set up the deployment I’ll explain how I chose to structure my app and why.

First, my app is a single-page application where the front-end code is completely separate from the back-end API code. The alternative would be to use Rails views and include the JavaScript and CSS in the asset pipeline. I don’t think either way is necessarily better than the other. In this case I chose the SPA way because a) I understand a lot of people are structuring their Angular/Rails apps this way and b) between the two structures, it’s probably the trickier of the two to deploy, and so it’s probably the one that could benefit most from being documented.

I could have chosen to keep my Angular app and my Rails app in two separate repos. In that case the Rails code could have been deployed to Heroku and the Angular code could have been deployed to, say, a static AWS site. What I instead chose to do was to include my Angular app in a subdirectory of the Rails project and host it all on the same Heroku app. The reason I did it this way is because a) front-end and back-end changes are often made at the same time by the same people and b) although it might have been less work initially to host the Angular app and the Rails app in two different places, it’s arguably simpler from a day-to-day workflow standpoint just to have a single way to deploy everything to one place.

The deployment steps below begin with the creation of an Angular/Rails app, so you don’t have to worry about having your own app to deploy. These steps will get you all the way there but you can also clone my example repo if you don’t want to follow the steps manually.

Initializing the App

The first step will be to initialize a new Rails 5 project. I’m calling mine rails_5_angular_2_deployment_example.

For the front-end we’ll use angular2-seed. Clone the repo into a subdirectory called client. (The name client, in this case, is important. If you call it something else, the deployment won’t work.)

git clone git@github.com:mgechev/angular2-seed.git client

We’re just interested in the angular2-seed code. If we keep it as a repository inside our project’s repository, there will be problems. Kill client/.git.

rm -rf client/.git

Now install the Node packages.

cd client
npm install

Before we try to deploy our app we’ll want to make sure it works locally. Run the following command to do a build.

npm run build.prod

This command will create a directory called client/dist/prod. If we want Rails to be able to serve something out of there, we’ll have to tell Rails about it somehow. We can do this by simply symlinking public/ to client/dist/prod/. Rails will look for public/index.html and find client/dist/prod/index.html and we’ll be in business.

cd ..
rm -rf public
ln -s client/dist/prod public

Start the Rails server. If you navigate to localhost:3000 you should see “Howdy! Here’s a list of awesome computer scientists…”

rails server

Creating the Heroku App

The first step is somewhat self-explanatory:

heroku create

We’ll need to use two buildpacks for this deployment. If we were deploying a plain old Rails app, Heroku would auto-detect Ruby and use the heroku/ruby buildpack. We also need a Node buildpack because in order to build our front-end app we need to run a Gulp command and in order to run Gulp commands we need to have Node packages installed.

We can tell Heroku about our two buildpacks like this:

heroku buildpacks:add https://github.com/jasonswett/heroku-buildpack-nodejs
heroku buildpacks:add heroku/ruby

The order matters. If we were to put the Ruby buildpack first and the Node buildpack second, Heroku would give us a single Node dyno which wouldn’t know how to run Ruby. By putting the Ruby buildpack second we get two dynos: a web dyno and a worker dyno, both of which know how to run Ruby.

The reason we’re using my https://github.com/jasonswett/heroku-buildpack-nodejs buildpack instead of the Heroku version is that I needed to modify the buildpack to look for package.json inside of the client directory instead of at the project root.

We need to do one last thing before we can push our code. Modify client/package.json to look like this:

{
  "name": "angular2-seed",
  "version": "0.0.0",
  "description": "Seed for Angular 2 apps",
  "repository": {
    "url": "https://github.com/mgechev/angular2-seed"
  },
  "scripts": {
    "build.dev": "gulp build.dev --color",
    "build.dev.watch": "gulp build.dev.watch --color",
    "build.e2e": "gulp build.e2e --color",
    "build.prod": "gulp build.prod --color",
    "build.test": "gulp build.test --color",
    "build.test.watch": "gulp build.test.watch --color",
    "docs": "npm run gulp -- build.docs --color && npm run gulp -- serve.docs --color",
    "e2e": "protractor",
    "e2e.live": "protractor --elementExplorer",
    "gulp": "gulp",
    "karma": "karma",
    "karma.start": "karma start",
    "postinstall": "typings install && gulp check.versions && npm prune && gulp build.prod",
    "reinstall": "npm cache clean && npm install",
    "serve.coverage": "remap-istanbul -b src/ -i coverage/coverage-final.json -o coverage -t html && npm run gulp -- serve.coverage --color",
    "serve.dev": "gulp serve.dev --color",
    "serve.e2e": "gulp serve.e2e --color",
    "serve.prod": "gulp serve.prod --color",
    "start": "gulp serve.dev --color",
    "tasks.list": "gulp --tasks-simple --color",
    "test": "gulp test --color",
    "webdriver-start": "webdriver-manager start",
    "webdriver-update": "webdriver-manager update"
  },
  "author": "Minko Gechev <mgechev>",
  "license": "MIT",
  "devDependencies": {
  },
  "dependencies": {
    "angular2": "2.0.0-beta.15",
    "es6-module-loader": "^0.17.8",
    "es6-promise": "^3.1.2",
    "es6-shim": "0.35.0",
    "reflect-metadata": "0.1.2",
    "rxjs": "5.0.0-beta.2",
    "systemjs": "~0.19.25",
    "zone.js": "^0.6.10",
    "async": "^1.4.2",
    "autoprefixer": "^6.3.3",
    "browser-sync": "^2.11.2",
    "chalk": "^1.1.3",
    "codelyzer": "0.0.12",
    "colorguard": "^1.1.1",
    "connect": "^3.4.1",
    "connect-history-api-fallback": "^1.1.0",
    "connect-livereload": "^0.5.3",
    "cssnano": "^3.5.2",
    "doiuse": "^2.3.0",
    "event-stream": "^3.3.2",
    "express": "~4.13.1",
    "express-history-api-fallback": "^2.0.0",
    "extend": "^3.0.0",
    "gulp": "^3.9.1",
    "gulp-cached": "^1.1.0",
    "gulp-concat": "^2.6.0",
    "gulp-filter": "^4.0.0",
    "gulp-inject": "^4.0.0",
    "gulp-inline-ng2-template": "^1.1.2",
    "gulp-load-plugins": "^1.2.0",
    "gulp-plumber": "~1.1.0",
    "gulp-postcss": "^6.1.0",
    "gulp-shell": "~0.5.2",
    "gulp-sourcemaps": "git+https://github.com/floridoo/gulp-sourcemaps.git#master",
    "gulp-template": "^3.1.0",
    "gulp-tslint": "^4.3.3",
    "gulp-typedoc": "^1.2.1",
    "gulp-typescript": "~2.12.1",
    "gulp-uglify": "^1.5.3",
    "gulp-util": "^3.0.7",
    "gulp-watch": "^4.3.5",
    "is-ci": "^1.0.8",
    "isstream": "^0.1.2",
    "jasmine-core": "~2.4.1",
    "jasmine-spec-reporter": "^2.4.0",
    "karma": "~0.13.22",
    "karma-chrome-launcher": "~0.2.2",
    "karma-coverage": "^0.5.5",
    "karma-ie-launcher": "^0.2.0",
    "karma-jasmine": "~0.3.8",
    "karma-mocha-reporter": "^2.0.0",
    "karma-phantomjs-launcher": "^1.0.0",
    "merge-stream": "^1.0.0",
    "open": "0.0.5",
    "phantomjs-prebuilt": "^2.1.4",
    "postcss-reporter": "^1.3.3",
    "protractor": "^3.0.0",
    "remap-istanbul": "git+https://github.com/SitePen/remap-istanbul.git#master",
    "rimraf": "^2.5.2",
    "run-sequence": "^1.1.0",
    "semver": "^5.1.0",
    "serve-static": "^1.10.2",
    "slash": "~1.0.0",
    "stream-series": "^0.1.1",
    "stylelint": "^5.3.0",
    "stylelint-config-standard": "^5.0.0",
    "systemjs-builder": "^0.15.14",
    "tiny-lr": "^0.2.1",
    "traceur": "^0.0.91",
    "ts-node": "^0.7.1",
    "tslint": "^3.7.0-dev.2",
    "tslint-stylish": "2.1.0-beta",
    "typedoc": "^0.3.12",
    "typescript": "~1.8.10",
    "typings": "^0.7.12",
    "vinyl-buffer": "^1.0.0",
    "vinyl-source-stream": "^1.1.0",
    "yargs": "^4.2.0"
  }
}

We’ve done two things here. First, we added gulp build.prod to the postinstall script. This will force Heroku to do a build as part of the deployment process. Second, we move everything from devDependencies to dependencies. Since it’s a production environment, Node won’t pick up the devDependencies, but we need those.

By the way, why not just build locally, commit the generated code, and push that? You could do that and it would work. The reason I didn’t want to is that it’s not a good idea to commit build artifacts to version control. You end up with a bunch of “changed” files every time you do a build, which not only doesn’t make sense but serves as a distraction. I’ve worked on projects before that commit build artifacts to version control and it has been painful.

After all our changes are committed we can do a push:

git push heroku master

Now open the app in Heroku.

heroku open

You should see the same Angular 2 Seed app in the production environment. Obviously, the Angular app isn’t talking to Rails but it is coexisting with it, and that’s the hard part.

How to Deploy an Angular CLI Webpack Project to Heroku

Angular CLI recently switched from SystemJS to Webpack. This affects the Heroku deployment process.

Here I’ll show you how to initiate an Angular CLI/Webpack project and deploy it to Heroku. (By the way, this is an Angular-only post. I won’t be talking about Rails at all here. Even if you use the Angular/Rails combo I’d recommend deploying an Angular-only app first just so you understand how the process works. If you’re ready for the Rails version, that can be found here.)

First, make sure you have the following versions of the following things installed:

Angular CLI: 1.0.0-beta.11-webpack.8
NPM: 3.10.6
Node: 6.5.0

First let’s initiate the project, which I’ll give the random name of bananas:

$ ng new bananas

When we deploy this, we’ll want to invoke the ng build command after installation. When you run ng build it creates a dist directory with your TypeScript transpiled to JavaScript and all that stuff. It’s a “static” set of files that can be served up as-is.

Because the error-free operation of ng build is critical to our success, let’s make sure we can run it locally before we try to invoke it on Heroku:

$ cd bananas
$ ng build

In all likelihood this did not run error-free for you. You probably got Cannot find global type 'Array'. and a bunch of other similar stuff like these people did.

The way I fixed that was to first have the right Node and NPM versions installed. We’ve already taken care of that at the beginning. The second step is to change your typescript version in package.json from ^2.0.0 to 2.0.0.

Here’s what my package.json looks like after the change:

{
  "name": "bananas",
  "version": "0.0.0",
  "license": "MIT",
  "angular-cli": {},
  "scripts": {
    "start": "ng serve",
    "lint": "tslint \"src/**/*.ts\"",
    "test": "ng test",
    "pree2e": "webdriver-manager update",
    "e2e": "protractor"
  },
  "private": true,
  "dependencies": {
    "@angular/common": "2.0.0-rc.5",
    "@angular/compiler": "2.0.0-rc.5",
    "@angular/core": "2.0.0-rc.5",
    "@angular/forms": "0.3.0",
    "@angular/http": "2.0.0-rc.5",
    "@angular/platform-browser": "2.0.0-rc.5",
    "@angular/platform-browser-dynamic": "2.0.0-rc.5",
    "@angular/router": "3.0.0-rc.1",
    "core-js": "^2.4.0",
    "rxjs": "5.0.0-beta.11",
    "ts-helpers": "^1.1.1",
    "zone.js": "0.6.12"
  },
  "devDependencies": {
    "@types/jasmine": "^2.2.30",
    "angular-cli": "1.0.0-beta.11-webpack.8",
    "codelyzer": "~0.0.26",
    "jasmine-core": "2.4.1",
    "jasmine-spec-reporter": "2.5.0",
    "karma": "0.13.22",
    "karma-chrome-launcher": "0.2.3",
    "karma-jasmine": "0.3.8",
    "karma-remap-istanbul": "^0.2.1",
    "protractor": "4.0.3",
    "ts-node": "1.2.1",
    "tslint": "3.13.0",
    "typescript": "2.0.0"
  }
}

Now do an npm install and ng build again. You should be error-free this time.

$ npm install
$ ng build

Next you’ll need to change the scripts section of package.json as follows:

  "scripts": {
    "start": "http-server",
    "lint": "tslint \"src/**/*.ts\"",
    "test": "ng test",
    "pree2e": "webdriver-manager update",
    "e2e": "protractor",
    "preinstall": "npm install -g http-server",
    "postinstall": "ng build && mv dist/* ."
  },

We did three things: First, we told Heroku to install http-server globally, which we’ll use to serve our application. Second, we told Heroku to run ng build after installation as well as to move everything in the dist directory to the project root. Third, we told Heroku to serve the app by running http-server. (Heroku automatically runs npm start for Node projects.)

We also have to move our devDependencies into dependencies. Heroku won’t pick up anything in devDependencies but we need some of it. Here’s my final package.json:

{
  "name": "bananas",
  "version": "0.0.0",
  "license": "MIT",
  "angular-cli": {},
  "scripts": {
    "start": "http-server",
    "lint": "tslint \"src/**/*.ts\"",
    "test": "ng test",
    "pree2e": "webdriver-manager update",
    "e2e": "protractor",
    "preinstall": "npm install -g http-server",
    "postinstall": "ng build && mv dist/* ."
  },
  "private": true,
  "dependencies": {
    "@angular/common": "2.0.0-rc.5",
    "@angular/compiler": "2.0.0-rc.5",
    "@angular/core": "2.0.0-rc.5",
    "@angular/forms": "0.3.0",
    "@angular/http": "2.0.0-rc.5",
    "@angular/platform-browser": "2.0.0-rc.5",
    "@angular/platform-browser-dynamic": "2.0.0-rc.5",
    "@angular/router": "3.0.0-rc.1",
    "core-js": "^2.4.0",
    "rxjs": "5.0.0-beta.11",
    "ts-helpers": "^1.1.1",
    "zone.js": "0.6.12",
    "@types/jasmine": "^2.2.30",
    "angular-cli": "1.0.0-beta.11-webpack.8",
    "codelyzer": "~0.0.26",
    "jasmine-core": "2.4.1",
    "jasmine-spec-reporter": "2.5.0",
    "karma": "0.13.22",
    "karma-chrome-launcher": "0.2.3",
    "karma-jasmine": "0.3.8",
    "karma-remap-istanbul": "^0.2.1",
    "protractor": "4.0.3",
    "ts-node": "1.2.1",
    "tslint": "3.13.0",
    "typescript": "2.0.0"
  },
  "devDependencies": {
  }
}

Now let’s create the Heroku project. (I’m assuming you already have a Heroku account and Heroku toolbelt set up.)

$ heroku create

Make sure you have all your changes commit and push your code.

$ git push heroku master

When that has finished, run:

$ heroku open

You should see “app works!”.

Page reload problem

There is one problem, though. If your Angular/Rails application has any routes defined, you’ll notice that if you navigate to a route by clicking a link, then refresh the page, the page will no longer work. The reasons for this are nuanced but I explain them in depth in my HTML5 pushState post.

The fix to the problem is pretty easy.

First, add the rack-rewrite gem to your Gemfile.

# Gemfile

gem 'rack-rewrite'

Then bundle install, of course.

$ bundle install

Then you’ll add a redirect rule to your config.ru that looks like this:

# config.ru

# This file is used by Rack-based servers to start the application.

use Rack::Rewrite do
  rewrite %r{^(?!.*(api|\.)).*$}, '/index.html'
end

require ::File.expand_path('../config/environment',  __FILE__)
run Rails.application

After you commit these changes and deploy again you should be good.

How to Add a Test Coverage Report to an Angular CLI Project

Angular CLI has test coverage reporting somewhat built in.

First install the dependencies.

$ npm install karma karma-jasmine karma-chrome-launcher karma-jasmine-html-reporter karma-coverage-istanbul-reporter

Then run ng test.

$ ng test --code-coverage

Then run the server that shows you your report.

$ http-server -c-1 -o -p 9875 ./coverage

You should see something like this.