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.
