Expanding on the Angular 2 Quickstart

2015-05-25

Not so long ago the Angular 2 team released a quickstart tutorial to give people a chance to play with the new version of the framework. The tutorial itself is pretty skinny, likely because the framework is still subject to change in big ways. Nonetheless, there have been screencasts, more screencasts, tutorials, and more tutorials covering this or that part of the framework.

All the same, though, when I set out to build my own basic Angular 2 app, it was hard to find one blog post that answered all my questions -- stuff like how to inject services into components, how to assign attributes to components, the basics of the template syntax, as well as what a TypeScript workflow looked like. So in the interest of answering these questions for others who might be interested, I will walk through a basic guestbook app. If reading the code straightaway is more helpful, you can find it here. In short, this post is meant to be a minor expansion on the Angular 2 quickstart.

Before we get into the code, I found WebStorm 10 to work nicely with TypeScript. Even though the IDE offers to compile .ts files, I instead ran the TypeScript compiler from the command line. The Atom editor from GitHub paired with atom-typescript looks like a nice alternative.

Aside from an editor, you will also need a few command line tools: tsc, a TypeScript compiler, tsd, a TypeScript definition manager, and http-server, a simple server which we'll use to view our app. To install these tools, run:

npm install -g tsd
npm install -g typescript
npm install -g http-server

To start, let's create a working directory and fetch the Angular 2 type definitions:

mkdir guestbook && cd guestbook
tsd query angular2 --action install

This will create a typings directory with an angular/angular2.d.ts file, which we will use in a moment. Next, we need to create our application files:

touch Guestbook.ts index.html

And with that, we are ready to start compiling our TypeScript:

tsc --watch -m commonjs -t es5 --emitDecoratorMetadata Guestbook.ts

Note that whenever we create a new file, we will need to restart the tsc process for it to pick up the new file.

We now need to specify the TypeScript definitions for Angular at the top of Guestbook.ts:

/// <reference path="typings/angular2/angular2.d.ts" />

The last involved part of setting up Angular 2 is bootstrapping its dependencies in the index.html. Note that we're using JSPM to load our Guestbook code onto the page.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8"/>
  <title>Angular 2 Quickstart Expanded</title>
  <script src="https://github.jspm.io/jmcriffey/[email protected]/traceur-runtime.js"></script>
  <script src="https://jspm.io/[email protected]"></script>
  <script src="https://code.angularjs.org/2.0.0-alpha.22/angular2.dev.js"></script>
</head>
<body>

  <!-- we'll write this component below -->
  <guestbook></guestbook>

  <script>System.import('Guestbook');</script>
</body>
</html>

As a side note, at the time of this post, I tried bumping to the newest Angular 2 (version 2.0.0-alpha.25), but ran into some trouble with injecting services. So we'll stick with the version suggested by the Angular team on their quickstart.

And with that, we are ready to start writing an Angular 2 app. Let's start by getting a basic component on the page. We'll import the Component, View, and bootstrap functions first before defining the rough outline of our guestbook component.

import {
  Component,
  View,
  bootstrap
} from 'angular2/angular2';

@Component({
  selector: "guestbook"
})
@View({
  template: "<h1>My Cool Guestbook</h1>"
})
class Guestbook {

}

bootstrap(Guestbook);

If we launch our http-server from our working directory and visit localhost:8080, we should see that everything is working. As we go, keep an eye on the tsc process, as it will report any errors.

And that's about where the Angular 2 quickstart leaves off. Let's make this component more interesting by injecting a service into it.

Any guestbook will have a list of names, so let's update our component to show that list. First, we'll update the template and pull it out into a separate file. We'll need to change the View annotation to point to a template URL. If this were Angular 1, we would want to add an ng-repeat somewhere for that list of names. In Angular 2, ng-repeat has become For.

import {
  Component,
  View,
  For,        // <---- This is how we bring in For
  bootstrap
} from 'angular2/angular2';


