JavaScript
Article
By Jurgen Van de Moere , Todd Motto

Angular 2 Tutorial: Create a CRUD App with Angular CLI

By Jurgen Van de Moere , Todd Motto

Build an Angular CRUD App from the Ground Up

Illustration of a monitor covered with post-it notes and to-do lists

This article is by guest authors Todd Motto and Jurgen Van de Moere. SitePoint guest posts aim to bring you engaging content from prominent writers and speakers of the JavaScript community.


2016.12.20: This article has been revised in response to reader feedback, and to take account of the current release version of Angular 2.


2017.02.17: As of February 9, 2017, the ng deploy command has been removed from the core of Angular CLI. Read more here.


This is the first article in a 4-part series on how to write a Todo application in Angular 2:

For another app development example, using Ember, check out our video tutorial: Get Started Building Ember Apps.

  1. Part 0— The Ultimate Angular CLI Reference Guide
  2. Part 1— Getting our first version of the Todo application up and running
  3. Part 2— Creating separate components to display a list of todo’s and a single todo
  4. Part 3— Update the Todo service to communicate with a REST API
  5. Part 4— Use Component Router to route to different components

In each article we’ll refine the underlying architecture of the application and we make sure we have a working version of the application that looks like this:

Animated GIF of Finished Todo Application

By the end of this series, our application architecture will look like this:

Application Architecture of Finished Todo Application

The items that are marked with a red border are discussed in this article, while items that are not marked with a red border will be discussed in follow-up articles within this series.

In this first part, you will learn how to:

  • initialize your Todo application using Angular CLI
  • create a Todo class to represent individual todo’s
  • create a TodoDataService service to create, update and remove todo’s
  • use the AppComponent component to display the user interface
  • deploy your application to GitHub pages

So let’s get started!

Rather than a successor of AngularJS 1.x, Angular 2 can be considered an entirely new framework built on lessons from AngularJS 1.x. Hence the name change where Angular is used to denote Angular 2 and AngularJS refers to AngularJS 1.x. In this article we will use Angular and Angular 2 interchangeably but they both refer to Angular 2.

Initialize Your Todo Application Using Angular CLI

One of the easiest ways to start a new Angular 2 application is to use Angular’s command-line interface (CLI).

To install Angular CLI, run:

$ npm install -g angular-cli

which will install the ng command globally on your system.

To verify whether your installation completed successfully, you can run:

$  ng version

which should display the version you have installed:

angular-cli: 1.0.0-beta.21
node: 6.1.0
os: darwin x64

Now that you have Angular CLI installed, you can use it to generate your Todo application:

$ ng new todo-app

This creates a new directory with all files you need to get started:

todo-app
├── README.md
├── angular-cli.json
├── e2e
│   ├── app.e2e-spec.ts
│   ├── app.po.ts
│   └── tsconfig.json
├── karma.conf.js
├── package.json
├── protractor.conf.js
├── src
│   ├── app
│   │   ├── app.component.css
│   │   ├── app.component.html
│   │   ├── app.component.spec.ts
│   │   ├── app.component.ts
│   │   ├── app.module.ts
│   │   └── index.ts
│   ├── assets
│   ├── environments
│   │   ├── environment.prod.ts
│   │   └── environment.ts
│   ├── favicon.ico
│   ├── index.html
│   ├── main.ts
│   ├── polyfills.ts
│   ├── styles.css
│   ├── test.ts
│   ├── tsconfig.json
│   └── typings.d.ts
└── tslint.json

If you are not familiar with the Angular CLI yet, make sure you check out The Ultimate Angular CLI Reference.

You can now navigate to the new directory:

$ cd todo-app

and start the Angular CLI development server:

$ ng serve

which will start a local development server that you can navigate to in your browser on http://localhost:4200/.

The Angular CLI development server includes LiveReload support, so your browser automatically reloads the application when a source file changes.

How convenient is that!

Creating the Todo Class

Because Angular CLI generates TypeScript files, we can use a class to represent Todo items.

So let’s use Angular CLI to generate a Todo class for us:

$ ng generate class Todo --spec

which will create:

src/app/todo.spec.ts
src/app/todo.ts

Let’s open up src/app/todo.ts:

export class Todo {
}

and add the logic we need:

export class Todo {
  id: number;
  title: string = '';
  complete: boolean = false;

  constructor(values: Object = {}) {
    Object.assign(this, values);
  }
}

In this Todo class definition, we specify that each Todo instance will have three properties:

  • id: number, unique ID of the todo item
  • title: string, title of the todo item
  • complete: boolean, whether or not the todo item is complete

We also provide constructor logic that lets us specify property values during instantiation so we can easily create new Todo instances like this:

let todo = new Todo({
  title: 'Read SitePoint article',
  complete: false
});

While we are at it, let’s add a unit test to make sure our constructor logic works as expected.

When generating the Todo class, we used the --spec option. This told Angular CLI to also generate src/app/todo.spec.ts for us with a basic unit test:

import {Todo} from './todo';

describe('Todo', () => {
  it('should create an instance', () => {
    expect(new Todo()).toBeTruthy();
  });
});

Let’s add an additional unit test to make sure the constructor logic works as expected:

import {Todo} from './todo';

describe('Todo', () => {
  it('should create an instance', () => {
    expect(new Todo()).toBeTruthy();
  });

  it('should accept values in the constructor', () => {
    let todo = new Todo({
      title: 'hello',
      complete: true
    });
    expect(todo.title).toEqual('hello');
    expect(todo.complete).toEqual(true);
  });
});

To verify whether our code works as expected, we can now run:

$ ng test

to execute the Karma test runner and run all our unit tests. This should output:

[karma]: No captured browser, open http://localhost:9876/
[karma]: Karma v1.2.0 server started at http://localhost:9876/
[launcher]: Launching browser Chrome with unlimited concurrency
[launcher]: Starting browser Chrome
[Chrome 54.0.2840 (Mac OS X 10.12.0)]: Connected on socket /#ALCo3r1JmW2bvt_fAAAA with id 84083656
Chrome 54.0.2840 (Mac OS X 10.12.0): Executed 5 of 5 SUCCESS (0.159 secs / 0.154 secs)

If your unit tests are failing, you can compare your code to the working code on GitHub.

Now that we have a working Todo class to represent an individual todo, let’s create a TodoDataService service to manage all todo’s.

Creating the TodoDataService Service

The TodoDataService will be responsible for managing our Todo items.

In another part of this series you will learn how to communicate with a REST API, but for now we will store all data in memory.

Let’s use Angular CLI again to generate the service for us:

$ ng generate service TodoData

which outputs:

installing service
  create src/app/todo-data.service.spec.ts
  create src/app/todo-data.service.ts
  WARNING Service is generated but not provided, it must be provided to be used

When generating a service, Angular CLI also generates a unit test by default so we don’t have to explicitly use the --spec option.

Angular CLI has generated the following code for our TodoDataService in src/app/todo-data.service.ts:

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

@Injectable()
export class TodoDataService {

  constructor() { }

}

and a corresponding unit test in src/app/todo-data.service.spec.ts:

/* tslint:disable:no-unused-variable */

import { TestBed, async, inject } from '@angular/core/testing';
import { TodoDataService } from './todo-data.service';

describe('TodoDataService', () => {
  beforeEach(() => {
    TestBed.configureTestingModule({
      providers: [TodoDataService]
    });
  });

  it('should ...', inject([TodoDataService], (service: TodoDataService) => {
    expect(service).toBeTruthy();
  }));
});

Let’s open up src/app/todo-data.service.ts and add our todo management logic to the TodoDataService:

import {Injectable} from '@angular/core';
import {Todo} from './todo';

@Injectable()
export class TodoDataService {

  // Placeholder for last id so we can simulate
  // automatic incrementing of id's
  lastId: number = 0;

  // Placeholder for todo's
  todos: Todo[] = [];

  constructor() {
  }

  // Simulate POST /todos
  addTodo(todo: Todo): TodoDataService {
    if (!todo.id) {
      todo.id = ++this.lastId;
    }
    this.todos.push(todo);
    return this;
  }

  // Simulate DELETE /todos/:id
  deleteTodoById(id: number): TodoDataService {
    this.todos = this.todos
      .filter(todo => todo.id !== id);
    return this;
  }

  // Simulate PUT /todos/:id
  updateTodoById(id: number, values: Object = {}): Todo {
    let todo = this.getTodoById(id);
    if (!todo) {
      return null;
    }
    Object.assign(todo, values);
    return todo;
  }

  // Simulate GET /todos
  getAllTodos(): Todo[] {
    return this.todos;
  }

