I started creating Angular 2 applications when it was in beta (back in March). To keep up with Angular 2's changes, I wrote a tutorial about developing with RC1 in June. Earlier this month, RC5 was released and many things changed once again. I think Scott Davis sums it up nicely in a tweet.

They keep saying "Release Candidate", but I don't think it means what they think it means...



/cc #angular2#rc5https://t.co/WmNalTYgTN — Scott Davis (@scottdavis99) August 10, 2016

To keep up with the rapid pace of change in Angular 2, I decided to write another tutorial, this time using Angular CLI. The biggest change I found since writing the last tutorial is testing infrastructure changes. Since Angular's Testing documentation hasn't been updated recently, hopefully this tutorial will help.

Below is a table of contents in case you want to skip right to a particular section.

What you'll build

What you'll need

Create your project

Run the application

Add a search feature The Basics The Backend

Add an edit feature

Testing and CI (to be covered in the next post)



What You'll Build

You'll build a simple web application with Angular CLI, a new tool for Angular 2 development. You'll create an application with search and edit features.

What You'll Need

The latest release of Angular CLI (beta 10) uses Angular 2 RC4. Because of this, I used the master branch of Angular CLI to create this tutorial. To do this, clone angular-cli and run npm link in the directory you cloned it into. If you have issues, see #1733.

Angular Augury is a Google Chrome Dev Tools extension for debugging Angular 2 applications. I haven't needed it much myself, but I can see how it might come in handy.

Create Your Project

Create a new project using the ng new command:

ng new ng2-demo



This will create a ng2-demo project and run npm install in it. It takes about a minute to complete, but will vary based on your internet connection speed.

[mraible:~/dev] 45s $ ng new ng2-demo installing ng2 create .editorconfig create README.md create src/app/app.component.css create src/app/app.component.html create src/app/app.component.spec.ts create src/app/app.component.ts create src/app/environment.ts create src/app/index.ts create src/app/shared/index.ts create src/favicon.ico create src/index.html create src/main.ts create src/system-config.ts create src/tsconfig.json create src/typings.d.ts create angular-cli-build.js create angular-cli.json create config/environment.dev.ts create config/environment.js create config/environment.prod.ts create config/karma-test-shim.js create config/karma.conf.js create config/protractor.conf.js create e2e/app.e2e-spec.ts create e2e/app.po.ts create e2e/tsconfig.json create e2e/typings.d.ts create .gitignore create package.json create public/.npmignore create tslint.json create typings.json Successfully initialized git. - Installing packages for tooling via npm -- es6-shim (global) -- angular-protractor (global dev) -- jasmine (global dev) -- selenium-webdriver (global dev) Installed packages for tooling via npm. [mraible:~/dev] 1m5s $



You can see the what version of Angular CLI you're using with ng --version .

$ ng --version angular-cli: local (v1.0.0-beta.11-webpack.2, branch: master) node: 4.4.7 os: darwin x64



Run the Application

The project is configured with a simple web server for development. To start it, run:

ng serve



You should see a screen like the one below at http://localhost:4200.

You can make sure your new project's tests pass, run ng test :

$ ng test Built project successfully. Stored in "dist/". ... Chrome 52.0.2743 (Mac OS X 10.11.6): Executed 2 of 2 SUCCESS (0.039 secs / 0.012 secs)



Add a Search Feature

To add a search feature, open the project in an IDE or your favorite text editor. For IntelliJ IDEA, use File > New Project > Static Web and point to the ng2-demo directory.



The Basics

In a terminal window, cd into your project's directory and run the following command. This will create a search component.

$ ng g component search installing component create src/app/search/search.component.css create src/app/search/search.component.html create src/app/search/search.component.spec.ts create src/app/search/search.component.ts create src/app/search/index.ts





Adding a Search Route

In previous versions of CLI, you could generate a route and a component. However, since beta 8, route generation has been disabled. This will likely be re-enabled in a future release.

The Router documentation for Angular 2 RC5 provides the information you need to setup a route to the SearchComponent you just generated. Here's a quick summary:

