This is a continuation of the walk though of the application.
- Services
- Routing
- 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.