@View({
  templateUrl: "Guestbook", // <--- point to a template URL
  directives: [For] // <---- And this is how we tell the view, we'll use `For`
})

Then create a Guestbook.html file in the same directory with the following:

<div>
  <h1>My Cool Guestbook</h1>

  <ul>
    <li *for="#guest of guests">{{guest}}</li>
  </ul>
</div>

Assuming we have a collection called guests on our Guestbook component, the template simply loops over them and create an li for every guest name. The syntax looks a little strange at first, but in effect we're just using the Angular 2 version of ng-repeat='guest in guests' The asterix denotes an Angular directive and the pound sign, which we'll see again below, creates a data binding.

Now that we have our template wired up correctly, let's add the guests field to our component:

class Guestbook {
  guests: Array<string>;

  constructor() {
    this.guests = ["Alice", "Bob"];
  }
}

Now, if we return to localhost, we will see our list of guests on the page.

To make this more interesting, let's suppose we have an API endpoint which will return guest names, so we'll write a service to fetch those names and inject it into the Guestbook component.

First, for the service. Make a new file called GuestService.ts with the following code and then restart tsc:

class GuestService {
  guests: Array<string>;

  constructor() {
    // hard coding guest names for now
    this.guests = ["Alice", "Bob"];
  }
}

export { GuestService };

There is nothing special about this service. It's a plain JavaScript object. We assign names to the guests array in the constructor and we export the service for importing elsewhere.

Now, back in the Guestbook component, we can import the service. Just below the Angular 2 imports, add the following line:

import {GuestService} from "./GuestService";

With the service now available to us, we have to tell Angular to inject it into the Guestbook component for us. To do that, we'll update the @Component annotation.

@Component({
  selector: 'guestbook',
  injectables: [GuestService] // <--- here we configure our injectable objects
})

And then in the Guestbook constructor, we specify the GuestService as an argument:

class Guestbook {
  guests: Array<string>;

  constructor(service: GuestService) {
    this.guests = service.guests;
  }
}

Now, reload localhost (watching for possible errors in the tsc process), and our guest names are now being delivered by the service. Our app otherwise looks exactly the same. From here, it would be trivial to fetch the guests across a network call, rather than hardcoding them, but we'll leave that exercise for another day.

To take our guestbook features on step further, let's allow users to add new guests to the guestbook.

Best practice would require us to use a form, but for the sake of simplicity, let's sidestep dealing with forms. For the curious reader, here is a good talk on forms in Angular 2. For our purposes here, we will use just an input and a button.

First, we will update the Guestbook.html template:

<div>
  <h1>My Cool Guestbook</h1>

  <!-- new stuff follows -->

  <input type="text" #new-guest />

  <button (click)="addToGuestbook(newGuest.value)">
    Add to Guestbook
  </button>

  <!-- end new stuff -->

  <ul>
    <li *for="#guest of guests">{{guest}}</li>
  </ul>
</div>

The changes here jump right out to a developer versed in Angular 1. There is no ng-model. Instead we have #new-guest with the pound sign indicating a data binding. Also, there is no ng-click. Instead, it's (click) (which is shorthand for bind-click). Otherwise, the idea is the same. We bind user input to a new-guest object and pass the value of that object (value being a property that Angular gives us) to an addToGuestbook function. Note the difference between #new-guest and newGuest. Angular makes the conversion between kebab case and camel case automatically to ensure valid JavaScript variable names. This is not a new feature.

To make this work with our component, we need to add the addToGuestbook function as well as extend our GuestService to accept newly registered guests:

class Guestbook {
  guests: Array<string>;
  service: GuestService;

  constructor(service: GuestService) {
    this.service = service;
    this.guests = service.guests;
  }

  addToGuestbook(guestName: string) {
    this.service.register(guestName);
  }
}

The register function on the GuestService is plain and simple:

class GuestService {
  guests: Array<string>;