Create src/app/app.routing.ts to define your routes.

import { Routes, RouterModule } from '@angular/router'; import { SearchComponent } from './search/index'; const appRoutes: Routes = [ { path: 'search', component: SearchComponent }, { path: '', redirectTo: '/search', pathMatch: 'full' } ]; export const appRoutingProviders: any[] = []; export const routing = RouterModule.forRoot(appRoutes);



Without the last path to redirect, there's a Cannot match any routes: '' console error.

In src/app/app.module.ts , import the two constants you exported and configure them in @NgModule :

import { routing, appRoutingProviders } from './app.routing'; import { SearchComponent } from './search/search.component'; @NgModule({ ... imports: [ ... routing ], providers: [appRoutingProviders], ... }) export class AppModule { }



In src/app/app.component.html , add a RouterOutlet to display routes.

<!-- Routed views go here --> <router-outlet></router-outlet>



Now that you have routing setup, you can continue writing the search feature.

To allow navigation to the SearchComponent , you can add a link in src/app/app.component.html .

<nav> <a routerLink="/search" routerLinkActive="active">Search</a> </nav>



Open src/app/search/search.component.html and replace its default HTML with the following:

<h2>Search</h2> <form> <input type="search" name="query" [(ngModel)]="query" (keyup.enter)="search()"> <button type="button" (click)="search()">Search</button> </form> <pre> {{searchResults | json}} </pre>



If you still have ng serve running, your browser should refresh automatically. If not, navigate to http://localhost:4200, and you should see the search form.

If you want to add CSS for this components, open src/app/search/search.component.css and add some CSS. For example:

:host { display: block; padding: 0 20px; }



This section has shown you how to generate a new component to a basic Angular 2 application with Angular CLI. The next section shows you how to create a use a JSON file and localStorage to create a fake API.

The Backend

To get search results, create a SearchService that makes HTTP requests to a JSON file. Start by generating a new service.

ng g service search



Move the generated search.service.ts and its test to app/shared/search . You will likely need to create this directory.

Then, create src/app/shared/search/data/people.json to hold your data.

[ { "id": 1, "name": "Peyton Manning", "phone": "(303) 567-8910", "address": { "street": "1234 Main Street", "city": "Greenwood Village", "state": "CO", "zip": "80111" } }, { "id": 2, "name": "Demaryius Thomas", "phone": "(720) 213-9876", "address": { "street": "5555 Marion Street", "city": "Denver", "state": "CO", "zip": "80202" } }, { "id": 3, "name": "Von Miller", "phone": "(917) 323-2333", "address": { "street": "14 Mountain Way", "city": "Vail", "state": "CO", "zip": "81657" } } ]



Modify src/app/shared/search/search.service.ts and provide Http as a dependency in its constructor. In this same file, create a getAll() method to gather all the people. Also, define the Address and Person classes that JSON will be marshalled to.

import { Injectable } from '@angular/core'; import { Http, Response } from '@angular/http'; @Injectable() export class SearchService { constructor(private http: Http) {} getAll() { return this.http.get('app/shared/search/data/people.json').map((res: Response) => res.json()); } } export class Address { street: string; city: string; state: string; zip: string; constructor(obj?: any) { this.street = obj && obj.street || null; this.city = obj && obj.city || null; this.state = obj && obj.state || null; this.zip = obj && obj.zip || null; } } export class Person { id: number; name: string; phone: string; address: Address; constructor(obj?: any) { this.id = obj && Number(obj.id) || null; this.name = obj && obj.name || null; this.phone = obj && obj.phone || null; this.address = obj && obj.address || null; } }



To make these classes available for consumption by your components, edit src/app/shared/index.ts and add the following:

export * from './search/search.service';



In search.component.ts , add imports for these classes.

import { Person, SearchService } from '../shared/index';



You can now add query and searchResults variables. While you're there, modify the constructor to inject the SearchService .

