Angular Reactive Form Example

import { Component, OnInit } from '@angular/core';
import { FormGroup, FormControl } from '@angular/forms';
import { Customer } from './customer';
@Component({
selector: 'app-customer',
templateUrl: './customer.component.html',
styleUrls: ['./customer.component.css']
})
export class CustomerComponent implements OnInit {
customerForm: FormGroup;
customer = new Customer();
constructor() { }
/* This can be replaced by using: this.customForm = new FormGroup({ firstName: '', lastName: '', ...
OR ... email: {value: 'n/a', disabled: tue}}) */
ngOnInit() {
this.customerForm = new FormGroup({
firstName: new FormControl(),
lastName: new FormControl(),
email: new FormControl(),
sendCatalog: new FormControl(true)
});
}
save() {
console.log(this.customerForm);
console.log('Saved: ' + JSON.stringify(this.customerForm.value));
}
/* use setValue to change every value */
testData() {
this.customerForm.patchValue({
firstName: 'Geoff'
});
}
}
<div class="card">
<div class="card-header">
Sign Up!
</div>
<div class="card-body">
<form novalidate
(ngSubmit)="save()"
[formGroup]="customerForm">
<div class="form-group row mb-2">
<label class="col-md-2 col-form-label" for="firstNameId"
>First Name</label
>
<div class="col-md-8">
<input
class="form-control"
id="firstNameId"
type="text"
placeholder="First Name (required)"
required
minlength="3"
formControlName="firstName"
name="firstName"
[ngClass]="{
'is-invalid':
(customerForm.get('firstName').touched || customerForm.get('firstName').dirty) &&
!customerForm.get('firstName').valid
}"
/>
<span class="invalid-feedback">
<span *ngIf="customerForm.get('firstName').errors?.required">
Please enter your first name.
</span>
<span *ngIf="customerForm.get('firstName').errors?.minlength">
The first name must be longer than 3 characters.
</span>
</span>
</div>
</div>
<div class="form-group row mb-2">
<label class="col-md-2 col-form-label" for="lastNameId"
>Last Name</label
>
<div class="col-md-8">
<input
class="form-control"
id="lastNameId"
formControlName="lastName"
type="text"
placeholder="Last Name (required)"
required
maxlength="50"
[ngClass]="{
'is-invalid':
(customerForm.get('lastName').touched || customerForm.get('lastName').dirty) && !customerForm.get('lastName').valid
}"
/>
<span class="invalid-feedback">
<span *ngIf="customerForm.get('lastName').errors?.required">
Please enter your last name.
</span>
<span *ngIf="customerForm.get('lastName').errors?.maxlength">
The last name must be less than 50 characters.
</span>
</span>
</div>
</div>
<div class="form-group row mb-2">
<label class="col-md-2 col-form-label" for="emailId">Email</label>
<div class="col-md-8">
<input
class="form-control"
id="emailId"
formControlName="email"
type="email"
placeholder="Email (required)"
required
email
name="email"
[ngClass]="{
'is-invalid':
(customerForm.get('email').touched || customerForm.get('email').dirty) && !customerForm.get('email').valid
}"
/>
<span class="invalid-feedback">
<span *ngIf="customerForm.get('email').errors?.required">
Please enter your email address.
</span>
<span *ngIf="customerForm.get('email').errors?.email">
Please enter a valid email address.
</span>
</span>
</div>
</div>
<div class="form-group row mb-2">
<div class="col-md-8">
<div class="form-check">
<label class="form-check-label">
<input
class="form-check-input"
id="sendCatalogId"
type="checkbox"
name="sendCatalog"
/>
Send me your catalog
</label>
</div>
</div>
</div>
<div *ngIf="customer.sendCatalog">
<div class="form-group row mb-2">
<label class="col-md-2 col-form-label pt-0">Address Type</label>
<div class="col-md-8">
<div class="form-check form-check-inline">
<label class="form-check-label">
<input
class="form-check-input"
id="addressType1Id"
type="radio"
value="home"
name="addressType"
/>
Home
</label>
</div>
<div class="form-check form-check-inline">
<label class="form-check-label">
<input
class="form-check-input"
id="addressType1Id"
type="radio"
value="work"
name="addressType"
/>
Work
</label>
</div>
<div class="form-check form-check-inline">
<label class="form-check-label">
<input
class="form-check-input"
id="addressType1Id"
type="radio"
value="other"
name="addressType"
/>
Other
</label>
</div>
</div>
</div>
<div class="form-group row mb-2">
<label class="col-md-2 col-form-label" for="street1Id"
>Street Address 1</label
>
<div class="col-md-8">
<input
class="form-control"
id="street1Id"
type="text"
placeholder="Street address"
name="street1"
/>
</div>
</div>
<div class="form-group row mb-2">
<label class="col-md-2 col-form-label" for="street2Id"
>Street Address 2</label
>
<div class="col-md-8">
<input
class="form-control"
id="street2Id"
type="text"
placeholder="Street address (second line)"
name="street2"
/>
</div>
</div>
<div class="form-group row mb-2">
<label class="col-md-2 col-form-label" for="cityId"
>City, State, Zip Code</label
>
<div class="col-md-3">
<input
class="form-control"
id="cityId"
type="text"
placeholder="City"
name="city"
/>
</div>
<div class="col-md-3">
<select
class="form-control"
id="stateId"
name="state"
>
<option value="" disabled selected hidden
>Select a State...</option
>
<option value="AL">Alabama</option>
<option value="AK">Alaska</option>
<option value="AZ">Arizona</option>
<option value="AR">Arkansas</option>
<option value="CA">California</option>
<option value="CO">Colorado</option>
<option value="WI">Wisconsin</option>
<option value="WY">Wyoming</option>
</select>
</div>
<div class="col-md-2">
<input
class="form-control"
id="zipId"
type="number"
placeholder="Zip Code"
name="zip"
/>
</div>
</div>
</div>
<div class="form-group row mb-2">
<div class="offset-md-2 col-md-4">
<button
class="btn btn-primary mr-3"
type="submit"
style="width:80px"
[title]="
customerForm.valid
? 'Save your entered data'
: 'Disabled until the form data is valid'
"
[disabled]="!customerForm.valid"
>
Save
</button>
<button class="btn btn-warning" (click)="testData()">Test Data</button>
</div>
</div>
</form>
</div>
</div>
<br />Dirty: {{ customerForm.dirty }} <br />Touched: {{ customerForm.touched }}
<br />Valid: {{ customerForm.valid }} <br />Value: {{ customerForm.value | json }}

Binding to properties and attributes – Danger You Need to Know This!

The distinction between an HTML attribute and a DOM property is one of the corner stones for understanding how binding in Angular works!

There are three distinct representations of the application data:

  • The HTML document is a collection of elements: consisting of HTML  tags and attributes both of type string
  • The DOM tree is a JavaScript object of type HTMLInputElement. The HTMLInputElement object includes the property type and value of type DOMString, and required of type boolean. It is the browser that instantiates the DOM objects (nodes) from the HTML document, that have properties which are rendered onto the web-page. Whenever the values of the DOM node’s properties are changed, the page is re-rendered. Reference online.

 

One-way Property Binding

One-way Attribute Binding

Angular Bindings – Deep Dive

Angular data binding allows you to connect the application data to the UI in a concise manner, updating in either direction as required. This article covers:


How and when to use the different forms of data binding

  1. Interpolation {{…}}
  2. Property binding [property] =”…”
  3. Event binding (event) = “…”
  4. Two-way data binding [(…)]=”…”