  // Simulate GET /todos/:id
  getTodoById(id: number): Todo {
    return this.todos
      .filter(todo => todo.id === id)
      .pop();
  }

  // Toggle todo complete
  toggleTodoComplete(todo: Todo){
    let updatedTodo = this.updateTodoById(todo.id, {
      complete: !todo.complete
    });
    return updatedTodo;
  }

}

The actual implementation details of the methods are not essential for the purpose of this article. The main takeaway is that we centralize the business logic in a service.

To make sure the business logic in our TodoDataService service works as expected, we also add some additional unit tests in src/app/todo-data.service.spec.ts:

import {TestBed, async, inject} from '@angular/core/testing';
import {Todo} from './todo';
import {TodoDataService} from './todo-data.service';

describe('TodoDataService', () => {
  beforeEach(() => {
    TestBed.configureTestingModule({
      providers: [TodoDataService]
    });
  });

  it('should ...', inject([TodoDataService], (service: TodoDataService) => {
    expect(service).toBeTruthy();
  }));

  describe('#getAllTodos()', () => {

    it('should return an empty array by default', inject([TodoDataService], (service: TodoDataService) => {
      expect(service.getAllTodos()).toEqual([]);
    }));

    it('should return all todos', inject([TodoDataService], (service: TodoDataService) => {
      let todo1 = new Todo({title: 'Hello 1', complete: false});
      let todo2 = new Todo({title: 'Hello 2', complete: true});
      service.addTodo(todo1);
      service.addTodo(todo2);
      expect(service.getAllTodos()).toEqual([todo1, todo2]);
    }));

  });

  describe('#save(todo)', () => {

    it('should automatically assign an incrementing id', inject([TodoDataService], (service: TodoDataService) => {
      let todo1 = new Todo({title: 'Hello 1', complete: false});
      let todo2 = new Todo({title: 'Hello 2', complete: true});
      service.addTodo(todo1);
      service.addTodo(todo2);
      expect(service.getTodoById(1)).toEqual(todo1);
      expect(service.getTodoById(2)).toEqual(todo2);
    }));

  });

  describe('#deleteTodoById(id)', () => {

    it('should remove todo with the corresponding id', inject([TodoDataService], (service: TodoDataService) => {
      let todo1 = new Todo({title: 'Hello 1', complete: false});
      let todo2 = new Todo({title: 'Hello 2', complete: true});
      service.addTodo(todo1);
      service.addTodo(todo2);
      expect(service.getAllTodos()).toEqual([todo1, todo2]);
      service.deleteTodoById(1);
      expect(service.getAllTodos()).toEqual([todo2]);
      service.deleteTodoById(2);
      expect(service.getAllTodos()).toEqual([]);
    }));

    it('should not removing anything if todo with corresponding id is not found', inject([TodoDataService], (service: TodoDataService) => {
      let todo1 = new Todo({title: 'Hello 1', complete: false});
      let todo2 = new Todo({title: 'Hello 2', complete: true});
      service.addTodo(todo1);
      service.addTodo(todo2);
      expect(service.getAllTodos()).toEqual([todo1, todo2]);
      service.deleteTodoById(3);
      expect(service.getAllTodos()).toEqual([todo1, todo2]);
    }));

  });

  describe('#updateTodoById(id, values)', () => {

    it('should return todo with the corresponding id and updated data', inject([TodoDataService], (service: TodoDataService) => {
      let todo = new Todo({title: 'Hello 1', complete: false});
      service.addTodo(todo);
      let updatedTodo = service.updateTodoById(1, {
        title: 'new title'
      });
      expect(updatedTodo.title).toEqual('new title');
    }));

    it('should return null if todo is not found', inject([TodoDataService], (service: TodoDataService) => {
      let todo = new Todo({title: 'Hello 1', complete: false});
      service.addTodo(todo);
      let updatedTodo = service.updateTodoById(2, {
        title: 'new title'
      });
      expect(updatedTodo).toEqual(null);
    }));

  });

  describe('#toggleTodoComplete(todo)', () => {

    it('should return the updated todo with inverse complete status', inject([TodoDataService], (service: TodoDataService) => {
      let todo = new Todo({title: 'Hello 1', complete: false});
      service.addTodo(todo);
      let updatedTodo = service.toggleTodoComplete(todo);
      expect(updatedTodo.complete).toEqual(true);
      service.toggleTodoComplete(todo);
      expect(updatedTodo.complete).toEqual(false);
    }));

  });

});

Karma comes pre-configured with Jasmine. You can read the Jasmine documentation to learn more about the Jasmine syntax.

Let’s zoom in on some of the parts in the unit tests above:

beforeEach(() => {
  TestBed.configureTestingModule({
    providers: [TodoDataService]
  });
});

First of all what is TestBed?

TestBed is a utility provided by @angular/core/testing to configure and create an Angular testing module in which we want to run our unit tests.

We use the TestBed.configureTestingModule() method to configure and create a new Angular testing module. We can configure the testing module to our liking by passing in a configuration object. This configuration object can have most of the properties of a normal Angular module.

In this case we use the providers property to configure the testing module to use the real TodoDataService when running the tests.

In part 3 of this series we will let the TodoDataService communicate with a real REST API and we will see how we can inject a mock service in our test module to prevent the tests from communicating with the real API.

Next, we use the inject function provided by @angular/core/testing to inject the correct service from the TestBed injector in our test function:

it('should return all todos', inject([TodoDataService], (service: TodoDataService) => {
  let todo1 = new Todo({title: 'Hello 1', complete: false});
  let todo2 = new Todo({title: 'Hello 2', complete: true});
  service.addTodo(todo1);
  service.addTodo(todo2);
  expect(service.getAllTodos()).toEqual([todo1, todo2]);
}));

The first argument to the inject function is an array of Angular dependency injection tokens. The second argument is the test function whose parameters are the dependencies that correspond to the dependency injection tokens from the array.

Here we tell the TestBed injector to inject the TodoDataService by specifying it in the array in the first argument. As a result we can access the TodoDataService as service in our test function because service is the name of the first parameter of our test function.

If you want to learn more about testing in Angular, be sure to check out the official Angular testing guide.

To verify whether our service works as expected, we run our unit tests again:

$ ng test
[karma]: No captured browser, open http://localhost:9876/
[karma]: Karma v1.2.0 server started at http://localhost:9876/
[launcher]: Launching browser Chrome with unlimited concurrency
[launcher]: Starting browser Chrome
[Chrome 54.0.2840 (Mac OS X 10.12.0)]: Connected on socket /#fi6bwZk8IjYr1DZ-AAAA with id 11525081
Chrome 54.0.2840 (Mac OS X 10.12.0): Executed 14 of 14 SUCCESS (0.273 secs / 0.264 secs)

Perfect— all unit tests ran successfully!

Now that we have a working TodoDataService service, it’s time to implement the actual user interface.

In Angular 2, parts of the user interface are represented by components.

Editing the AppComponent Component

When we initialized the Todo application, Angular CLI automatically generated a main AppComponent component for us:

src/app/app.component.css
src/app/app.component.html
src/app/app.component.spec.ts
src/app/app.component.ts

The template and styles can also be specified inline, inside the script file. Angular CLI creates separate files by default, so that’s what we’ll use in this article.

Let’s open up src/app/app.component.html:

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

and replace its content with:

<section class="todoapp">
  <header class="header">
    <h1>Todos</h1>
    <input class="new-todo" placeholder="What needs to be done?" autofocus="" [(ngModel)]="newTodo.title" (keyup.enter)="addTodo()">
  </header>
  <section class="main" *ngIf="todos.length > 0">
    <ul class="todo-list">
      <li *ngFor="let todo of todos" [class.completed]="todo.complete">
        <div class="view">
          <input class="toggle" type="checkbox" (click)="toggleTodoComplete(todo)" [checked]="todo.complete">
          <label>{{todo.title}}</label>
          <button class="destroy" (click)="removeTodo(todo)"></button>
        </div>
      </li>
    </ul>
  </section>
  <footer class="footer" *ngIf="todos.length > 0">
    <span class="todo-count"><strong>{{todos.length}}</strong> {{todos.length == 1 ? 'item' : 'items'}} left</span>
  </footer>
</section>

Here is a super-short primer on Angular’s template syntax in case you haven’t seen it yet:

  • [property]="expression": set property of an element to the value of expression
  • (event)="statement": execute statement when event occurred
  • [(property)]="expression": create two-way binding with expression
  • [class.special]="expression": add special CSS class to element when the value of expression is truthy
  • [style.color]="expression": set color CSS property to the value of expression