export class SearchComponent implements OnInit { query: string; searchResults: Array<Person>; constructor(private searchService: SearchService) {}



Then implement the search() method to call the service's getAll() method.

search(): void { this.searchService.getAll().subscribe( data => { this.searchResults = data; }, error => console.log(error) ); }



At this point, you'll likely see the following message in your browser's console.

ORIGINAL EXCEPTION: No provider for SearchService!



To fix the "No provider" error from above, update app.component.ts to import the SearchService and add the service to the list of providers.

import { SearchService } from './shared/index'; @Component({ ... styleUrls: ['app.component.css'], viewProviders: [SearchService] })



Now clicking the search button should work. To make the results look better, remove the <pre> tag and replace it with a <table> .

<table *ngIf="searchResults"> <thead> <tr> <th>Name</th> <th>Phone</th> <th>Address</th> </tr> </thead> <tbody> <tr *ngFor="let person of searchResults; let i=index"> <td> {{person.name}} </td> <td> {{person.phone}} </td> <td> {{person.address.street}} <br/> {{person.address.city}} , {{person.address.state}} {{person.address.zip}} </td> </tr> </tbody> </table>



Then add some additional CSS to improve its table layout.

table { margin-top: 10px; border-collapse: collapse; } th { text-align: left; border-bottom: 2px solid #ddd; padding: 8px; } td { border-top: 1px solid #ddd; padding: 8px; }



Now the search results look better.

But wait, we still don't have search functionality! To add a search feature, add a search() method to SearchService .

search(q: string) { if (!q || q === '*') { q = ''; } else { q = q.toLowerCase(); } return this.getAll().map(data => { let results: any = []; data.map(item => { if (JSON.stringify(item).toLowerCase().includes(q)) { results.push(item); } }); return results; }); }



Then refactor SearchComponent to call this method with its query variable.

search(): void { this.searchService.search(this.query).subscribe( data => { this.searchResults = data; }, error => console.log(error) ); }



Now, search results will be filtered by the query value you type in.

This section showed you how to fetch and display search results. The next section builds on this and shows how to edit and save a record.

Add an Edit Feature

Modify search.component.html to add a click handler for editing a person.

<td><a (click)="onSelect(person)">{person.name}</a></td>





In previous versions of Angular 2, you could embed a link with parameters directly into the HTML. For example:

<a [routerLink]="['/edit', person.id]">



Unfortunately, this doesn't work with RC5. Another issue is adding href="" causes the page to refresh. Without href , the link doesn't look like a link. If you know of a solution to this problem, please send me a pull request.

Then add onSelect(person) to search.component.ts . You'll need to import Router and set it as a local variable to make this work.

import { Router } from '@angular/router'; ... export class SearchComponent implements OnInit { ... constructor(private searchService: SearchService, private router: Router) { } ... onSelect(person: Person) { this.router.navigate(['/edit', person.id]); } }

Run the following command to generate an EditComponent .

$ ng g component edit installing component create src/app/edit/edit.component.css create src/app/edit/edit.component.html create src/app/edit/edit.component.spec.ts create src/app/edit/edit.component.ts create src/app/edit/index.ts



Add a route for this component in app.routing.ts :

import { EditComponent } from './edit/index'; const appRoutes: Routes = [ { path: 'search', component: SearchComponent }, { path: 'edit/:id', component: EditComponent }, { path: '', redirectTo: '/search', pathMatch: 'full' } ];



Update src/app/edit/edit.component.html to display an editable form. You might notice I've added id attributes to most elements. This is to make things easier when writing integration tests with Protractor.

<div *ngIf="person"> <h3> {{editName}} </h3> <div> <label>Id:</label> {{person.id}} </div> <div> <label>Name:</label> <input [(ngModel)]="editName" name="name" id="name" placeholder="name"/> </div> <div> <label>Phone:</label> <input [(ngModel)]="editPhone" name="phone" id="phone" placeholder="Phone"/> </div> <fieldset> <legend>Address:</legend> <address> <input [(ngModel)]="editAddress.street" id="street"><br/> <input [(ngModel)]="editAddress.city" id="city">, <input [(ngModel)]="editAddress.state" id="state" size="2"> <input [(ngModel)]="editAddress.zip" id="zip" size="5"> </address> </fieldset> <button (click)="save()" id="save">Save</button> <button (click)="cancel()" id="cancel">Cancel</button> </div>



Modify EditComponent to import model and service classes and to use the SearchService to get data.

import { Component, OnInit, OnDestroy } from '@angular/core'; import { Address, Person, SearchService } from '../shared/index'; import { Subscription } from 'rxjs'; import { ActivatedRoute, Router } from '@angular/router'; @Component({ selector: 'app-edit', templateUrl: 'edit.component.html', styleUrls: ['edit.component.css'] }) export class EditComponent implements OnInit, OnDestroy { person: Person; editName: string; editPhone: string; editAddress: Address; sub: Subscription; constructor(private route: ActivatedRoute, private router: Router, private service: SearchService) { } ngOnInit() { this.sub = this.route.params.subscribe(params => { let id = + params['id']; // (+) converts string 'id' to a number this.service.get(id).subscribe(person => { if (person) { this.editName = person.name; this.editPhone = person.phone; this.editAddress = person.address; this.person = person; } else { this.gotoList(); } }); }); } ngOnDestroy() { this.sub.unsubscribe(); } cancel() { this.router.navigate(['/search']); } save() { this.person.name = this.editName; this.person.phone = this.editPhone; this.person.address = this.editAddress; this.service.save(this.person); this.gotoList(); } gotoList() { if (this.person) { this.router.navigate(['/search', {term: this.person.name} ]); } else { this.router.navigate(['/search']); } } }



Modify SearchService to contain functions for finding a person by their id, and saving them. While you're in there, modify the search() method to be aware of updated objects in localStorage .

search(q: string) { if (!q || q === '*') { q = ''; } else { q = q.toLowerCase(); } return this.getAll().map(data => { let results: any = []; data.map(item => { // check for item in localStorage if (localStorage['person' + item.id]) { item = JSON.parse(localStorage['person' + item.id]); } if (JSON.stringify(item).toLowerCase().includes(q)) { results.push(item); } }); return results; }); } get(id: number) { return this.getAll().map(all => { if (localStorage['person' + id]) { return JSON.parse(localStorage['person' + id]); } return all.find(e => e.id === id); }); } save(person: Person) { localStorage['person' + person.id] = JSON.stringify(person); }



You can add CSS to src/app/edit/edit.component.css if you want to make the form look a bit better.

:host { display: block; padding: 0 20px; } button { margin-top: 10px; }



At this point, you should be able to search for a person and update their information.

The <form> in src/app/edit/edit.component.html calls a save() function to update a person's data. You already implemented this above. The function calls a gotoList() function that appends the person's name to the URL when sending the user back to the search screen.

gotoList() { if (this.person) { this.router.navigate(['/search', {term: this.person.name} ]); } else { this.router.navigate(['/search']); } }



Since the SearchComponent doesn't execute a search automatically when you execute this URL, add the following logic to do so in its constructor.

import { Router, ActivatedRoute } from '@angular/router'; import { Subscription } from 'rxjs'; ... sub: Subscription; constructor(private searchService: SearchService, private router: Router, private route: ActivatedRoute) { this.sub = this.route.params.subscribe(params => { if (params['term']) { this.query = decodeURIComponent(params['term']); this.search(); } }); }



You'll want to implement OnDestroy and define the ngOnDestroy method to clean up this subscription.

import { Component, OnInit, OnDestroy } from '@angular/core'; export class SearchComponent implements OnInit, OnDestroy { ... ngOnDestroy() { this.sub.unsubscribe(); } }



After making all these changes, you should be able to search/edit/update a person's information. If it works - nice job!

Conclusion

Wait, we're not done yet! Stay tuned for part 2 where we'll continue by going over automated testing and continuous integration in Angular 2.