Interpolation

This allows injection of dynamic strings into the HTML text. It’s syntax is {{…}}

Examples
//display the component property string value 
<h1> Welcome {{useName}}!</h1>
//evaluate 1+2 and display "3"
<p> The sum of 1 + 2 = {{1+2}} </p>
//set the img tag, src attribute
<img src = {{imgUrl}} >
Interpolation syntax
//evaluated and converted to a string
{{ template expression }}
Style Guidelines

You can use functions eg. getVal() and complex logic with some restrictions, but as good practice: keep it simple, executing quickly and Impotent.

Restrictions

The template expression is not allowed to promote side effects, so the following is not allowed  (incomplete list)

  • assignments(=, +=, …)
  • new
  • chaining expressions with ; or ,
  • increment / decrement operators (++ ,  –)
Notes

It may appear that interpolation syntax is just inserting an evaluated result, but this is not quite true! Interpolation is a special form of property binding.


Property binding

This enables the setting of a property value on a view element. It’s syntax is […]

Examples
//set the <img> tag's src property
<img [src] = "imageUrl >
//disable a <button> per the Boolean value isUnchanged 
<button [disable] = "isUnchanged">Change</button>
// DOM element properties are a more common target
//but Angular directives may also be used eg. NgClass
<div [ngClass]="classes">[ngClass] binding to the classes property</div>

Property binding Syntax
//Set the Binging target to the component property's value
[Binding target] = "component property"
//Make sure it evaluates to the correct value type
Property binding special case syntax: One-time string initialization

The brackets are omitted if the binding target property is:

  • Of type string
  • A fixed value expressed in the template
  • It’s initial value is never changed
//the AppDetailComponent properties: salutation is fixed
// and name is dynamically updated
<app-detail> salutation = "Mr" [name] = "userName"></app-detail>>
Binding to a property or interpolation?

The key is readability period, but stick to a style.

Content Security
//HTML with script tags are NOT allowed to leak into the  
//browser, neither with interpolation nor property binding.
//WARNING: sanitizing HTML stripped some content (see http://g.co/ng/security#xss).
evilTitle = 'Template <script>alert("evil never sleeps")</script>Syntax';

Event-binding

This is a callback from the UI to the Component code. That is one-way binding from the UI . It’s syntax is (…).

Examples
//the target event (click) is bound to the Component method(Event Handler)
<button (click) = onSubmit()>Submit</button>
//Alternative canonical form
<button on-click = "onSubmit()>Submit</button>
Event-binding Syntax

 

Special Case Syntax: $event and event handling statements

in work…

Special Case: Custom events with EventEmitter

in work…

Notes

Template statements do have side effects – They are expected!

// Example from the Angular documentation
<hero-detail (deleteRequest)="deleteHero($event)" [hero]="currentHero"></hero-detail>
//the parent component binds to the HeroDetailComponent's
//deleteHero() method which uses the parent component hero 
//object passed into the method via $event variable.
//the method deleteHero(Hero) deletes this instance of hero.
//Now that's what I call a side effect! 

Two-way data binding

This is a short hand way of combining two forms of binding into one: property binding from a component code variable to the DOM property (UI property) with event binding from the UI to the component code via a method function event handler. It’s syntax combines the one-way property binding […] = “…” with  the one-way event binding (…) = “…”.

Two-way data binding syntax
//the two, one-way bindings
<input [value] = "myComponentProperty" 
   (input) = "onInputEvent($event) >
//"banana in a box" Angular notation for two-way binding
<input [( ngModel )] = "myComponentProperty" >
Notes

We are using an Angular built-in attribute directive “*NgModel” this is explained later. What you need to know for now is that this represent the data model.
NB. when writing the directive in this case use lowercase prefix form “ngModel”.


Binding to HTML attribute vs. DOM property

The distinction between an HTML attribute and a DOM property is one of the corner stones for understanding how binding in Angular works.

This article explains the important differences

Resources and Tools for Web Developers

This is my current list of learning resources and tools. I will try to keep this up to date. Your feedback is welcome!

ASP.Net

ASP has been  Microsoft’s framework for building web apps for many years.  This link ASP website provides information on the latest incarnation.

Use in App Development

This set of technologies provide the ability to build all aspects of a web app, from server side to client side. The “.NET Core” (formally called  ASP.NET 5 and MVC 6). Link to article, describes some of the changes this version introduced (This article, believe it or not , has now become some what outdated).  This is a major sea change not to be ignored (Not just my opinion).

My Use for this technology

My current use is VS2015 and MVC 5 for the server side code. The details of the various technology’s usage will be left for another blog article. While the client side code can also be developed in ASP.NET my current preference is to use Angular with Web API for the server.

UPDATE! It looks like I will move on to ASP.NET Core for the server. Stay tuned.

IDE Tooling

My current IDE (Interactive Development Environment) is Visual Studio 2015. VS 2017 is due for release in a few days!. https://www.visualstudio.com/

Angular 2

Angular is the latest version from Google. Often referred to as Angular 2, to differentiate it from Angular 1, who’s official name is AngularJS.

Use in App Development

This is a JavaScript based open-source front-end web application framework. It’s advantage is that it moves a lot of the processing to the client or browser. This in turn creates a more responsive feel for the user.  The app is known as a SPA (Single Page Application) because the new pages are not generated in the tradition manner by a HTTP request and response to a web server, but instead are a partial page updates.

My Use for this technology

This is my preferred frontend (Browser) technology. Currently it’s looking good for the near term but is evolving fast. It solves many issues over the older technologies. More detailed information to come!

IDE Tooling

My preferred tooling is the editor Visual Studio Code (free)

Google developed Angular not in straight JavaScript but by using TypeScript (Microsoft). This is my preferred development language.

Why TypeScript? http://www.typescriptlang.org/

 

After stagnating for many years JavaScript started to evolve into a modern language. It was misunderstood and considered a toy language due to  number of unfortunate developments. It is one of the core technologies of the internet along with html and css,  and because of this it cannot be ignored. A famous quote goes:

” any application that can be written in JavaScript, will eventually be written in JavaScript. ”   CODING HORROR

JavaScript started out as “LiveScript” . It was written by Brendan Eich in 10 days, in May 1995. That was the start of it’s troubles, it breaks a lot of behaviors adopted by other languages such as Java and C#. To add to it’s problems they renamed it “JavaScript” as a marketing gimmick. Now real confusion and controversy set-in.

 “Java is to JavaScript like car is to carpet”

The quirks of JavaScript and it’s fundamental underpinning of the internet have finally begun to be resolved.   in November 1996 Ecma International started to create a standard. Ecma Script (JavaScript’s official name) was created. The latest versions are ES6 and ES7(renamed ECMScript 2015 and ECMScript 2016 !)  These are the current versions, but new versions are being developed and the projected release cycle is now every 6 months.

Browser Compatibility

Browser behavior is notoriously none standard. So the question arises, how do we take advantage of the latest JavaScript features in development and deploy code that will consistently behave in all browsers? One answer is Typescript. Typescript is a supper set of JavaScript. It has all the latest JavaScript features but is only used in development code. Transpilers are used to convert the code into ES6 or ES5 for deployment. Again this is a very high level view and leaves much to be discussed in future blog articles.

Happy coding in LiveScript, JavaScript, ES6, ECMScript 2015, I mean TypeScript!