  constructor() {
    this.guests = ["Alice", "Bob"];
  }

  // a simple way to register guests
  register(guestName: string) {
    this.guests.push(guestName);
  }
}

export { GuestService };

Now, when we reload the page, we can now add guests to our list. Note that the input field remains populated after successfully adding a guest, but I will leave this problem as an exercise for the reader.

As a finishing touch to our awesome guestbook, let's add a toggleable message showing the number of guests in our guestbook. Rather than write the toggleable message into our Guestbook component, we will make it generalizable and reusable. In other words, let's make a new component.

Our component will take the form of a button with some custom text. When the user clicks that button, a message will appear on the page. If the user clicks the button a second time, the message will disappear. We'll call our new component "toggle-message."

Let's start by updating the Guestbook.html template to include our new toggle-message component:

<div>
  <h1>My Cool Guestbook</h1>

  <input type="text" #newGuest />

  <button (click)="addToGuestbook(newGuest)">
    Add to Guestbook
  </button>

  <ul>
    <li *for="#guest of guests">{{guest}}}</li>
  </ul>

  <!-- here's the new component -->
  <toggle-message
    message="{{currentGuestCount()}}"
    button-text="Guest Count">
  </toggle-message>
</div>

We'll make the message and the button text configurable. The message will be the return value of a new function on our Guestbook component. Let's write that first:

class Guestbook {
  // ...

  currentGuestCount(): number {
    return this.service.currentCount();
  }
}

The new method on the GuestService is also trivial:

class GuestService {
  // ...
  currentCount() {
    return this.guests.length;
  }
}

While we're updating the Guestbook component, we also need to tell Angular about the component's dependency on toggle-message. To do that, we'll import the toggle-message component and update the @View annotation:

// ...
import {ToggleMessage} from "./ToggleMessage";

//...
@View({
  templateUrl: 'Guestbook',
  directives: [For, ToggleMessage] // <--- adding ToggleMessage to the list
})

Now with all the configuration out the way, we're ready to write the toggle-message component. First, create a template ToggleMessage.html with the following:

<div>
  <button (click)="toggleMessage()">{{buttonText}}</button>
  <h1 [hidden]="!toggle">{{message}}</h1>
</div>

Again, we have a click handler which calls a function, this time toggleMessage(). We interpolate the buttonText and message as passed in by the parent component. What's new about this example is the use of [hidden], which sets the hidden property of the h1 DOM element and sets it to the negated value of the toggle variable. Again, see this screencast for more detail on the square bracket notation.

With the template done, let's now write the actual component and complete this app. Create a new file called ToggleMessage.ts and add the following:

import {Component, View} from "angular2/angular2";

@Component({
  selector: 'toggle-message',
  properties: {                   // <---- this is the interesting part
    'message': 'message',
    'buttonText': 'button-text'
  }
})
@View({
  templateUrl: 'ToggleMessage'
})
export class ToggleMessage {
  toggle: boolean;

  constructor() {
    this.toggle = false;
  }

  toggleMessage() {
    this.toggle = !this.toggle;
  }
}

As usual, we import Component and View to describe the selector and the location of the template. We also implement a basic toggleable field using a boolean toggle variable.

The most interesting part is how we tell Angular about the component's properties, i.e., the properties key in the Component annotation. The keys on the right, e.g., button-text will be passed in by the component's parent, while the values on the left will be the local bindings available for us in the template of toggle-message itself.

Now restart the tsc process for it to compile the ToggleMessage component and visit localhost to view our amazing guestbook app in all its glory. If we click on the "Guest Count" button, our count will appear. Clicking again will hide the count. If we add a new guest, the count will be updated.

Granted there are still lots of things to explore about Angular 2 and the framework is still undergoing major changes, but the bigger ideas seemed to have settled. In my view, Angular 2 takes the best of component-based frameworks like ReactJS, while maintaining the excellent testability and use of dependency injection. I think Angular 2 is going to be a fantastic upgrade.