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.
- Introduction
- The Hero Editor
- Master/Detail
- Multiple Components
- Services
- Routing
- 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.