Tutorial Walk Through: Tour of Heroes in VSCode Editor – Part 3

This is  a continuation of the walk though and completion of the application.


7   HTTP

Our stakeholders appreciate our progress. Now they want to get the hero data from a server, let users add, edit, and delete heroes, and save these changes back to the server.

In this chapter we teach our application to make the corresponding HTTP calls to a remote server’s web API.

Providing HTTP Services

The HttpModule is not a core Angular module. It ships as ‘@angular/http’. This in turn is dependent on a whole set of HTTP Service. This was done deliberately, so that you as a user can pick only the elements you require for your particular application.

Adding to file: src/app/app.module.ts(v1) Making http available across components.

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { DashboardComponent } from './dashboard.component';
import { HeroesComponent } from './heroes.component';
import { HeroDetailComponent } from './hero-detail.component';
import { HeroService } from './hero.service';
@NgModule({
 imports: [
 BrowserModule,
 FormsModule,
 HttpModule,
 AppRoutingModule
 ],
 declarations: [
 AppComponent,
 DashboardComponent,
 HeroDetailComponent,
 HeroesComponent,
 ],
 providers: [ HeroService ],
 bootstrap: [ AppComponent ]
})
export class AppModule { }
Simulating the web API

We recommend registering application-wide services in the root AppModuleproviders.

Our application is in the early stages of development and far from ready for production. We don’t even have a web server that can handle requests for heroes. Until we do, we’ll have to fake it.

We’re going to trick the HTTP client into fetching and saving data from a mock service, the in-memory web API.

Here is a version of src/app/app.module.ts that performs this trick: (v2)

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

import { AppRoutingModule } from './app-routing.module';

// Imports for loading & configuring the in-memory web api
import { InMemoryWebApiModule } from 'angular-in-memory-web-api';
import { InMemoryDataService } from './in-memory-data.service';

import { AppComponent } from './app.component';
import { DashboardComponent } from './dashboard.component';
import { HeroesComponent } from './heroes.component';
import { HeroDetailComponent } from './hero-detail.component';
import { HeroService } from './hero.service';

@NgModule({
 imports: [
 BrowserModule,
 FormsModule,
 HttpModule,
 InMemoryWebApiModule.forRoot(InMemoryDataService),
 AppRoutingModule
 ],
 declarations: [
 AppComponent,
 DashboardComponent,
 HeroDetailComponent,
 HeroesComponent,
 ],
 providers: [ HeroService ],
 bootstrap: [ AppComponent ]
})
export class AppModule { }

You will probably at this point not have “in-memory-web-api” library.

npm i angular-in-memory-web-api

Rather than require a real API server, this example simulates communication with the remote server by adding the InMemoryWebApiModule to the module imports, effectively replacing the Http client’s XHR backend service with an in-memory alternative.

InMemoryWebApiModule.forRoot(InMemoryDataService),

The forRoot configuration method takes an InMemoryDataService class that primes the in-memory database as follows:

src/app/in-memory-data.service.ts

import { InMemoryDbService } from 'angular-in-memory-web-api';
export class InMemoryDataService implements InMemoryDbService {
 createDb() {
 let heroes = [
 {id: 11, name: 'Mr. Nice'},
 {id: 12, name: 'Narco'},
 {id: 13, name: 'Bombasto'},
 {id: 14, name: 'Celeritas'},
 {id: 15, name: 'Magneta'},
 {id: 16, name: 'RubberMan'},
 {id: 17, name: 'Dynama'},
 {id: 18, name: 'Dr IQ'},
 {id: 19, name: 'Magma'},
 {id: 20, name: 'Tornado'}
 ];
 return {heroes};
 }
}

This file replaces the mock-heroes.ts which is now safe to delete.

Heroes and HTTP

Look at our current HeroService implementation

getHeroes(): Promise<Hero[]> {
 return Promise.resolve(HEROES);
}

Let’s convert getHeroes() to use HTTP.

src/app/hero.service.ts (updated getHeroes and new class members)

  private heroesUrl = 'api/heroes'; // URL to web api

 constructor(private http: Http) { }

 getHeroes(): Promise<Hero[]> {
 return this.http.get(this.heroesUrl)
 .toPromise()
 .then(response => response.json().data as Hero[])
 .catch(this.handleError);
 }

 private handleError(error: any): Promise<any> {
 console.error('An error occurred', error); // for demo purposes only
 return Promise.reject(error.message || error);
 }

Our updated import statements are now:

import { Injectable } from '@angular/core';
import { Headers, Http } from '@angular/http';

import 'rxjs/add/operator/toPromise';

import { Hero } from './hero';

Check in browser – ALL OK?

Ref code file:  hero.service.ts

import { Injectable } from '@angular/core';
import { Headers, Http } from '@angular/http';

import 'rxjs/add/operator/toPromise';

import { Hero } from './hero';


@Injectable()
export class HeroService {
 private heroesUrl = 'api/heroes'; // URL to web api

 constructor(private http: Http) { }

 getHeroes(): Promise<Hero[]> {
 return this.http.get(this.heroesUrl)
 .toPromise()
 .then(response => response.json().data as Hero[])
 .catch(this.handleError);
 }

 private handleError(error: any): Promise<any> {
 console.error('An error occurred', error); // for demo purposes only
 return Promise.reject(error.message || error);
 }


 getHero(id: number): Promise<Hero> {
 return this.getHeroes()
 .then(heroes => heroes.find(hero => hero.id === id));
 }
}
HTTP Promise

We’re still returning a Promise but we’re creating it differently.

The Angular http.get returns an RxJS Observable. Observables are a powerful way to manage asynchronous data flows. We’ll learn about Observables later in this chapter.

For now we get back on familiar ground by immediately converting that Observable to a Promise using the toPromise operator.

Unfortunately, the Angular Observable doesn’t have a toPromise operator … not out of the box. The Angular Observable is a bare-bones implementation.

There are scores of operators like toPromise that extend Observable with useful capabilities. If we want those capabilities, we have to add the operators ourselves. That’s as easy as importing them from the RxJS library like this:

import 'rxjs/add/operator/toPromise';
Extracting the data in the then callback

In the promise’s then callback we call the json method of the HTTP Response to extract the data within the response.

.then(response => response.json().data as Hero[])

That response JSON has a single data property. The data property holds the array of heroes that the caller really wants. So we grab that array and return it as the resolved Promise value.

Pay close attention to the shape of the data returned by the server. This particular in-memory web API example happens to return an object with a data property. Your API might return something else. Adjust the code to match your web API.

The caller is unaware of these machinations. It receives a Promise of heroes just as it did before. It has no idea that we fetched the heroes from the (mock) server. It knows nothing of the twists and turns required to convert the HTTP response into heroes. Such is the beauty and purpose of delegating data access to a service like this HeroService.

Error Handling

At the end of getHeroes() we catch server failures and pass them to an error handler:

.catch(this.handleError);

This is a critical step! We must anticipate HTTP failures as they happen frequently for reasons beyond our control.

private handleError(error: any): Promise<any> {
 console.error('An error occurred', error); // for demo purposes only
 return Promise.reject(error.message || error);
}

In this demo service we log the error to the console; we would do better in real life.

We’ve also decided to return a user friendly form of the error to the caller in a rejected promise so that the caller can display a proper error message to the user.

Get hero by id

The HeroDetailComponent asks the HeroService to fetch a single hero to edit.

