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.

 

 

Leave a Reply

Your email address will not be published. Required fields are marked *