If you’re not familiar with Angular’s template syntax, you should definitely read the official template syntax documentation.

Let’s see what that means for our view. At the top there is an input to create a new todo:

<input class="new-todo" placeholder="What needs to be done?" autofocus="" [(ngModel)]="newTodo.title" (keyup.enter)="addTodo()">
  • [(ngModel)]="newTodo.title": adds a two-way binding between the input value and newTodo.title
  • (keyup.enter)="addTodo()": tells Angular to execute addTodo() when the enter key was pressed while typing in the input element

Don’t worry about where newTodo or addTodo() come from yet, we will get there shortly. Just try to understand the semantics of the view for now.

Next there is a section to display existing todo’s:

<section class="main" *ngIf="todos.length > 0">
  • *ngIf="todos.length > 0": only show the section element and all its children when there is at least one todo

Within that section, we ask Angular to generate an li element for each todo:

<li *ngFor="let todo of todos" [class.completed]="todo.complete">
  • *ngFor="let todo of todos": loop over all todo’s and assign current todo to a variable called todo for each iteration
  • [class.completed]="todo.complete": apply CSS class completed to li element when todo.complete is truthy

and finally we display todo details for each individual todo:

<div class="view">
  <input class="toggle" type="checkbox" (click)="toggleTodoComplete(todo)" [checked]="todo.complete">
  <label>{{todo.title}}</label>
  <button class="destroy" (click)="removeTodo(todo)"></button>
</div>
  • (click)="toggleTodoComplete(todo)": execute toggleTodoComplete(todo) when the checkbox is clicked
  • [checked]="todo.complete": assign the value of todo.complete to the property checked of the element
  • (click)="removeTodo(todo)": execute removeTodo(todo) when the destroy button is clicked

OK, let’s breathe. That was quite a bit of syntax we went through.

If you want to learn every detail about Angular’s template syntax, make sure you read the official template documentation.

You may wonder how expressions like addTodo() and newTodo.title can be evaluated. We haven’t defined them yet, so how does Angular know what we mean?

That’s exactly where the expression context comes in. An expression context is a context in which expressions are evaluated. The expression context of a component is the component instance. And the component instance is an instance of the component class.

The component class of our AppComponent is defined in src/app/app.component.ts.

Angular CLI already created some boilerplate code for us:

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

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'app works!';
}

so we can immediately start adding our custom logic.

We will need the TodoDataService service in our AppComponent logic, so let’s start by injecting the service in our component.

First we import TodoDataService and specify it in the providers array of the Component decorator:

// Import class so we can register it as dependency injection token
import {TodoDataService} from './todo-data.service';

@Component({
  // ...
  providers: [TodoDataService]
})
export class AppComponent {
  // ...
}

The AppComponent‘s dependency injector will now recognize the TodoDataService class as a dependency injection token and return a single instance of TodoDataService when we ask for it.

Angular’s dependency injection system accepts a variety of dependency injection recipes. The syntax above is a shorthand notation for the Class provider recipe that provides dependencies using the singleton pattern. Check out Angular’s dependency injection documentation for more details.

Now that the component’s dependency injector knows what it needs to provide, we ask it to inject the TodoDataService instance in our component by specifying the dependency in the AppComponent constructor:

// Import class so we can use it as dependency injection token in the constructor
import {TodoDataService} from './todo-data.service';

@Component({
  // ...
})
export class AppComponent {

  // Ask Angular DI system to inject the dependency
  // associated with the dependency injection token `TodoDataService`
  // and assign it to a property called `todoDataService`
  constructor(private todoDataService: TodoDataService) {
  }

  // Service is now available as this.todoDataService
  toggleTodoComplete(todo) {
    this.todoDataService.toggleTodoComplete(todo);
  }
}

The use of public or private on arguments in the constructor is a shorthand notation that allows us to automatically create properties with that name, so:

class AppComponent {

  constructor(private todoDataService: TodoDataService) {
  }
}

is a shorthand notation for:

class AppComponent {

  private todoDataService: TodoDataService;

  constructor(todoDataService: TodoDataService) {
    this.todoDataService = todoDataService;
  }
}

We can now implement all view logic by adding properties and methods to our AppComponent class:

import {Component} from '@angular/core';
import {Todo} from './todo';
import {TodoDataService} from './todo-data.service';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
  providers: [TodoDataService]
})
export class AppComponent {

  newTodo: Todo = new Todo();

  constructor(private todoDataService: TodoDataService) {
  }

  addTodo() {
    this.todoDataService.addTodo(this.newTodo);
    this.newTodo = new Todo();
  }

  toggleTodoComplete(todo) {
    this.todoDataService.toggleTodoComplete(todo);
  }

  removeTodo(todo) {
    this.todoDataService.deleteTodoById(todo.id);
  }

  get todos() {
    return this.todoDataService.getAllTodos();
  }

}

We first define a newTodo property and assign a new Todo() when the component class is instantiated. This is the same Todo instance specified in the two-way binding expression of [(ngModel)] in our view:

<input class="new-todo" placeholder="What needs to be done?" autofocus="" [(ngModel)]="newTodo.title" (keyup.enter)="addTodo()">

Whenever the input value changes in the view, the value in the component instance is updated. And whenever the value in the component instance changes, the value in the input element in the view is updated.

Next, we implement all methods we used in our view:

addTodo() {
  this.todoDataService.addTodo(this.newTodo);
  this.newTodo = new Todo();
}

toggleTodoComplete(todo) {
  this.todoDataService.toggleTodoComplete(todo);
}

removeTodo(todo) {
  this.todoDataService.deleteTodoById(todo.id);
}

get todos() {
  return this.todoDataService.getAllTodos();
}

Their implementation is very short and should be self-explanatory as we delegate all business logic to the todoDataService.

Delegating business logic to a service is a good programming practice as it allows us to centrally manage and test it.

Before we try the result in our browser, let’s run our unit tests again:

$ ng test
05 12 2016 01:16:44.714:WARN [karma]: No captured browser, open http://localhost:9876/
05 12 2016 01:16:44.722:INFO [karma]: Karma v1.2.0 server started at http://localhost:9876/
05 12 2016 01:16:44.722:INFO [launcher]: Launching browser Chrome with unlimited concurrency
05 12 2016 01:16:44.725:INFO [launcher]: Starting browser Chrome
05 12 2016 01:16:45.373:INFO [Chrome 54.0.2840 (Mac OS X 10.12.0)]: Connected on socket /#WcdcOx0IPj-cKul8AAAA with id 19440217
Chrome 54.0.2840 (Mac OS X 10.12.0) AppComponent should create the app FAILED
        Can't bind to 'ngModel' since it isn't a known property of 'input'. ("">
            <h1>Todos</h1>
            <input class="new-todo" placeholder="What needs to be done?" autofocus="" [ERROR ->][(ngModel)]="newTodo.title" (keyup.enter)="addTodo()">
          </header>
          <section class="main" *ngIf="tod"): AppComponent@3:78
        Error: Template parse errors:
            at TemplateParser.parse (webpack:///Users/jvandemo/Projects/jvandemo/todo-app/~/@angular/compiler/src/template_parser/template_parser.js:97:0 <- src/test.ts:11121:19)
            at RuntimeCompiler._compileTemplate (webpack:///Users/jvandemo/Projects/jvandemo/todo-app/~/@angular/compiler/src/runtime_compiler.js:255:0 <- src/test.ts:25503:51)
            at webpack:///Users/jvandemo/Projects/jvandemo/todo-app/~/@angular/compiler/src/runtime_compiler.js:175:47 <- src/test.ts:25423:62
            at Set.forEach (native)
            at RuntimeCompiler._compileComponents (webpack:///Users/jvandemo/Projects/jvandemo/todo-app/~/@angular/compiler/src/runtime_compiler.js:175:0 <- src/test.ts:25423:19)
            at createResult (webpack:///Users/jvandemo/Projects/jvandemo/todo-app/~/@angular/compiler/src/runtime_compiler.js:86:0 <- src/test.ts:25334:19)
            at RuntimeCompiler._compileModuleAndAllComponents (webpack:///Users/jvandemo/Projects/jvandemo/todo-app/~/@angular/compiler/src/runtime_compiler.js:90:0 <- src/test.ts:25338:88)
            at RuntimeCompiler.compileModuleAndAllComponentsSync (webpack:///Users/jvandemo/Projects/jvandemo/todo-app/~/@angular/compiler/src/runtime_compiler.js:62:0 <- src/test.ts:25310:21)
            at TestingCompilerImpl.compileModuleAndAllComponentsSync (webpack:///Users/jvandemo/Projects/jvandemo/todo-app/~/@angular/compiler/bundles/compiler-testing.umd.js:482:0 <- src/test.ts:37522:35)
            at TestBed._initIfNeeded (webpack:///Users/jvandemo/Projects/jvandemo/todo-app/~/@angular/core/bundles/core-testing.umd.js:758:0 <- src/test.ts:7065:40)
...
Chrome 54.0.2840 (Mac OS X 10.12.0): Executed 14 of 14 (3 FAILED) (0.316 secs / 0.245 secs)

Three tests are failing with the following error: Can't bind to 'ngModel' since it isn't a known property of 'input'..

Let’s open up src/app/app.component.spec.ts:

/* tslint:disable:no-unused-variable */

import { TestBed, async } from '@angular/core/testing';
import { AppComponent } from './app.component';

describe('AppComponent', () => {
  beforeEach(() => {
    TestBed.configureTestingModule({
      declarations: [
        AppComponent
      ],
    });
  });

  it('should create the app', async(() => {
    let fixture = TestBed.createComponent(AppComponent);
    let app = fixture.debugElement.componentInstance;
    expect(app).toBeTruthy();
  }));

  it(`should have as title 'app works!'`, async(() => {
    let fixture = TestBed.createComponent(AppComponent);
    let app = fixture.debugElement.componentInstance;
    expect(app.title).toEqual('app works!');
  }));

  it('should render title in a h1 tag', async(() => {
    let fixture = TestBed.createComponent(AppComponent);
    fixture.detectChanges();
    let compiled = fixture.debugElement.nativeElement;
    expect(compiled.querySelector('h1').textContent).toContain('app works!');
  }));
});

The reason Angular complains about not knowing ngModel, is because the FormsModule is not loaded when the AppComponent is instantiated by Karma using the TestBed.createComponent() method.

To learn more about TestBed, make sure to check out the Official Angular documentation on testing.

To make sure Angular also loads the FormsModule when Karma instantiates the AppComponent using TestBed.createComponent(), we must specify FormsModule in the imports property of the Testbed configuration object:

/* tslint:disable:no-unused-variable */

import { TestBed, async } from '@angular/core/testing';
import { AppComponent } from './app.component';
import { FormsModule } from '@angular/forms';

describe('AppComponent', () => {
  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [
        FormsModule
      ],
      declarations: [
        AppComponent
      ],
    });
  });

  it('should create the app', async(() => {
    let fixture = TestBed.createComponent(AppComponent);
    let app = fixture.debugElement.componentInstance;
    expect(app).toBeTruthy();
  }));

  it(`should have as title 'app works!'`, async(() => {
    let fixture = TestBed.createComponent(AppComponent);
    let app = fixture.debugElement.componentInstance;
    expect(app.title).toEqual('app works!');
  }));

  it('should render title in a h1 tag', async(() => {
    let fixture = TestBed.createComponent(AppComponent);
    fixture.detectChanges();
    let compiled = fixture.debugElement.nativeElement;
    expect(compiled.querySelector('h1').textContent).toContain('app works!');
  }));
});

We now have 2 failing tests:

Chrome 54.0.2840 (Mac OS X 10.12.0) AppComponent should have as title 'app works!' FAILED
    Expected undefined to equal 'app works!'.
        at webpack:///Users/jvandemo/Projects/jvandemo/todo-app/src/app/app.component.spec.ts:28:22 <- src/test.ts:46473:27
        at ZoneDelegate.invoke (webpack:///Users/jvandemo/Projects/jvandemo/todo-app/~/zone.js/dist/zone.js:232:0 <- src/test.ts:50121:26)
        at AsyncTestZoneSpec.onInvoke (webpack:///Users/jvandemo/Projects/jvandemo/todo-app/~/zone.js/dist/async-test.js:49:0 <- src/test.ts:34133:39)
        at ProxyZoneSpec.onInvoke (webpack:///Users/jvandemo/Projects/jvandemo/todo-app/~/zone.js/dist/proxy.js:76:0 <- src/test.ts:34825:39)
Chrome 54.0.2840 (Mac OS X 10.12.0) AppComponent should render title in a h1 tag FAILED
    Expected 'Todos' to contain 'app works!'.
        at webpack:///Users/jvandemo/Projects/jvandemo/todo-app/src/app/app.component.spec.ts:35:53 <- src/test.ts:46479:58
        at ZoneDelegate.invoke (webpack:///Users/jvandemo/Projects/jvandemo/todo-app/~/zone.js/dist/zone.js:232:0 <- src/test.ts:50121:26)
        at AsyncTestZoneSpec.onInvoke (webpack:///Users/jvandemo/Projects/jvandemo/todo-app/~/zone.js/dist/async-test.js:49:0 <- src/test.ts:34133:39)
        at ProxyZoneSpec.onInvoke (webpack:///Users/jvandemo/Projects/jvandemo/todo-app/~/zone.js/dist/proxy.js:76:0 <- src/test.ts:34825:39)
Chrome 54.0.2840 (Mac OS X 10.12.0): Executed 14 of 14 (2 FAILED) (4.968 secs / 4.354 secs)

Karma warns us that the component instance does not have a property title that equals app works! and that there is no h1 element that contains app works!.

That is correct because we changed the component logic and template. So let’s update the unit tests accordingly:

/* tslint:disable:no-unused-variable */

import { TestBed, async } from '@angular/core/testing';
import { AppComponent } from './app.component';
import { FormsModule } from '@angular/forms';
import { Todo } from './todo';

describe('AppComponent', () => {
  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [
        FormsModule
      ],
      declarations: [
        AppComponent
      ],
    });
  });

  it('should create the app', async(() => {
    let fixture = TestBed.createComponent(AppComponent);
    let app = fixture.debugElement.componentInstance;
    expect(app).toBeTruthy();
  }));

  it(`should have a newTodo todo`, async(() => {
    let fixture = TestBed.createComponent(AppComponent);
    let app = fixture.debugElement.componentInstance;
    expect(app.newTodo instanceof Todo).toBeTruthy()
  }));

  it('should display "Todos" in h1 tag', async(() => {
    let fixture = TestBed.createComponent(AppComponent);
    fixture.detectChanges();
    let compiled = fixture.debugElement.nativeElement;
    expect(compiled.querySelector('h1').textContent).toContain('Todos');
  }));
});

We first add a unit test to make sure the newTodo property is instantiated correctly:

it(`should have a newTodo todo`, async(() => {
  let fixture = TestBed.createComponent(AppComponent);
  let app = fixture.debugElement.componentInstance;
  expect(app.newTodo instanceof Todo).toBeTruthy()
}));

and then we add a unit test to make sure the h1 element contains the expected string:

it('should display "Todos" in h1 tag', async(() => {
  let fixture = TestBed.createComponent(AppComponent);
  fixture.detectChanges();
  let compiled = fixture.debugElement.nativeElement;
  expect(compiled.querySelector('h1').textContent).toContain('Todos');
}));

Now our tests are running successfully:

$ ng test
WARN [karma]: No captured browser, open http://localhost:9876/
INFO [karma]: Karma v1.2.0 server started at http://localhost:9876/
INFO [launcher]: Launching browser Chrome with unlimited concurrency
INFO [launcher]: Starting browser Chrome
INFO [Chrome 55.0.2883 (Mac OS X 10.12.0)]: Connected on socket /#S1TIAhPPqLOV0Z3NAAAA with id 73327097
Chrome 54.0.2840 (Mac OS X 10.12.0): Executed 14 of 14 SUCCESS (0.411 secs / 0.402 secs)

If you want to learn more about testing, be sure to check out the chapter on Testing in the Official Angular documentation.

Feel free to play around with the live demo to see what the result looks like.

Before we wrap up this article, let’s have a look at one last really cool feature of Angular CLI.

Deploying to GitHub Pages

Angular CLI makes it super simple to deploy our application to GitHub Pages with a single command like this:

$ ng github-pages:deploy --message 'deploy(dist): deploy on GitHub pages'

The github-pages:deploy command tells Angular CLI to build a static version of our Angular application and push it to the gh-pages branch of our GitHub repository:

$ ng github-pages:deploy --message 'deploy(dist): deploy on GitHub pages'
Built project successfully. Stored in "dist/".
Deployed! Visit https://sitepoint-editors.github.io/todo-app/
Github pages might take a few minutes to show the deployed site.

Our application is now available at https://sitepoint-editors.github.io/todo-app/.

How awesome is that!

Summary

Angular 2 is a beast, no doubt. A very powerful beast!

In this first article, we learned:

  • how to kickstart a new Angular application using Angular CLI
  • how to implement business logic in an Angular service and how to test our business logic with unit tests
  • how to use a component to interact with the user and how to delegate logic to a service using dependency injection
  • the basics of Angular template syntax, briefly touching on how Angular dependency injection works
  • finally, we learned how to quickly deploy our application to GitHub Pages

There is a lot more to learn about Angular 2. In the next part of this series, we will have a look at how we can create separate components to display a list of todos and individual todo details.

So stay tuned for more about this wonderful world of Angular 2.

Have you built anything with Angular 2 yet? Are you planning to upgrade your Angular 1.x applications? Please get in touch via the comments and let us know what you think!

Ready for a another app development tutorial. Watch our screencast Get Started Building Ember Apps to continue your learning.

This article was peer reviewed by Vildan Softic. Thanks to all of SitePoint’s peer reviewers for making SitePoint content the best it can be!

More:
  • ccpony

    THANK YOU! Excellent presentation.

  • ccpony

    Well, after problems running ng serve and ng test, I’ve stopped testing Angular-cli. Is this product REALLY in beta – – because it’s performing like a pre-alpha.

    • jvandemo

      @ccpony:disqus – What problem are you seeing? Any specific error? Thanks!

  • bennypowers

    Great tutorial. I had a funny issue when applying some of the concepts I learned here to a half-started project. I had previously created a component called “QuoteComponent” When I did `ng g class quote` it caused karma tests to fail (TypeError: quote_1:Quote is not a constructor). I solved the problem by renaming QuoteComponent to SingleQuoteComponent (in all my files) and removing references to the old barrels in system-config.ts.

    • jvandemo

      @bennypowers:disqus – Was it caused by the fact that you already had an existing class with the name `QuoteComponent`? Thanks!

      • bennypowers

        It seems like it. A lot of the angular 2 tutorials out in the wild feature hand-spun components/services/classes with similar names (e.g. ticket.class.ts, ticket.service.ts, ticket.component.ts) but when it comes to ng-cli, it’s best to keep to unique names

        • jvandemo

          Great to know, thank you for sharing @bennypowers:disqus!

  • bennypowers

    Why didn’t you create a SingleToDo component to render each todo item?

    • jvandemo

      @bennypowers:disqus – Hi Benny, you can totally do that. We kept things as short as possible for the sake of article length (as it was already lengthy enough) but in a larger application that would definitely be the recommended approach. #thumbsUp

  • certainly better that the entire angular 2 tutorial(heroes tour or whatever) they have :)

    • Thanks!

    • jksg

      For sure a good, compact tutorial. But not for beginners. To many steps skipped (see comments) and boilerplate generators are in general no good advice for novices to become familiar with a new technology. Generators simply “generate” to much automatically. Newbees won’t understand what it is doing magically.
      But for everyone who worked with the official tutorial this is a good next step to become more productive. Indeed, I personally think the official tutorial is not that bad. If offers plenty of ways to extend it; much more as a simple TODO app allows.

  • G.J Anagnostopoulos

    Super useful. Thanks!

  • Vassilios

    Thanks for the tutorial! It’s been really helpful.
    One little thing to note: in order for the whole project to work you would need to add the following into angular2-todo-app.component.ts:

    ...
    import { TodoAppComponent } from './todo-app/todo-app.component';

    @Component({
    ...
    directives: [TodoAppComponent]
    })
    ...

    and in angular2-todo-app.component.html:

    ...
    <app-todo-app></app-todo-app>

    • jvandemo

      @Vassilios31:disqus – All code from the article is available right here: https://github.com/sitepoint-editors/angular2-todo-app. It should work out-of-the-box.

      Does it not work for you?

      Thanks!

      • Pooper Pig

        I think the point is that it works if you download the code – but if you follow the tutorial it doesn’t quite give enough steps to make all of the required changes

        • Adam Morgan

          Exactly. Following the tutorial results in an app that doesn’t even work. Why not finish it? Tutorials like this frustrate the hell out of beginners.

          • This looks like an oversight on our behalf, we’ll ping them to add the code we missed out.

          • jvandemo

            Thank you, that makes sense. I’ll check if we can add it to the article. Thank you for your feedback!

          • Vincent H

            If you go through his git repository you can see the step by step. You’ll independently have to look up some things but its solid. That’s what I’m doing

    • Carlos Roso

      Thank you!! Was banging my head for 2 hours, why da heck does the component not show!! it was due to this…

  • Pooper Pig

    An otherwise good tutorial spoiled by the lack of completeness… that or the authors left the last couple of things as an exercise for the reader :)

    In index.html you also need to change
    Loading…
    to
    Loading…

    And main.ts needs to be changed to

    import { bootstrap } from ‘@angular/platform-browser-dynamic’;
    import { enableProdMode } from ‘@angular/core’;
    import {environment} from ‘./app/’;
    import { TodoAppComponent } from ‘./app/todo-app’;

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

    bootstrap(TodoAppComponent);

    • jvandemo

      @pooperpig:disqus – You can choose to do that indeed.

      However, in the article we kept `app-root` generated by Angular CLI as a wrapper component for the `todo-app` component: https://github.com/sitepoint-editors/angular2-todo-app/blob/master/src/app/app.component.ts.

      Thank you for your feedback.

      (The source code for the code in the article is available right here if you are interested: https://github.com/sitepoint-editors/angular2-todo-app.)

    • Elad Nava

      Thanks for this. Still, the end result looks so hideous compared to the demo posted in the article. Would’ve been nice if they provided the CSS as well as actually went over the tutorial to verify it’s complete.

      • Roberto Maldonado

        Ok, so it looks different because you are not using the styling they use. Try searching in npm´s site for the packages called “todomvc-app-css” and “todomvc-common”. Finally include the styling sheets (.css files) in the index.html before closing the “” tag. I hope this works for you.

    • Roberto Maldonado

      Sure I had the same problem… but it is a good thing that they leave some research for the developer to do. In most cases is what happens with the documentation, specially with new technology like Angular2.

  • karthik

    hi, i am new in angular 2. i used bootstrap 3.6. i have confusion for using angular 2 controls like file upload, chips tags, autocomplete. If any documents or example are available please post..

  • Juan Carlos Calix

    I have problem when to do ng test ? it is necesarty to do this for to be practice?

    • jvandemo

      What problem are you seeing Juan? The `ng test` command runs the unit tests so it’s not needed to build your app for production but it’s highly recommended to write (and run) unit tests.

    • Gabrielle

      I read on StackOverflow that if you run “ng build” and then “ng test –build=false”, it bypasses the build problems of ng test. Works for me, don’t know if you have the same problem though..

      • jvandemo

        @juan_carlos_calix:disqus – Gabrielle’s advice is solid. You should definitely try it. Thanks Gabrielle!

  • Claude Yu

    I copied movieApp from your site and applied on my local machine. However,
    I tried to localise the rest-api without using movieapp-sitepoint… but rather used Strongloop(loopback) as my rest-api and MongoDB as db posted all movies into it and succeeded in CRUD operations within Loopback only. However, when i tried to use your client app(index.html) by using http://localhost:3000/movieApp/index.html got access error. Also when i used Brackets developing tool and ran live preview of index.html only showed simple The Movie App .home no other content available to interact with the Strongloop rest-api. I changed the service.js to point to /api/movies/.id without use. Could you or anyone help solve my stumbling block?

    • jvandemo

      Hi Claude, I’m assuming you are referring to another article. Or do I understand incorrectly? Thanks!

      • Claude Yu

        Hi jvandemo,

        You are right! I followed Part-1 of this article on another Angular JS doing movieApp. Don’t know if you know they article’s owner and passed my queries to him so that i can work that in my localhost instead. I could run the same thing in the browser by pointing to the sitepoint, just like laymen can it without skill needed. I would like to change that and run that in my localhost. Or you can get me some idea?? Thanks jvandemo.

        • Claude Yu

          to cont.. jvd
          that article was released one year ago and the owner Sandeep not responded to any new issues.

          • jvandemo

            Unfortunately I haven’t read that article so it’s hard for me to judge what could be wrong. You could contact the SitePoint team to ask whether they can get you in contact with the original author too. The SitePoint team is really great so I think they would genuinely help you out where they can. Or if you can post the code as a plunk on plnkr.co, I’d be happy to have a look. Thanks!

  • jvandemo

    @juan_carlos_calix:disqus – There seems to be an issue on this in GitHub: https://github.com/angular/angular-cli/issues/864 and https://github.com/angular/angular-cli/issues/977. Can you try `ng build && ng test –build=false`? Thanks!

    • Yep, ran into that, too. Separating the build and the test fixes it up for me.

      • jvandemo

        Thanks for the confirmation Ryan.

  • Alexander Leon

    Great tutorial! I’m wondering if this part’s redundant:

    constructor(values: Object = {}) {
    Object.assign(this, values);
    }

    since we specify property values in the TodoService anyways.

  • Versuvious St

    DOES ANYBODY NOTICED THE PERFORMANCE? I SAW 341 REQUESTS IN THE CONSOLE. YOU PEOPLE ARE INSANE.

    • This is whilst in development, not production. Prod builds are vastly different if you try it.

      • Sam Vloeberghs

        with http2 that will not even matter :)

  • Abubaker Shangab

    implement routing to make it a multi-page application??? why problem with simple routing? ha?
    That is why they call it the “Bleeding edge of ng2)”. They keep changing how you do things like hell

    • jvandemo

      We decided to split up the routing part because the router API was not stable yet at the time of writing. Thus the code would have been deprecated by the time the article was published. The routing part is coming soon though! :-)

      • Abubaker Shangab

        Good work, keep up the spirit. Angular 2 is totally a new product than angular 1, you had the opportuity to change the name :-), I did angular 1 for around two years now and was about to switch to ember when all of a sudden angular 2 came.

        • jvandemo

          Thank you!

  • John Rizzo

    Great article. I noticed a few things.

    1. If you want the todo-app.component.spec.ts to work you need to update it to something akin to the following;

    /* tslint:disable:no-unused-variable */

    import {
    addProviders,
    beforeEach, beforeEachProviders,
    describe, xdescribe,
    expect, it, xit,
    async, inject
    } from ‘@angular/core/testing’;
    import { By } from ‘@angular/platform-browser’;
    import { DebugElement } from ‘@angular/core’;
    import { TodoAppComponent } from ‘./todo-app.component’;
    import { TodoService } from ‘../todo.service’;

    describe(‘Component: TodoApp’, () => {
    beforeEachProviders(() => [TodoService]);
    it(‘should create an instance’, () => {
    inject([TodoAppComponent], (component: TodoAppComponent) => {
    expect(component).toBeTruthy();
    })
    });
    });

    2. If you are going to try and deploy this to github-pages then you need to make sure that you create a repository on github and then set the remote and commit all local changes. For example;

    > git remote add origin git@github.com:johnrizzo1/angular2-todo-app.git
    > git add .
    > git commit -a -m ‘pushing to github pages’
    > ng github-pages:deploy –message ‘deploy(dist): deploy on GitHub pages’

    Before I could execute the push to github pages I had to delete the dist directory. Not sure why but apparently I’m not the only one who had this issue, https://github.com/angular/angular-cli/issues/1409. Try pushing and if that is a problem then delete the dist directory and then push again.

    > rm -rf dist/
    > ng github-pages:deploy –message ‘deploy(dist): deploy on GitHub pages’

    Thanks again for the article. I look forward to more of them.

    • jvandemo

      @disqus_GtNTPpVDOp:disqus – Thank you for your feedback, much appreciated!

  • Roberto Maldonado

    It is a great tutorial and very well explained.

    This is a good base to understand relationship with the .ts files and the .spec.ts files. It also highlights the relationship between the Class and the Service used in the to-do application. Thanks.

  • Rakeysh Patel

    Getting this error

    The Broccoli Plugin: [BroccoliTypeScriptCompiler] failed with:
    Error: Typescript found the following errors:
    E:/nodejs/angular2-todo-app/tmp/broccoli_type_script_compiler-input_base_path-
    yRX6acLj.tmp/0/src/app/todo.service.spec.ts (22, 19): Supplied parameters do not
    match any signature of call target.
    E:/nodejs/angular2-todo-app/tmp/broccoli_type_script_compiler-input_base_path-
    yRX6acLj.tmp/0/src/app/todo.service.spec.ts (23, 19): Supplied parameters do not
    match any signature of call target.

    • Chinedu Oty

      Hi, the problem is from your todo.spec.ts

      change from ‘@angular/testing’; to from ‘@angular/core/testing’;

      The core is missing.

  • Ibrahim Cissé

    Hi, could someone tell me why I get this :

    ======> assign does not exist on type objectConstructor ? !

    export class Todo {
    id: number;
    title: string = ”;
    complete: boolean = false;

    constructor(values: Object = {}) {
    Object.assign(this, values);
    }
    }

    • jvandemo

      Can you please try (Object).assign(this, values); ? Can you let us know if it works so we can update the code accordingly? Thanks!

  • Frank

    I tried some hours with the tutorial and could not get it to work.

    So i tried downloading the GIT and used that.

    The GIT also does not work, same problem as Ibrahim Cissé, in the browser, localhost:4200:

    Build Error: ‘assign’ does not exist on type ‘objectConstructor’

    eg in todo.service.ts, line 39, but it happens with all appearances of: Object.assign(this, values);

    angular-cli: 1.0.0-beta.6
    node: 4.4.4
    os: darwin x64

    No helpful hints on the net (at least nothing i cound get along with).

    Any help would be very welcome, wouldn’t have thought this becomes that unsatisfying.

    • James Hibbard

      Hey Frank, sorry about this. Please see my comment above.

  • razorree

    a big mess…… Angular mixed with JS ? where is the proper backend ? how to use Angular2 with a backend stuff (like Java) ?

    • Eduardo Sanchez

      where is the JS?

      • razorree

        sorry, i wasn’t clear. i meant – code is combined spoiled with JS at backand.

        I want to learn Angular2 and combine that with Java backend, but all examples are a mess… angular2 + nodejs, etc.
        a lot of different libraries,
        and i want to create just simple web app using angular2, include some files using element, that’s it !

  • Didn’t learn anything from the above

  • al joulson

    These tutorials are like amazon products, you have to check the reviews first. Perhaps you should mention this not for beginners so people don’t waist there time trying this out only to then see your not explaining your damn code and the person get’s totally stuck and learned nothing. thanks!

    • Macharius

      Or… You can look up the entire article before getting into it to see if it is what you are looking for.

      • RyanThePatriot

        Seriously… what is up with the incredible feeling of entitlement coming from the consumers of these great tutorials?! They do realize how much time is spent creating these don’t they? Or are they just too lazy to do what you recommended? I signed into the Disqus on this article merely because I couldn’t standby without putting my $.02 in the ring on how God awful these people are behaving. I’m glad I’m not the only one with those feelings.

        Thank you @jvandemo:disqus and @toddmotto:disqus.

        • Welcome to the internet ;) ha :) thanks for the kind words man. Happy coding!

        • jvandemo

          @RyanThePatriot:disqus – Thank you for your kind comment. Your words are worth all the effort. Much appreciated!

  • James Hibbard

    2016.09.26 To everybody having trouble running the code

    Apologies.

    Angular 2 is moving so fast, that it’s hard to stay on top of the current state of things.

    I’ve just tested this using Node v6.2 and can confirm that with a couple of minor changes, it should work as expected.


    git clone git@github.com:sitepoint-editors/angular2-todo-app.git
    cd angular2-todo-app
    npm install
    npm install -g typings
    typings install

    Then edit line 39 of src/app/todo.service.ts and line 8 of src/app/todo.ts changing Object.assign(this, values); to (Object).assign(this, values);

    After which the code should run as expected.

    Once again, apologies for the confusion. We’ll get the article updated ASAP.

    We’re also going to be publishing an article detailing how to setup a build environment with the Angular CLI.

    • Frank

      Hi and thank you!
      I can confirm this is working, i didn’t unse the brackets around Object when i tried it before.
      One thing: i can now download a full working copy and get it going, but this was not the intention of this tutorial, was it? Will it be working for anyone coming here the first time who does not read through all the comments before he/she starts? That would help a lot in not giving visitors bad moods.

      • James Hibbard

        That’s a good point, Frank, thanks! I’ll add a note to the top of the tutorial.

        Also, once the above-mentioned article on setting up a build environment with the Angular CLI has been published, we’re going to update this article to reflect the current state of affairs.

      • Ganesh

        yes, it worked me in the first attempt..

        • jvandemo

          @Ganesh – Thank you for the confirmation, much appreciated!

    • Jim

      When I finished these additional steps, the installation of typings finished with a warning:
      typings WARN enoent Path “path/to/angulr2-todo-app/typings.json” is missing
      so I executed
      typings init
      typings install
      and this time it reported no warnings. I’m still working through all these comments to fix the other “Angular 2 is moving so fast” updates, so I don’t really know yet if the init step is all that was needed, but that’s my experience so far. angular-cli: 1.0.0-beta.21; node: 7.1.0; npm: 3.10.9; typings 2.0.0

      • jvandemo

        Thanks for sharing your experiences. A rewrite of this article with the latest version of Angular CLI is coming soon, so we will take your comment into account. Much appreciated!

  • Frank

    This stands corrected, see comment above.

  • K Sole

    Hi chaps great job here, whilst the code may not compile or run properly, it does provide a very good conceptual overview and starting point fo angular 2 development for a newbie such as myself. I’ve searched to no avail for the version of this tutorial you mentioned that would connect to a rest service, I imagine you haven’t got around to it. is it posible you provide a few bullet point steps on how to acheive this connectivity. many thanks in advance

    • Nilson Jacques

      Thanks, glad you enjoyed the article! We’ve got the follow-up scheduled for the end of this month.

    • jvandemo

      @ksole:disqus – Thank you, we appreciate your feedback. We are working on an update of this article to reflect the latest updates from Angular 2 final. As soon as it has been published, we are also going to publish an article about communicating with the REST API. Thanks again!

      • K Sole

        thanks for the quick response, I’m currently tasked with developing an angular 2 , spring (mvc) application. I was hoping you could give me a few bullet points steps on how to acheive connectivity beteween angular and spring. Your help would be obviously greatly appreciated.

        • jvandemo

          @ksole:disqus – We are working hard on the follow-up articles that will be released over the next couple of weeks. The REST article is one of them. We are still researching the steps that are needed with Angular 2 final so we can’t really summarise them in a comment at this stage yet I’m afraid.

  • Austin_Ed

    I’m getting a different error from everyone else. I guess the testing model/framework has changed?

    ERROR in [default] ./angular2-todo-app/src/app/todo.spec.ts:2:2
    Module ‘”./angular2-todo-app/node_modules/@angular/core/testing/index”‘ has no exported member ‘beforeEach’.

    ERROR in [default] ./angular2-todo-app/src/app/todo.spec.ts:2:14
    Module ‘”./angular2-todo-app/node_modules/@angular/core/testing/index”‘ has no exported member ‘beforeEachProviders’.

    ERROR in [default] ./angular2-todo-app/src/app/todo.spec.ts:3:2
    Module ‘”./angular2-todo-app/node_modules/@angular/core/testing/index”‘ has no exported member ‘describe’.

    ERROR in [default] ./angular2-todo-app/src/app/todo.spec.ts:3:12
    Module ‘”./angular2-todo-app/node_modules/@angular/core/testing/index”‘ has no exported member ‘xdescribe’.

    ERROR in [default] ./angular2-todo-app/src/app/todo.spec.ts:4:2
    Module ‘”./angular2-todo-app/node_modules/@angular/core/testing/index”‘ has no exported member ‘expect’.

    ERROR in [default] ./angular2-todo-app/src/app/todo.spec.ts:4:10
    Module ‘”./angular2-todo-app/node_modules/@angular/core/testing/index”‘ has no exported member ‘it’.

    ERROR in [default] ./angular2-todo-app/src/app/todo.spec.ts:4:14
    Module ‘”./angular2-todo-app/node_modules/@angular/core/testing/index”‘ has no exported member ‘xit’.

  • Avram Virgil

    The tutorial in this page works as is in the latest CLI build, only thing that I did was to add everything to the ngModule.
    The css for this you can get it from here sitepoint-editors.github.io/angular2-todo-app/css/todo-mvc.css copy and paste the css into the style.css in your src folder.

    This is not a tutorial for beginners in angular2, it is more of how to make a todo app in angular2, if you are new to angular2 but not new to javascript that this should have looked really easy. For me the methods where complicated ( I am a beginner in java script ).

  • Mehdi Chihaoui

    Hi, when i generate the class Todo with g generate, it just create todo.ts , no todo.spec.ts ? ?

    • Bryan Wilber

      I have the same issue. Why isn’t todo.spec.ts created?

      • David Mallinson

        yes the command needs the –spec option e.g. ng generate class Todo –spec

        • jvandemo

          @mehdi_chihaoui:disqus, @bryanwilber:disqus: @disqus_WzgbQGgweA:disqus is right, you now need to specify the –spec option. The updated version of this article will be online in a couple of days. Thanks!!

  • JCharly

    Great tutorial!! Helps to point out several aspects I think are key to understand how an A2 app works. Clear and straight to the point, nice job.

  • Aleksander

    Have the new articles been posted yet? where can i find them?

    • jvandemo

      The updated version of this article is currently being reviewed and will be published shortly. Thanks!

  • Ripulryu

    Advanced and short tutorial which focussed on implementation. Exactly what I was searching for. Kudos to authors !

    • jvandemo

      @Ripulryu:disqus Thank you, really appreciate your kind words. There is an update of this article coming shortly.

  • hanthuy

    Thanks for the tutorial. I finally manage to make it work on angular-cli version 1.00 (Dec 12 – 2016)

    You can take a look at my code: https://github.com/dalenguyen/angular2-todo-app

    • jvandemo

      Awesome, thanks for sharing!

  • Jeff Lu

    @toddmotto:disqus do you have an updated version of this todo tutorial that works with the latest ng-cli?

    • jvandemo

      Hey Jeff, the updated version is ready and is currently being edited for publication. Should be online soon now. Thanks!

  • Frand

    Hi quick question: im having this error when using ng test:
    Uncaught TypeError: testing_1.describe is not a function
    at webpack:///home/francisco/angular2-todo-app/src/app/todo.spec.ts:9:0 <- src/test.ts:46429

    I copy/pasted everything and did everything like it says. I cant find where this testing_1 is coming from.
    Tthanks!

    • jvandemo

      @disqus_lVJj9qf7Yv:disqus – What version of the CLI are you using? There is an updated article that is ready to be published that uses a more recent version of the CLI. Thanks in advance!

    • Nilson Jacques

      Hey @disqus_lVJj9qf7Yv:disqus, the article has been extensively updated by @jvandemo:disqus – please let me know if you have any problems following along.

  • Todd Zmijewski

    All I ever see in these ng and/or react tutorials are stupid, simple apps like that dumb todo list. How about a real world example of like a blog or forum with user authentication and multiple data entry forms including uploading files with a WYSIWG or something.

    • I’m sure @jameshibbard:disqus would love to see you contribute this kind of article to SitePoint.

      • Todd Zmijewski

        Why would I do that…

        • steven kauyedauty

          because it it more rewarding than simply trolling the internet.

          • Todd Zmijewski

            It also takes up much more of my time.

    • jvandemo

      @toddzmijewski:disqus I think the wide variety of topics would almost lead to a book and would be very hard to bundle in an article or an article series. Don’t you think? Thanks for your feedback!

  • curd

  • hakkatil

    Thank you for the tutorial.
    But, it is a lot of code for a single to-do list. Why do I need AngularJs if I can code this with 15 lines of code using Jquery and PHP?

    • HAT.

      good one! I have been searching online and everyone is talking about how good angular is that it makes you write less code. tbh i dont see how that is possible! :/

  • Slim Comar

    where are the second and third parts at?

    • jvandemo

      The rewrite of this first part is now being edited and will be published really soon. Then part 2, 3 and 4 will follow in the beginning of next year. Thanks!

  • Slim Comar

    thanks :)

  • suman dahal

    nice tutorial, I don’t fine link for others series, waiting for complete part, thank you for awesome tutorial

  • Giorgio Riccardi

    a typo here “To make sure the business logic in our TodoDataService service works as expected, we also add some additional unit tests in src/app/todo.service.spec.ts:” should say “… in src/app/todo-data.service.spec.ts:”

    • jvandemo

      @giorgioriccardi:disqus Thank you, nice catch! A fix has been created and submitted for publication. Thank you for reporting!

  • Roy Lin

    Instead of this.todos.push(todo) for adding, you could do this.todos = this.todos.concat(todo)
    which is consistent with what you do with removing.

    You may then reek the benefits of pure pipes down the track if you decide to use them.

    • jvandemo

      Thanks for the suggestion, that would indeed be nice if you want the reference to change. We may suggest this in a follow up article as an optimised version, so thanks for your feedback.

  • Mirek

    nice quickstart for Angular 2, thx

    • jvandemo

      Thank you, much appreciated!

  • Elhoussine Talab

    I really like the style you got for the app more than the Angular logic, good job!

    • jvandemo

      Thank you, we hope the Angular code was still useful as well ;-)

  • subchan yayan bachtiar

    nice angular 2 get started tutorial, so helpful

    thank you

    • jvandemo

      Thank you, we really appreciate your kind words.

  • Thanks for awesome tutorial it helpful to all developers.

    • jvandemo

      Thank you for your kind words, much appreciated!

  • Marcus Reed

    where is part 2,3,4?

    • jvandemo

      Part 2 is coming soon. Part 3 and 4 are in the making. Thanks!

      • Martin Piontek

        That’s far too slow :P If every part needs 2 months, there will be angular 3, when i finished the tutorial.

        • jvandemo

          @martinpiontek:disqus – Yes, we are aware of that. We had to rewrite the first parts first to make sure they align with the latest version of Angular. And if all goes well, we should have Angular 4 somewhere in March indeed :-)

        • jvandemo

          @martinpiontek:disqus – Quick update: part 2 is currently being reviewed and will be published soon.

      • Benny E. Willyanto

        waiting for next tutorial part…
        nice tutorial

        • jvandemo

          @bennyewillyanto:disqus Thank you, much appreciated, part 2 is coming soon.

        • jvandemo

          @bennyewillyanto:disqus – Quick update: part 2 is currently being reviewed and will be published soon.

        • jvandemo

          @bennyewillyanto:disqus – Part 2 has been released right here: https://www.sitepoint.com/understanding-component-architecture-angular/. Thank you!

      • Saman

        Any update on when Part 2 ?

    • jvandemo

      @disqus_5Db01Dz6Ig:disqus – Part 2 has been released right here: https://www.sitepoint.com/understanding-component-architecture-angular/. Thank you!

  • Prabin Giri

    awesome, But I got problem, after creating the ClI project and running through the server, i got the same result again and again when i change the code. Please help me, I am new to this. Is this a configuration problem or something else

    • jvandemo

      @prabin_giri:disqus – Can you list the commands that your are performing? Thanks!

      • Prabin Giri

        yeh! found the solution, all i have to reset my counter and have to restart my PC. thank you… :)

        • jvandemo

          @prabin_giri:disqus Ok, awesome, thanks for the update!

  • great job. keep it up. when are the next parts coming?

  • Bielik

    Great tutorial indeed, can’t wait for the second part.
    Sadly GitHub pages deployment doesn’t work in the current sate (at least for me). Maybe I have done something wrong but local version works just fine. Here is a link if you wish to inspect the problem:
    https://github.com/Bielik20/Todo-Angular
    commit: 0bc54f19b08b2408a1520e09f5c4f5d6b6aaf4a4

    I will try to replicate changes from your repository to make it work. I am leaving comment so that you are aware of the problem. Once again great job :)

  • Buddhi Prabhath

    great tutorial to learn angular CRUD

    • jvandemo

      Thank you for your kind words.

  • jvandemo

    @Frank – Quick update: part 2 is currently being reviewed and will be published soon.

    • Shashank

      @jvandemo:disqus any updates on the release date ?

      • jvandemo

        @disqus_1UcgDGfM1r:disqus Yes, part 2 has been reviewed, updated and is now being edited. It should be published within one or two weeks. Thanks!

  • jmc92

    This is dope! <3 Better than tutorial on the angular2 site. Not only this article made me understand the bigger picture but helped me understood the testing process as well! Looking forward for the second part!

  • Meyssa Jebri

    Thanks that was very helpful and pedagogic as I am moving from Angular 1 to 2. Where can I find the next series?

    • jvandemo

      Thank you for your kind words. The next article is currently being reviewed and will be published soon.

  • Tiglath

    Hi Folks, I am now using @angular/cli v1.0.0-rc.0 and apparently some while back in one of the beta releases they removed the ng github-pages:deploy command. However; some kind person has created an angular-cli-ghpages package available on npm. You will need to use ng build –prod command to create a production build first and then you can use ngh to perform the deploy. You will also need to configure your app repository for GitHub Pages and set the source to the gh-pages branch (This branch is created the first time you run ngh). Then you can run your app using the url: https://{your username}.github.io/{your repository}/

  • Leandrit Ferizi

    It was a truly good read. Thanks a lot

    • jvandemo

      Thank you, we really appreciate your kind words.

  • Adam Ilčišák

    Thank you Guys, great tutorial. Very helpful that you don’t waste time with elementary things and you focus on more complicated concepts.

    • jvandemo

      Thank you, Adam, we really appreciate your kind words.

  • Manish Thomas

    Thanks for the tutorial!
    Pls check if the task entered is empty. Its accepting empty string.

    • James Hibbard

      Hey Manish, probably the easiest way to do this is to check the value of the submitted Todo in the addTodo() method and take no action if it is an empty string.

      addTodo() {
      if(this.newTodo.title === '') return;

      this.todoDataService.addTodo(this.newTodo);
      this.newTodo = new Todo();
      }

    • jvandemo

      @manishthomas:disqus – Thank you for your feedback, validation will be added in an upcoming article.

  • Miguel Rodriguez

    Same feeling as the other guys….thank you for this tutorial….great way to get started with Angular2

    • jvandemo

      @disqus_lrEvZp8ZWX:disqus – Thank you, we really appreciate your kind words.

  • John

    Hello,

    I am having an issue when I run ng test. Jasmine is saying that it cannot read property ‘length’ of an undefined. II think this is in reference to the app component template and the property that is undefined is the array of todo objects, todos. Would anyone happen to know why this is happening or what could be going wrong?

    Great tutorial by the way, I am learning a lot!

  • jvandemo

    Part 2 has been released right here: https://www.sitepoint.com/understanding-component-architecture-angular/. Thank you!

  • jvandemo

    @Otaku – Part 2 has been released right here: https://www.sitepoint.com/understanding-component-architecture-angular/. Thank you!

  • Vamsi

    Can you provide the style sheet too, which is used in the html

  • Downloading a hundred of files for a simple project !!!
    A wast of time.

    • James Hibbard

      Yeah, I know, right. You could have done all of this in one monolithic jQuery file called main.js

      Seriously though, it depends on your use case. If you’re making a data-driven web app with a lot of moving parts, then the modularity soon pays off.

  • Nova Lufian Sovie

    i have problem
    20 03 2017 14:25:41.982:WARN [karma]: No captured browser, open http://localhost:9876/
    20 03 2017 14:25:42.004:INFO [karma]: Karma v1.4.1 server started at http://0.0.0.0:9876/
    20 03 2017 14:25:42.006:INFO [launcher]: Launching browser chromium-browser with unlimited concurrency
    20 03 2017 14:25:42.008:ERROR [launcher]: Cannot load browser “chromium-browser”: it is not registered! Perhaps you are missing some plugin?
    20 03 2017 14:25:42.008:ERROR [karma]: Found 1 load error

    how to fix it ?
    thanks

    • jvandemo

      Please check whether you have `chromium-browser` installed by running `which chromium-browser` on the command line. If it is installed, then it will return the path, e.g. `/usr/bin/chromium-browser`.

      Then set the path by running `export CHROME_BIN=/usr/bin/chromium-browser` so Karma can locate it.

      Can you let us know if that works? Thanks!

  • Rupesh Kumar Tiwari

    Thanks Todd Motto,
    I enjoyed the tutorial and learned many things. Going to start next tutorial soon.
    However, how to put CSS files you have not explained in this part of the tutorial.
    My first page is not looking good. I hope you will explain CSS files in next chapter.

  • Jake Corn

    This is fantastic. Thanks so much

    • jvandemo

      Thank you, Jake, we really appreciate your kind words.

  • William Everett

    You can install angular-cli using “npm install -g @angular/cli” For me, using “npm install -g angular-cli” didn’t work

    • jvandemo

      You are right. We just released an update for this article, which will be publish next week. Thank you for your feedback!

  • Martin Jul Hammer

    Excellent guide! Nice to see how to use unit tests, and the tidbit about updating the array was just what I needed. Thanks Jurgen and Todd!

    Edit:
    Uhhh, and I missed the deploy to github pages part. So smancy! Thanks again :)

    • jvandemo

      @martinjulhammer:disqus – Thank you, we really appreciate your kind feedback, Martin!

  • Rizki Adiguno Wibowo

    got two error

    AppComponent should display “Todos” in h1 tag
    Failed: Cannot read property ‘title’ of undefined

    TodoDataService #updateTodoById(id, values) should return null if todo is not found
    TypeError: Cannot read property ‘title’ of null

    how can I fix this?

Recommended
Sponsors
Get the latest in JavaScript, once a week, for free.