The HeroService currently fetches all heroes and then finds the desired hero by filtering for the one with the matching id. That’s fine in a simulation. It’s wasteful to ask a real server for all heroes when we only want one. Most web APIs support a get-by-id request in the form api/hero/:id (e.g., api/hero/11).

Update the HeroService.getHero method to make a get-by-id request, applying what we just learned to write getHeroes:

getHero(id: number): Promise<Hero> {
 const url = `${this.heroesUrl}/${id}`;
 return this.http.get(url)
 .toPromise()
 .then(response => response.json().data as Hero)
 .catch(this.handleError);
}

It’s almost the same as getHeroes. The URL identifies which hero the server should update by encoding the hero id into the URL to match the api/hero/:id pattern.

We also adjust to the fact that the data in the response is a single hero object rather than an array.

Unchanged getHeroes API

Although we made significant internal changes to getHeroes() and getHero(), the public signatures did not change. We still return a Promise from both methods. We won’t have to update any of the components that call them.

Our stakeholders are thrilled with the web API integration so far. Now they want the ability to create and delete heroes.

Update hero details

If we try to update hero’s name, the changes are lost on clicking back button!

Updates weren’t lost before. What changed? When the app used a list of mock heroes, updates were applied directly to the hero objects within the single, app-wide, shared list. Now that we are fetching data from a server, if we want changes to persist, we’ll need to write them back to the server.

Save hero details

Add to HeroDetailComponent (save):

In the template, html:

<button (click)="save()">Save</button>

In the class:

save(): void {
 this.heroService.update(this.hero)
 .then(() => this.goBack());
}

And now the “Update Service” (file: src/app/hero.service.ts):

private headers = new Headers({'Content-Type': 'application/json'});

update(hero: Hero): Promise<Hero> {
 const url = `${this.heroesUrl}/${hero.id}`;
 return this.http
 .put(url, JSON.stringify(hero), {headers: this.headers})
 .toPromise()
 .then(() => hero)
 .catch(this.handleError);
}

Check in browser “Save Updated Hero”

 

Explanation: We identify which hero the server should update by encoding the hero id in the URL. The put body is the JSON string encoding of the hero, obtained by calling JSON.stringify. We identify the body content type (application/json) in the request header.

Add a hero

To add a new hero we need to know the hero’s name. Let’s use an input element for that, paired with an add button.

Add to file: src/app/heroes.component.htm

<div>
 <label>Hero name:</label> <input #heroName />
 <button (click)="add(heroName.value); heroName.value=''">
 Add
 </button>
</div>

And Add to heroes.component.ts

add(name: string): void {
 name = name.trim();
 if (!name) { return; }
 this.heroService.create(name)
 .then(hero => {
 this.heroes.push(hero);
 this.selectedHero = null;
 });
}

When the given name is non-blank, the handler delegates creation of the named hero to the hero service, and then adds the new hero to our array.

And on the “heroService”
Add to file: src/app/hero.service.ts(create)

create(name: string): Promise<Hero> {
  return this.http
    .post(this.heroesUrl, JSON.stringify({name: name}), {headers: this.headers})
    .toPromise()
    .then(res => res.json().data)
    .catch(this.handleError);
}

Check in browser

 

Delete a hero

Add to file: src/app/heroes.component.html, the delete button (just the bold)

  <li *ngFor="let hero of heroes" (click)="onSelect(hero)"
 [class.selected]="hero === selectedHero">
 <span class="badge">{{hero.id}}</span>
 <span>{{hero.name}}</span>
 <button class="delete"
 (click)="delete(hero); $event.stopPropagation()">x</button>
 </li>

Add to file:src/app/heroes.component.ts  (delete method)

delete(hero: Hero): void {
 this.heroService
 .delete(hero.id)
 .then(() => {
 this.heroes = this.heroes.filter(h => h !== hero);
 if (this.selectedHero === hero) { this.selectedHero = null; }
 });
}

Add to file:src/app/heroes.component.css (additions)

button.delete {
  float:right;
  margin-top: 2px;
  margin-right: .8em;
  background-color: gray !important;
  color:white;
}

The delete handler method hero.service.ts

delete(id: number): Promise<void> {
 const url = `${this.heroesUrl}/${id}`;
 return this.http.delete(url, {headers: this.headers})
 .toPromise()
 .then(() => null)
 .catch(this.handleError);
}

Check in browser!

Observables

The service currently returns an Observable of HTTP Response object, which are converted into a Promise. Many times it is best to use the Observable directly. This is a discussion visited in this link. For now this concludes our project.

 

Tutorial Walk Through: Tour of Heroes in VSCode Editor – Part 2

This is  a continuation of the walk though of the application.

  1. Services
  2. Routing
  3. HTTP

5   Services

Multiple components will need access to hero data and we don’t want to copy and paste the same code over and over. Instead, we’ll create a single reusable data service and learn to inject it in the components that need it.

Refactoring data access to a separate service keeps the component lean and focused on supporting the view. It also makes it easier to unit test the component with a mock service.

Because data services are invariably asynchronous, we’ll finish the section with a Promise-based version of the data service.

We already can select a hero from a list. Soon we’ll add a dashboard with the top performing heroes and create a separate view for editing hero details. All three views need hero data.

Create the Hero Service

Add the file: hero.service.ts contain the following code. We decorate with @Injectable() function imported from ‘@angular/core’.  This enables the class to use DI (dependency injection) into the consuming classes. See link for more details on DI.

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

@Injectable()
export class HeroService {
}

Add a getHeroes() method stub.

@Injectable()
export class HeroService {
  getHeroes(): void {} // stub
}

We will for now you mock data. Add file: src/app/mock-heroes.ts with the following code

import { Hero } from './hero';
export const HEROES: Hero[] = [
 {id: 11, name: 'Mr. Nice'},
 {id: 12, name: 'Narco'},
 {id: 13, name: 'Bombasto'},
 {id: 14, name: 'Celeritas'},
 {id: 15, name: 'Magneta'},
 {id: 16, name: 'RubberMan'},
 {id: 17, name: 'Dynama'},
 {id: 18, name: 'Dr IQ'},
 {id: 19, name: 'Magma'},
 {id: 20, name: 'Tornado'}
];

Remove in AppComponent we remove the data HERO array and replace the heroes variable with the empty array

heroes: Hero[];

Removed from AppComponent:

const HEROES: Hero[] = [
  { id: 11, name: 'Mr. Nice' },
  { id: 12, name: 'Narco' },
  { id: 13, name: 'Bombasto' },
  { id: 14, name: 'Celeritas' },
  { id: 15, name: 'Magneta' },
  { id: 16, name: 'RubberMan' },
  { id: 17, name: 'Dynama' },
  { id: 18, name: 'Dr IQ' },
  { id: 19, name: 'Magma' },
  { id: 20, name: 'Tornado' }
];

The app is currently broken, we fix this by pointing to the mock data.

Heroservice is updatad by importing Hero and HERO for the mock-heros

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

import { Hero } from './hero';
import { HEROES } from './mock-heroes';

@Injectable()
export class HeroService {
  getHeroes(): Hero[] {
    return HEROES;
  }
}

Now let’s use the service by adding this import to the AppComponent

import { HeroService } from './hero.service';

We will use DI. please don’t use new

heroService = new HeroService(); // don't do this
constructor(private heroService: HeroService) { } //DI yes!

Add the @Component() variable (still in AppComponent)

providers: [HeroService]

This in stub form in AppComponent is now

@Component({
 selector: 'app-root',
 template: `
 ...
 ...
 `,
 styles: [`
  ...
  ...
 `],
 providers: [HeroService]
})

Now use this in a getHeros() method

  getHeroes(): void {
    this.heroes = this.heroService.getHeroes();
  }

NOTE: It is bad practice to call getHeros() in the constructor! Instead use one of the built in life cycle events/hooks ngOnInit(). See here link for more Details on angular events/Lifecycle hooks.

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

export class AppComponent implements OnInit {
  ngOnInit(): void {
      this.getHeroes();
  }
}

This is now working! But wait, in real life the the data is often delivered from a remote server via  a Web API call. This should be call as an asynchronous service. We do this next using “Promise”. We will eventually will be calling the service as an  “observable” stream, but one step at a time. See link for details.

Calling the hero service with promises
Source: Updated getHeroes() in HeroService
getHeroes(): Promise<Hero[]> {
  return Promise.resolve(HEROES);
}
Subscriber: Updated getHeroes() in AppComponent

We have to change our implementation to act on the Promise when it resolves. When the Promise resolves successfully, then we will have heroes to display.

We pass our callback function as an argument to the Promise’s then method:

getHeroes(): void {
  this.heroService.getHeroes().then(heroes => this.heroes = heroes);
}

Summarizing the changes to convert into a promise service

Subscriber:

Source:

Check it out in the browser!


6   Routing

*** New requirements for our Tour of Heroes application: ***  😉

  • Add a Dashboard view.
  • Navigate between the Heroes and Dashboard views.
  • Clicking on a hero in either view navigates to a detail view of the selected hero.
  • Clicking a deep link in an email opens the detail view for a particular hero.

Lets use routing and navigation…

Action Plan
  • Turn AppComponent into an application shell that only handles navigation
  • Relocate the Heroes concerns within the current AppComponent to a separate HeroesComponent
  • Add routing
  • Create a new DashboardComponent
  • Tie the Dashboard into the navigation structure

Our revised app should present a shell with a choice of views (Dashboard and Heroes) and then default to one of them.

Change file: src/app/app.component.ts to scr/app/heroes.component.ts (showing renaming only)

@Component({
 selector: 'my-heroes',
})
export class HeroesComponent implements OnInit {
}

In file index.html, change <app-root></app-root> to <my-hero></my-hero>

<my-heroes>Loading...</my-heroes>

House keeping! change AppModule…

import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';

import { HeroesComponent } from './heroes.component';
import { HeroDetailComponent } from './hero-detail.component';

@NgModule({
 declarations: [
 HeroesComponent,
 HeroDetailComponent
 ],
 imports: [
 BrowserModule,
 FormsModule,
 HttpModule
 ],
 providers: [],
 bootstrap: [HeroesComponent]
})
export class AppModule { }

Create a new src/app/app.component.ts (first draft)

import { Component } from '@angular/core';
@Component({
 selector: 'my-app',
 template: `
 <h1>{{title}}</h1>
 <my-heroes></my-heroes>
 `
})
export class AppComponent {
 title = 'Tour of Heroes';
}

and update AppModule add…

import { AppComponent } from './app.component';
...
...
 declarations: [
 AppComponent,
...
...
bootstrap: [ AppComponent ]

The app with the new HeroesComponent still work and is ready for adding routing.

Add Routing

The hero list needs to be displayed via buttons

Add <base href> in index.html

<head>
  <base href="/">

NOTE this is essential, see link for details

Configure routes

In file: src/app/app.module.ts (heroes route)

import { RouterModule } from '@angular/router';
...
...
RouterModule.forRoot([
  {
    path: 'heroes',
    component: HeroesComponent
   }])
...

Note the new syntax used. See link for more details.

....
@NgModule({
  declarations: [
    AppComponent,
    HeroesComponent,
    HeroDetailComponent
  ],
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule,
    RouterModule.forRoot([
      {
        path: 'heroes',
        component: HeroesComponent
      }])
  ],
...

Router Outlet

The element <router-outlet></router-outlet> is placed in the temple where HeroesComponent meta data will appear when the user navigates to the “/heroes” address.

file: src/app/app.component.ts (template-v2)

template: `
 <h1>{{title}}</h1>
 <a routerLink = "/heroes">Heroes</a>
 <router-outlet></router-outlet>
 `

Explanation: “routerLink = ‘/heroes’ ” is the Angular replacement for “href=’/heroes’ “. It’s a one way binding to the route path. See link for more details.

Summing up src/app/app.component.ts (v2)

import { Component } from '@angular/core';
@Component({
 selector: 'my-app',
 template: `
 <h1>{{title}}</h1>
 <a routerLink="/heroes">Heroes</a>
 <router-outlet></router-outlet>
 `
})
export class AppComponent {
 title = 'Tour of Heroes';
}

Check in Browser:

Add file: src/app/dashboard.component.ts (v1)

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

@Component({
  selector: 'my-dashboard',
  template: '<h3>My Dashboard</h3>'
})
export class DashboardComponent { }

Add dashboard route. In file: src/app/app.module.ts (Dashboard route)

{
 path: 'dashboard',
 component: DashboardComponent
},

And again “house keeping” in file: src/app/app.module.ts (dashboard)

 

...
import { DashboardComponent } from './dashboard.component';
...
declarations: [
 AppComponent,
 DashboardComponent,
 HeroDetailComponent,
 HeroesComponent
],

Redirect to /dashboard (First page)

Part1: Add to file: src/app/app.module.ts (redirect)

{
 path: '',
 redirectTo: '/dashboard',
 pathMatch: 'full'
},

Add navigation to the template

Part2: Add to file: src/app/app.component.ts (template-v3)

...
template: `
 <h1>{{title}}</h1>
 <nav>
 <a routerLink="/dashboard">Dashboard</a>
 <a routerLink="/heroes">Heroes</a>
 </nav>
 <router-outlet></router-outlet>
 `
...

Check in browser (default redirect to localhost:4200/dashboard)

Dashboard Top Heroes

We will now build out the dashboard

Updated file: src/app/dashboard.component.ts (metadata)

@Component({
 moduleId: module.id,
 selector: 'my-dashboard',
 templateUrl: './dashboard.component.html',
})

Set the moduleId property to module.id for module-relative loading of the templateUrl

Add the file:   src/app/dashboard.component.html

<h3>Top Heroes</h3>
<div class="grid grid-pad">
  <div *ngFor="let hero of heroes" class="col-1-4">
     <div class="module hero">
        <h4>{{hero.name}}</h4>
    </div>
  </div>
</div>

Update the file: src/app/dashboard.component.ts (imports)

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

import { Hero } from './hero';
import { HeroService } from './hero.service';

Update the file:   src/app/dashboard.component.ts (class)

export class DashboardComponent implements OnInit {

 heroes: Hero[] = [];

 constructor(private heroService: HeroService) { }

 ngOnInit(): void {
 this.heroService.getHeroes()
 .then(heroes => this.heroes = heroes.slice(1, 5));
 }
}

Check in browser

Navigate to Hero Details

Configure a Route with a Parameter

Update file:  src/app/app.module.ts (hero detail)

{
 path: 'detail/:id',
 component: HeroDetailComponent
},
Revise the HeroDetailComponent

Current file: src/app/hero-detail.component.ts (current)

import { Component, Input } from '@angular/core';
import { Hero } from './hero';
@Component({
 selector: 'my-hero-detail',
 template: `
 <div *ngIf="hero">
 <h2>{{hero.name}} details!</h2>
 <div>
 <label>id: </label>{{hero.id}}
 </div>
 <div>
 <label>name: </label>
 <input [(ngModel)]="hero.name" placeholder="name"/>
 </div>
 </div>
 `
})
export class HeroDetailComponent {
 @Input() hero: Hero;
}

Update the file imports

// Keep the Input import for now, we'll remove it later:
import { Component, Input, OnInit } from '@angular/core';
import { ActivatedRoute, Params } from '@angular/router';
import { Location } from '@angular/common';

import { HeroService } from './hero.service';

src/app/hero-detail.component.ts (constructor)

constructor(
 private heroService: HeroService,
 private route: ActivatedRoute,
 private location: Location
) {}

src/app/hero-detail.component.ts (switchMap import)

import 'rxjs/add/operator/switchMap';

We tell the class that we want to implement the OnInit interface.

export class HeroDetailComponent implements OnInit {

src/app/hero-detail.component.ts (ngOnInit)

ngOnInit(): void {
 this.route.params
 .switchMap((params: Params) => this.heroService.getHero(+params['id']))
 .subscribe(hero => this.hero = hero);
}
Add HeroService.getHero

src/app/hero.service.ts (getHero)

getHero(id: number): Promise<Hero> {
 return this.getHeroes()
 .then(heroes => heroes.find(hero => hero.id === id));
}
Find our way back

src/app/hero-detail.component.ts (goBack)

goBack(): void {
 this.location.back();
}

Wire this method with an event binding to a Back button

<button (click)="goBack()">Back</button>

Now we can move this template out into it’s own file
src/app/hero-detail.component.html

<div *ngIf="hero">
 <h2>{{hero.name}} details!</h2>
 <div>
 <label>id: </label>{{hero.id}}</div>
 <div>
 <label>name: </label>
 <input [(ngModel)]="hero.name" placeholder="name" />
 </div>
 <button (click)="goBack()">Back</button>
</div>

src/app/hero-detail.component.ts (metadata)

@Component({
 moduleId: module.id,
 selector: 'my-hero-detail',
 templateUrl: './hero-detail.component.html',
})

Check in browser

Select a Dashboard Hero

When a user selects a hero in the dashboard, the app should navigate to the HeroDetailComponent to view and edit the selected hero.

Although the dashboard heroes are presented as button-like blocks, they should behave like anchor tags. When hovering over a hero block, the target URL should display in the browser status bar and the user should be able to copy the link or open the hero detail view in a new tab.

To achieve this effect, reopen the dashboard.component.html and replace the repeated <div *ngFor…> tags with <a> tags. The opening <a> tag looks like this:

<a *ngFor="let hero of heroes" [routerLink]="['/detail', hero.id]" class="col-1-4">

Notice the [routerLink] binding.

src/app/app.module.ts (hero detail)

{
 path: 'detail/:id',
 component: HeroDetailComponent
},

Recap: file: hero-detail.component.ts

<h3>Top Heroes</h3>
<div class="grid grid-pad">
 <a *ngFor="let hero of heroes" [routerLink]="['/detail', hero.id]" class="col-1-4">
 <div class="module hero">
 <h4>{{hero.name}}</h4>
 </div>
 </a>
</div>

Check in browser

Refactor routes to a Routing Module

Add file: src/app/app-routing.module.ts

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { DashboardComponent } from './dashboard.component';
import { HeroesComponent } from './heroes.component';
import { HeroDetailComponent } from './hero-detail.component';
const routes: Routes = [
 { path: '', redirectTo: '/dashboard', pathMatch: 'full' },
 { path: 'dashboard', component: DashboardComponent },
 { path: 'detail/:id', component: HeroDetailComponent },
 { path: 'heroes', component: HeroesComponent }
];
@NgModule({
 imports: [ RouterModule.forRoot(routes) ],
 exports: [ RouterModule ]
})
export class AppRoutingModule {}

Noteworthy points, typical of Routing Modules:

  • Pulls the routes into a variable. You might export it in future and it clarifies the Routing Module pattern.
  • Adds RouterModule.forRoot(routes) to imports.
  • Adds RouterModule to exports so that the components in the companion module have access to Router declarables such as RouterLink and RouterOutlet.
  • No declarations! Declarations are the responsibility of the companion module.
  • Adds module providers for guard services if you have them; there are none in this example.

Update AppModule: src/app/app.module.ts

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { AppComponent } from './app.component';
import { DashboardComponent } from './dashboard.component';
import { HeroDetailComponent } from './hero-detail.component';
import { HeroesComponent } from './heroes.component';
import { HeroService } from './hero.service';
import { AppRoutingModule } from './app-routing.module';
@NgModule({
 imports: [
 BrowserModule,
 FormsModule,
 AppRoutingModule
 ],
 declarations: [
 AppComponent,
 DashboardComponent,
 HeroDetailComponent,
 HeroesComponent
 ],
 providers: [ HeroService ],
 bootstrap: [ AppComponent ]
})
export class AppModule { }
Select a Hero in the HeroesComponent

src/app/heroes.component.ts ( current template )

template: `
  <h1>{{title}}</h1>
  <h2>My Heroes</h2>
  <ul class="heroes">
    <li *ngFor="let hero of heroes"
      [class.selected]="hero === selectedHero"
      (click)="onSelect(hero)">
      <span class="badge">{{hero.id}}</span> {{hero.name}}
    </li>
  </ul>
  <my-hero-detail [hero]="selectedHero"></my-hero-detail>
`,

Delete the <h1> at the top (we forgot about it during the AppComponent-to-HeroesComponent conversion).

Delete the last line of the template with the <my-hero-detail> tags.

We are keeping the “master/detail” style but shrinking the detail to a “mini”, read-only version.

Add the mini-detail

Add the following HTML fragment at the bottom of the template where the <my-hero-detail> used to be:

<div *ngIf="selectedHero">
 <h2>
 {{selectedHero.name | uppercase}} is my hero
 </h2>
 <button (click)="gotoDetail()">View Details</button>
</div>

After clicking a hero, the user should see something like this below the hero list:

Format with the uppercase pipe
{{selectedHero.name | uppercase}} is my hero
Move content out of the component file

Let’s migrate the template and the styles to their own files before we make any more changes:

  • Cut-and-paste the template contents into a new heroes.component.html file.
  • Cut-and-paste the styles contents into a new heroes.component.css file.
  • Set the component metadata’s templateUrl and styleUrls properties to refer to both files.
  • Set the moduleId property to module.id so that templateUrl and styleUrls are relative to the component.

File: src/app/heroes.component.ts (revised metadata)

@Component({
  moduleId: module.id,
  selector: 'my-heroes',
  templateUrl: './heroes.component.html',
  styleUrls: [ './heroes.component.css' ]
})
Update the HeroesComponent class.
  • Import the router from the Angular router library
  • Inject the router in the constructor (along with the HeroService)
  • Implement gotoDetail by calling the router.navigate method
gotoDetail(): void {
 this.router.navigate(['/detail', this.selectedHero.id]);
}

For reference, our heroes.component.ts class now looks:

export class HeroesComponent implements OnInit {
 heroes: Hero[];
 selectedHero: Hero;

 constructor(
 private router: Router,
 private heroService: HeroService) { }

 getHeroes(): void {
 this.heroService.getHeroes().then(heroes => this.heroes = heroes);
 }

 ngOnInit(): void {
 this.getHeroes();
 }

 onSelect(hero: Hero): void {
 this.selectedHero = hero;
 }

 gotoDetail(): void {
 this.router.navigate(['/detail', this.selectedHero.id]);
 }
}

Styling the App

Add a dashboard.component.css (src/app/dashboard.component.ts

styleUrls: [ './dashboard.component.css' ]

and (src/app/dashboard.component.css)

[class*='col-'] {
 float: left;
 padding-right: 20px;
 padding-bottom: 20px;
}
[class*='col-']:last-of-type {
 padding-right: 0;
}
a {
 text-decoration: none;
}
*, *:after, *:before {
 -webkit-box-sizing: border-box;
 -moz-box-sizing: border-box;
 box-sizing: border-box;
}
h3 {
 text-align: center; margin-bottom: 0;
}
h4 {
 position: relative;
}
.grid {
 margin: 0;
}
.col-1-4 {
 width: 25%;
}
.module {
 padding: 20px;
 text-align: center;
 color: #eee;
 max-height: 120px;
 min-width: 120px;
 background-color: #607D8B;
 border-radius: 2px;
}
.module:hover {
 background-color: #EEE;
 cursor: pointer;
 color: #607d8b;
}
.grid-pad {
 padding: 10px 0;
}
.grid-pad > [class*='col-']:last-of-type {
 padding-right: 20px;
}
@media (max-width: 600px) {
 .module {
 font-size: 10px;
 max-height: 75px; }
}
@media (max-width: 1024px) {
 .grid {
 margin: 0;
 }
 .module {
 min-width: 60px;
 }
}

now file: src/app/hero.component.css

label {
 display: inline-block;
 width: 3em;
 margin: .5em 0;
 color: #607D8B;
 font-weight: bold;
}
input {
 height: 2em;
 font-size: 1em;
 padding-left: .4em;
}
button {
 margin-top: 20px;
 font-family: Arial;
 background-color: #eee;
 border: none;
 padding: 5px 10px;
 border-radius: 4px;
 cursor: pointer; cursor: hand;
}
button:hover {
 background-color: #cfd8dc;
}
button:disabled {
 background-color: #eee;
 color: #ccc; 
 cursor: auto;
}
Style the Navigation Links

Add a app.component.css file to the app folder with the following content.

file: crs/app/app.component.css

h1 {
 font-size: 1.2em;
 color: #999;
 margin-bottom: 0;
}
h2 {
 font-size: 2em;
 margin-top: 0;
 padding-top: 0;
}
nav a {
 padding: 5px 10px;
 text-decoration: none;
 margin-top: 10px;
 display: inline-block;
 background-color: #eee;
 border-radius: 4px;
}
nav a:visited, a:link {
 color: #607D8B;
}
nav a:hover {
 color: #039be5;
 background-color: #CFD8DC;
}
nav a.active {
 color: #039be5;
}

Check in browser

The routerLinkActive directive

First add moduleId: module.id to the @Component metadata of the AppComponent to enable module-relative file URLs. Then add a styleUrls property that points to this CSS file as follows.

styleUrls: ['./app.component.css'],

Create the file styles.css, if it doesn’t exist already. Ensure that it contains the master styles given here.

Global application styles

When we add styles to a component, we’re keeping everything a component needs — HTML, the CSS, the code — together in one convenient place. It’s pretty easy to package it all up and re-use the component somewhere else.

We can also create styles at the application level outside of any component.

Our designers provided some basic styles to apply to elements across the entire app. These correspond to the full set of master styles that we installed earlier during setup. Here is an excerpt:

styles.css(excerpt)

/* Master Styles */
h1 {
 color: #369;
 font-family: Arial, Helvetica, sans-serif;
 font-size: 250%;
}
h2, h3 {
 color: #444;
 font-family: Arial, Helvetica, sans-serif;
 font-weight: lighter;
}
body {
 margin: 2em;
}
body, input[text], button {
 color: #888;
 font-family: Cambria, Georgia;
}
/* . . . */
/* everywhere else */
* {
 font-family: Arial, Helvetica, sans-serif;
}

If necessary, also edit index.html to refer to this stylesheet.

<link rel="stylesheet" href="styles.css">
Recap
  • We added the Angular Router to navigate among different components.
  • We learned how to create router links to represent navigation menu items.
  • We used router link parameters to navigate to the details of user selected hero.
  • We shared the HeroService among multiple components.
  • We moved HTML and CSS out of the component file and into their own files.
  • We added the uppercase pipe to format data.
  • We refactored routes into a Routing Module that we import.

 

Tutorial Walk Through: Tour of Heroes in VSCode Editor – Part 1

This is a walk through of the Google Quick Start Tutorial. We will be creating an Angular web application using  TypeScript.  The finished project is in the Plunker online editor for reference. We will be building our project in the VSCode Editor.  My VSCode can be found on GitHub.

  1. Introduction
  2. The Hero Editor
  3. Master/Detail
  4. Multiple Components
  5. Services
  6. Routing
  7. HTTP

1   Introduction

The example is designed to cover many of the most used features in Angular.

The project imaginary requirements: are the display of a list of heroes and their details, and provide CRUD (Create, Read, Update and Delete) functionality.

Here are some screen shots of the finished project

Shown below is the inter-page navigation


2   The Hero Editor

Setup the starter project using Angular CLI, name the project “heroes-app”

Change to the “heroes-app” directory and open the project in VSCode

cd heroes-app
code .

The project will look like this:

npm start

In VSCode goto the menu item View>Intergrated Terminal and type in “npm start” to display the project in the browser (http://localhost:4200)

In the AppComponent (src/app/app.component.ts)
Add two properties: title and hero

export class AppComponent {
  title = 'Tour of Heroes';
  hero = 'Windstorm';
}

Display the properties in the component template. The syntax {{   }} is used to bind to the class property and display it. See link for more details.

template: '<h1>{{title}}</h1><h2>{{hero}} details!</h2>'

Now we know it is working let’s create a hero class. We continue in the mean while to use the file src/app/app.component.ts

export class Hero {
  id: number;
  name: string;
}

Update the template using the multi-line temple, between ` ` (back ticks)

template:`
  <h1>{{title}}</h1>
  <h2>{{hero.name}} details!</h2>
  <div><label>id: </label>{{hero.id}}</div>
  <div><label>name: </label>{{hero.name}}</div>
  `

The updated code. We are using the TypeScript syntax to define hero as type Hero. We could have defined hero as of type interface IHero or “any” (no type). See link for more details.

Check in browser:

Update to a web form

template:`
  <h1>{{title}}</h1>
  <h2>{{hero.name}} details!</h2>
  <div><label>id: </label>{{hero.id}}</div>
  <div>
    <label>name: </label>
    <input value="{{hero.name}}" placeholder="name">
  </div>
  `

Check in browser:

We now need to use two-way binding. This requires the import of FormsModule in the AppModule (src/app/app.module.ts). See link for more details.

import { NgModule }      from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule }   from '@angular/forms';
import { AppComponent }  from './app.component';
@NgModule({
  imports: [
    BrowserModule,
    FormsModule
  ],
  declarations: [
    AppComponent
  ],
  bootstrap: [ AppComponent ]
})
export class AppModule { }

The two-way binding is achieved using the syntax banana in a box. See link for more details.

<input [(ngModel)]="hero.name" placeholder="name">

Check: change the text box and see the title update.


3    Master/Detail

Define a list of HEROS of type Hero.  In the AppComponent (src/app/app.component.ts) . Place this towards the top of the file , after the Hero class.

const HEROES: Hero[] = [
  { id: 11, name: 'Mr. Nice' },
  { id: 12, name: 'Narco' },
  { id: 13, name: 'Bombasto' },
  { id: 14, name: 'Celeritas' },
  { id: 15, name: 'Magneta' },
  { id: 16, name: 'RubberMan' },
  { id: 17, name: 'Dynama' },
  { id: 18, name: 'Dr IQ' },
  { id: 19, name: 'Magma' },
  { id: 20, name: 'Tornado' }
];

We define a global variable “hero”. The type is implicitly inferred from the array type.

export class AppComponent {
  title = 'Tour of Heroes';
  heroes = HEROES;
}

Display the heroes

<h2>My Heroes</h2>
<ul class="heroes">
  <li>
    <!-- each hero goes here -->
  </li>
</ul>

Replace <li><!– each hero goes here –></li> using the  *ngFor directive. See link for more details.

<li *ngFor="let hero of heroes">
  <span class="badge">{{hero.id}}</span> {{hero.name}}
</li>

We style the list using  in the component a style property section. This is an array of styles

@Component({ 
   selector:'my=app',
   template:`
     ...
     ...
    `,
  styles: [`{...}, {...}`]
})
,
styles: [`
    .selected {
      background-color: #CFD8DC !important;
      color: white;
    }
    .heroes {
      margin: 0 0 2em 0;
      list-style-type: none;
      padding: 0;
      width: 15em;
    }
    .heroes li {
      cursor: pointer;
      position: relative;
      left: 0;
      background-color: #EEE;
      margin: .5em;
      padding: .3em 0;
      height: 1.6em;
      border-radius: 4px;
    }
    .heroes li.selected:hover {
      background-color: #BBD8DC !important;
      color: white;
    }
    .heroes li:hover {
      color: #607D8B;
      background-color: #DDD;
      left: .1em;
    }
    .heroes .text {
      position: relative;
      top: -3px;
    }
    .heroes .badge {
      display: inline-block;
      font-size: small;
      color: white;
      padding: 0.8em 0.7em 0 0.7em;
      background-color: #607D8B;
      line-height: 1em;
      position: relative;
      left: -1px;
      top: -4px;
      height: 1.8em;
      margin-right: .8em;
      border-radius: 4px 0 0 4px;
    }
  `]

The template is now

 template: `
    <h1>{{title}}</h1>
    <h2>My Heroes</h2>
    <ul class="heroes">
      <li *ngFor="let hero of heroes">
         <span class="badge">{{hero.id}}</span> {{hero.name}}
      </li>
    </ul>
`

Check in browser:

Selecting a Hero

Add click event, in the template

<li *ngFor="let hero of heroes" (click)="onSelect(hero)">
  <span class="badge">{{hero.id}}</span> {{hero.name}}
</li>

Add the click event handler in the class

onSelect(hero: Hero): void {
  this.selectedHero = hero;
}

Add the template to display the selected hero .

<h2>{{selectedHero.name}} details!</h2>
<div><label>id: </label>{{selectedHero.id}}</div>
<div>
    <label>name: </label>
    <input [(ngModel)]="selectedHero.name" placeholder="name"/>
</div>

Add *ngIf to handle when selecedHero is undefined (before first selection). See link for more details.

<div *ngIf="selectedHero">
  <h2>{{selectedHero.name}} details!</h2>
  <div><label>id: </label>{{selectedHero.id}}</div>
  <div>
    <label>name: </label>
    <input [(ngModel)]="selectedHero.name" placeholder="name"/>
  </div>
</div>

 

The current Template code

 template: `
    <h1>{{title}}</h1>
    <h2>My Heroes</h2>
    <ul class="heroes">
      <li *ngFor="let hero of heroes"
        [class.selected]="hero === selectedHero"
        (click)="onSelect(hero)">
        <span class="badge">{{hero.id}}</span> {{hero.name}}
      </li>
    </ul>
    <div *ngIf="selectedHero">
      <h2>{{selectedHero.name}} details!</h2>
      <div><label>id: </label>{{selectedHero.id}}</div>
      <div>
        <label>name: </label>
        <input [(ngModel)]="selectedHero.name" placeholder="name"/>
      </div>
    </div>
  `,

The current class code

export class AppComponent {
  title = 'Tour of Heroes';
  heroes = HEROES;
  selectedHero: Hero;

  onSelect(hero: Hero): void {
    this.selectedHero = hero;
  }
}

Check in browser

Error in browser console with missing *ngIf check.



4   Multiple Components

Skills explained in this section

  • How to create a reusable component
  • How to make a component accept @input
  • How to declare the application directives we need in an Angular module. We list the directives in the NgModule decorator’s declarations array.
  • How to bind a parent component to a child component.

The task is to separate the list and details for the heroes. This cleans the code by applying the Single Responsibility Principle, making it more usable.

 Separating the Hero Detail Component

Add a new file HeroDetailComponent (src/app/hero-detail.component.ts.  See link for more details on naming conventions using kebab-case.

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

@Component({
  selector: 'my-hero-detail',
})
export class HeroDetailComponent {
}

Add the template property (remove this from AppComponent)

template: `
  <div *ngIf="hero">
    <h2>{{hero.name}} details!</h2>
    <div><label>id: </label>{{hero.id}}</div>
    <div>
      <label>name: </label>
      <input [(ngModel)]="hero.name" placeholder="name"/>
    </div>
  </div>
`

Add the HERO property to the component (HeroDetailComponent)

 hero: Hero;

BUT we need to move the class Hero from src/app/app.component.ts to it’s own file src/app/hero.ts

export class Hero {
  id: number;
  name: string;
}

Add the following import statement near the top of both app.component.ts and hero-detail.component.ts.

import { Hero } from './hero';

The HeroDetailComponent must be told what hero to display. Who will tell it? The parent AppComponent! This is done in the template of AppComponent, using property binding syntax as shown below. See link for more details.

<my-hero-detail [hero]="selectedHero"></my-hero-detail>

Explanation: Notice that the hero property is the target of a property binding — it’s in square brackets to the left of the (=). Angular insists that we declare a target property to be an input property. If we don’t, Angular rejects the binding and throws an error.

A the other end there are a few ways to go. We will use @Input(). See link for more details.

  @Input()
 hero: Hero;

It just broke!. We now need so house keeping. In the root module AppModule we need to import and declare the import “HeroDetailComponent”. See link for more details.

import { HeroDetailComponent } from './hero-detail.component';
@NgModule({
  imports: [
    BrowserModule,
    FormsModule
  ],
  declarations: [
    AppComponent,
    HeroDetailComponent
  ],
  bootstrap: [ AppComponent ]
})
export class AppModule { }

The two components won’t coordinate until we bind the selectedHero property of the AppComponent to the HeroDetailComponent element’s hero property like this:

<my-hero-detail [hero]="selectedHero"></my-hero-detail>

The AppComponent’s template should now look like this

template: `
 <h1>{{title}}</h1>
 <h2>My Heroes</h2>
 <ul class="heroes">
 <li *ngFor="let hero of heroes"
 [class.selected]="hero === selectedHero"
 (click)="onSelect(hero)">
 <span class="badge">{{hero.id}}</span> {{hero.name}}
 </li>
 </ul>
 <my-hero-detail [hero]="selectedHero"></my-hero-detail>
`,
Continued in Part 2.