parent
19920924de
commit
82383bc673
@ -32,22 +32,15 @@ template:`
|
||||
// #enddocregion editing-Hero
|
||||
|
||||
// #docregion app-component-1
|
||||
export class AppComponent {
|
||||
public title = 'Tour of Heroes';
|
||||
public hero = 'Windstorm';
|
||||
}
|
||||
export class AppComponent {
|
||||
public title = 'Tour of Heroes';
|
||||
public hero = 'Windstorm';
|
||||
}
|
||||
// #enddocregion app-component-1
|
||||
|
||||
// #docregion hero-interface-1
|
||||
interface Hero {
|
||||
id: number;
|
||||
name: string;
|
||||
}
|
||||
// #enddocregion hero-interface-1
|
||||
|
||||
// #docregion hero-property-1
|
||||
public hero: Hero = {
|
||||
id: 1,
|
||||
name: 'Windstorm'
|
||||
};
|
||||
public hero: Hero = {
|
||||
id: 1,
|
||||
name: 'Windstorm'
|
||||
};
|
||||
// #enddocregion hero-property-1
|
@ -1,10 +1,12 @@
|
||||
// #docregion pt1
|
||||
import {Component} from 'angular2/core';
|
||||
|
||||
// #docregion hero-interface-1
|
||||
interface Hero {
|
||||
id: number;
|
||||
name: string;
|
||||
}
|
||||
// #enddocregion hero-interface-1
|
||||
|
||||
@Component({
|
||||
selector: 'my-app',
|
||||
|
19
public/docs/_examples/toh-1/ts/index.html
Normal file
19
public/docs/_examples/toh-1/ts/index.html
Normal file
@ -0,0 +1,19 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Angular 2 QuickStart</title>
|
||||
<script src="node_modules/angular2/bundles/angular2-polyfills.js"></script>
|
||||
<script src="node_modules/systemjs/dist/system.src.js"></script>
|
||||
<script src="node_modules/rxjs/bundles/Rx.js"></script>
|
||||
<script src="node_modules/angular2/bundles/angular2.dev.js"></script>
|
||||
<script>
|
||||
System.config({packages: {app: {format: 'register', defaultExtension: 'js'}}});
|
||||
System.import('app/boot')
|
||||
.then(null, console.error.bind(console));
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<my-app>Loading...</my-app>
|
||||
</body>
|
||||
</html>
|
8
public/docs/_examples/toh-1/ts/plnkr.json
Normal file
8
public/docs/_examples/toh-1/ts/plnkr.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"description": "Tour of Heroes: Part 1",
|
||||
"files":[
|
||||
"!**/*.d.ts",
|
||||
"!**/*.js"
|
||||
],
|
||||
"tags": ["tutorial", "tour", "heroes"]
|
||||
}
|
@ -56,36 +56,6 @@ public heroes = HEROES;
|
||||
<li *ngFor="#hero of heroes">
|
||||
// #enddocregion heroes-ngfor-1
|
||||
|
||||
// #docregion styles-1
|
||||
styles:[`
|
||||
.heroes {list-style-type: none; margin-left: 1em; padding: 0; width: 10em;}
|
||||
|
||||
.heroes li { cursor: pointer; position: relative; left: 0; transition: all 0.2s ease; }
|
||||
|
||||
.heroes li:hover {color: #369; background-color: #EEE; left: .2em;}
|
||||
|
||||
.heroes .badge {
|
||||
font-size: small;
|
||||
color: white;
|
||||
padding: 0.1em 0.7em;
|
||||
background-color: #369;
|
||||
line-height: 1em;
|
||||
position: relative;
|
||||
left: -1px;
|
||||
top: -1px;
|
||||
}
|
||||
.selected { background-color: #EEE; color: #369; }
|
||||
`]
|
||||
// #enddocregion styles-1
|
||||
|
||||
// #docregion selected-hero-1
|
||||
public selectedHero: Hero;
|
||||
// #enddocregion selected-hero-1
|
||||
|
||||
// #docregion on-select-1
|
||||
onSelect(hero: Hero) { this.selectedHero = hero; }
|
||||
// #enddocregion on-select-1
|
||||
|
||||
// #docregion class-selected-1
|
||||
[class.selected]="hero === selectedHero"
|
||||
// #enddocregion class-selected-1
|
@ -27,13 +27,11 @@ interface Hero {
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
// #docregion styles-1
|
||||
styles:[`
|
||||
.heroes {list-style-type: none; margin-left: 1em; padding: 0; width: 10em;}
|
||||
|
||||
.heroes li { cursor: pointer; position: relative; left: 0; transition: all 0.2s ease; }
|
||||
|
||||
.heroes li:hover {color: #369; background-color: #EEE; left: .2em;}
|
||||
|
||||
.heroes .badge {
|
||||
font-size: small;
|
||||
color: white;
|
||||
@ -45,14 +43,19 @@ interface Hero {
|
||||
top: -1px;
|
||||
}
|
||||
.selected { background-color: #EEE; color: #369; }
|
||||
`]
|
||||
`]
|
||||
// #enddocregion styles-1
|
||||
})
|
||||
export class AppComponent {
|
||||
public title = 'Tour of Heroes';
|
||||
public heroes = HEROES;
|
||||
// #docregion selected-hero-1
|
||||
public selectedHero: Hero;
|
||||
|
||||
// #enddocregion selected-hero-1
|
||||
|
||||
// #docregion on-select-1
|
||||
onSelect(hero: Hero) { this.selectedHero = hero; }
|
||||
// #enddocregion on-select-1
|
||||
}
|
||||
// #enddocregion pt2
|
||||
|
||||
|
19
public/docs/_examples/toh-2/ts/index.html
Normal file
19
public/docs/_examples/toh-2/ts/index.html
Normal file
@ -0,0 +1,19 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Angular 2 QuickStart</title>
|
||||
<script src="node_modules/angular2/bundles/angular2-polyfills.js"></script>
|
||||
<script src="node_modules/systemjs/dist/system.src.js"></script>
|
||||
<script src="node_modules/rxjs/bundles/Rx.js"></script>
|
||||
<script src="node_modules/angular2/bundles/angular2.dev.js"></script>
|
||||
<script>
|
||||
System.config({packages: {app: {format: 'register', defaultExtension: 'js'}}});
|
||||
System.import('app/boot')
|
||||
.then(null, console.error.bind(console));
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<my-app>Loading...</my-app>
|
||||
</body>
|
||||
</html>
|
8
public/docs/_examples/toh-2/ts/plnkr.json
Normal file
8
public/docs/_examples/toh-2/ts/plnkr.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"description": "Tour of Heroes: Part 2",
|
||||
"files":[
|
||||
"!**/*.d.ts",
|
||||
"!**/*.js"
|
||||
],
|
||||
"tags": ["tutorial", "tour", "heroes"]
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
<!-- #docregion component-1 -->
|
||||
<my-hero-detail></my-hero-detail>
|
||||
<!-- #enddocregion component-1 -->
|
||||
|
||||
<!-- #docregion component-2 -->
|
||||
<my-hero-detail [hero]="selectedHero"></my-hero-detail>
|
||||
<!-- #enddocregion component-2 -->
|
||||
|
@ -0,0 +1,41 @@
|
||||
// #docregion hero-detail-component-1
|
||||
@Component({
|
||||
selector: 'my-hero-detail'
|
||||
})
|
||||
export class HeroDetailComponent { }
|
||||
// #enddocregion hero-detail-component-1
|
||||
|
||||
// #docregion hero-detail-component-2
|
||||
@Component({
|
||||
selector: 'my-hero-detail',
|
||||
template: ``
|
||||
})
|
||||
export class HeroDetailComponent { }
|
||||
// #enddocregion hero-detail-component-2
|
||||
|
||||
// #docregion hero-detail-component-3
|
||||
@Component({
|
||||
selector: 'my-hero-detail',
|
||||
template: `
|
||||
<div *ngIf="hero">
|
||||
<h2>{{selected.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 { }
|
||||
// #enddocregion hero-detail-component-3
|
||||
|
||||
// #docregion imports-1
|
||||
import {Component} from 'angular2/core';
|
||||
// #enddocregion imports-1
|
||||
|
||||
// #docregion hero-property
|
||||
export class HeroDetailComponent {
|
||||
public hero: Hero;
|
||||
}
|
||||
// #enddocregion hero-property
|
64
public/docs/_examples/toh-3/ts/app/app.component.ts
Normal file
64
public/docs/_examples/toh-3/ts/app/app.component.ts
Normal file
@ -0,0 +1,64 @@
|
||||
import {Component} from 'angular2/core';
|
||||
// #docregion hero-import
|
||||
import {Hero} from './hero';
|
||||
// #enddocregion hero-import
|
||||
// #docregion hero-detail-import
|
||||
import {HeroDetailComponent} from './hero-detail.component';
|
||||
// #enddocregion hero-detail-import
|
||||
|
||||
@Component({
|
||||
selector: 'my-app',
|
||||
// #docregion hero-detail-template
|
||||
template:`
|
||||
<h1>{{title}}</h1>
|
||||
<h2>My Heroes</h2>
|
||||
<ul class="heroes">
|
||||
<li *ngFor="#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>
|
||||
`,
|
||||
// #enddocregion hero-detail-template
|
||||
styles:[`
|
||||
.heroes {list-style-type: none; margin-left: 1em; padding: 0; width: 10em;}
|
||||
.heroes li { cursor: pointer; position: relative; left: 0; transition: all 0.2s ease; }
|
||||
.heroes li:hover {color: #369; background-color: #EEE; left: .2em;}
|
||||
.heroes .badge {
|
||||
font-size: small;
|
||||
color: white;
|
||||
padding: 0.1em 0.7em;
|
||||
background-color: #369;
|
||||
line-height: 1em;
|
||||
position: relative;
|
||||
left: -1px;
|
||||
top: -1px;
|
||||
}
|
||||
.selected { background-color: #EEE; color: #369; }
|
||||
`],
|
||||
// #docregion declaring
|
||||
directives: [HeroDetailComponent]
|
||||
// #enddocregion declaring
|
||||
})
|
||||
export class AppComponent {
|
||||
public title = 'Tour of Heroes';
|
||||
public heroes = HEROES;
|
||||
public selectedHero: Hero;
|
||||
|
||||
onSelect(hero: Hero) { this.selectedHero = hero; }
|
||||
}
|
||||
|
||||
var 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" }
|
||||
];
|
6
public/docs/_examples/toh-3/ts/app/boot.ts
Normal file
6
public/docs/_examples/toh-3/ts/app/boot.ts
Normal file
@ -0,0 +1,6 @@
|
||||
// #docregion pt1
|
||||
import {bootstrap} from 'angular2/platform/browser';
|
||||
import {AppComponent} from './app.component';
|
||||
|
||||
bootstrap(AppComponent);
|
||||
// #enddocregion pt1
|
@ -1,14 +0,0 @@
|
||||
<!-- #docregion -->
|
||||
template: `
|
||||
<h1>{{title}}</h1>
|
||||
|
||||
<h2>My Heroes</h2>
|
||||
<ul class="heroes">
|
||||
<li *ng-for="#hero of heroes"
|
||||
[ng-class]="getSelectedClass(hero)"
|
||||
(click)="onSelect(hero)">
|
||||
<span class="badge">{{hero.id}}</span> {{hero.name}}</a>
|
||||
</li>
|
||||
</ul>
|
||||
<my-hero-detail [hero]="selectedHero"></my-hero-detail>
|
||||
`,
|
23
public/docs/_examples/toh-3/ts/app/hero-detail.component.ts
Normal file
23
public/docs/_examples/toh-3/ts/app/hero-detail.component.ts
Normal file
@ -0,0 +1,23 @@
|
||||
// #docregion hero-detail-component
|
||||
import {Component} from 'angular2/core';
|
||||
|
||||
// #docregion inputs
|
||||
@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>
|
||||
`,
|
||||
inputs: ['hero']
|
||||
})
|
||||
// #enddocregion inputs
|
||||
export class HeroDetailComponent {
|
||||
public hero: Hero;
|
||||
}
|
||||
// #enddocregion hero-detail-component
|
@ -1,34 +0,0 @@
|
||||
// #docregion template
|
||||
@Component({
|
||||
selector: 'my-hero-detail',
|
||||
template: `
|
||||
<div *ng-if="hero">
|
||||
<h2>{{selected.name}} details!</h2>
|
||||
<div><label>id: </label>{{hero.id}}</div>
|
||||
<div>
|
||||
<label>name: </label>
|
||||
<input [(ng-model)]="hero.name" placeholder="name"/>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
directives: [CORE_DIRECTIVES, FORM_DIRECTIVES]
|
||||
})
|
||||
// #enddocregion template
|
||||
|
||||
// #docregion inputs
|
||||
@Component({
|
||||
selector: 'my-hero-detail',
|
||||
template: `
|
||||
<div *ng-if="hero">
|
||||
<h2>{{hero.name}} details!</h2>
|
||||
<div><label>id: </label>{{hero.id}}</div>
|
||||
<div>
|
||||
<label>name: </label>
|
||||
<input [(ng-model)]="hero.name" placeholder="name"/>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
directives: [CORE_DIRECTIVES, FORM_DIRECTIVES],
|
||||
inputs: ['hero']
|
||||
})
|
||||
// #enddocregion inputs
|
6
public/docs/_examples/toh-3/ts/app/hero.ts
Normal file
6
public/docs/_examples/toh-3/ts/app/hero.ts
Normal file
@ -0,0 +1,6 @@
|
||||
// #docregion hero-interface
|
||||
export interface Hero {
|
||||
id: number;
|
||||
name: string;
|
||||
}
|
||||
// #enddocregion hero-interface
|
19
public/docs/_examples/toh-3/ts/index.html
Normal file
19
public/docs/_examples/toh-3/ts/index.html
Normal file
@ -0,0 +1,19 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Angular 2 QuickStart</title>
|
||||
<script src="node_modules/angular2/bundles/angular2-polyfills.js"></script>
|
||||
<script src="node_modules/systemjs/dist/system.src.js"></script>
|
||||
<script src="node_modules/rxjs/bundles/Rx.js"></script>
|
||||
<script src="node_modules/angular2/bundles/angular2.dev.js"></script>
|
||||
<script>
|
||||
System.config({packages: {app: {format: 'register', defaultExtension: 'js'}}});
|
||||
System.import('app/boot')
|
||||
.then(null, console.error.bind(console));
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<my-app>Loading...</my-app>
|
||||
</body>
|
||||
</html>
|
8
public/docs/_examples/toh-3/ts/plnkr.json
Normal file
8
public/docs/_examples/toh-3/ts/plnkr.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"description": "Tour of Heroes: Part 3",
|
||||
"files":[
|
||||
"!**/*.d.ts",
|
||||
"!**/*.js"
|
||||
],
|
||||
"tags": ["tutorial", "tour", "heroes"]
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
<h3>Top Heroes</h3>
|
||||
<div class="grid grid-pad">
|
||||
<div *ngFor="#hero of heroes | slice:0:4" class="col-1-4" (click)="gotoDetail(hero)">
|
||||
<div *ngFor="#hero of heroes" class="col-1-4" (click)="gotoDetail(hero)">
|
||||
<div class="module hero">
|
||||
<h4>{{hero.name}}</h4>
|
||||
</div>
|
||||
|
@ -14,7 +14,7 @@ export class DashboardComponent implements OnInit {
|
||||
constructor(private _heroService: HeroService, private _router: Router) { }
|
||||
|
||||
ngOnInit() {
|
||||
this._heroService.getHeroes().then(heroes => this.heroes = heroes);
|
||||
this._heroService.getHeroes().then(heroes => this.heroes = heroes.slice(1,5));
|
||||
}
|
||||
|
||||
gotoDetail(hero: Hero) {
|
||||
|
@ -2,16 +2,17 @@
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<base href="/"/>
|
||||
<base href="/">
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<script src="node_modules/angular2/bundles/angular2-polyfills.js"></script>
|
||||
<script src="node_modules/systemjs/dist/system.src.js"></script>
|
||||
<script src="node_modules/rxjs/bundles/Rx.js"></script>
|
||||
<script src="node_modules/angular2/bundles/angular2.dev.js"></script>
|
||||
<script src="node_modules/angular2/bundles/router.dev.js"></script>
|
||||
<script>
|
||||
System.config({
|
||||
packages: {'app': {defaultExtension: 'js'}}
|
||||
});
|
||||
System.import('app/boot').catch(console.log.bind(console));
|
||||
System.config({packages: {app: {format: 'register',defaultExtension: 'js'}}});
|
||||
System.import('app/boot')
|
||||
.then(null, console.error.bind(console));
|
||||
</script>
|
||||
</head>
|
||||
|
||||
|
@ -237,6 +237,7 @@ ol
|
||||
The `#p` local template variable is a different power in each iteration;
|
||||
we display its name using the interpolation syntax with the double-curly-braces.
|
||||
|
||||
<a id="ngModel"></a>
|
||||
.l-main-section
|
||||
:marked
|
||||
## Two-way data binding with ***ngModel**
|
||||
|
@ -11,6 +11,10 @@
|
||||
},
|
||||
"toh-pt2": {
|
||||
"title": "Master/Detail",
|
||||
"intro": "We build a master/detail page with a list of heroes "
|
||||
"intro": "We build a master/detail page with a list of heroes"
|
||||
},
|
||||
"toh-pt3": {
|
||||
"title": "Multiple Components",
|
||||
"intro": "We refactor the master/detail view into separate components"
|
||||
}
|
||||
}
|
@ -5,22 +5,25 @@ include ../../../../_includes/_util-fns
|
||||
|
||||
Every story starts somewhere. Our story starts where the [QuickStart](../quickstart.html) ends.
|
||||
|
||||
[Run the live example for part 1](/resources/live-examples/toh-1/ts/plnkr.html)
|
||||
|
||||
Follow the "QuickStart" steps. They provide the prerequisites, the folder structure,
|
||||
and the core files for our Tour of Heroes.
|
||||
|
||||
Copy the "QuickStart" code to a new folder and rename the folder `angular2-tour-of-heroes`.
|
||||
We should have the following structure:
|
||||
|
||||
code-example(format="").
|
||||
angular2-tour-of-heroes
|
||||
├── node_modules
|
||||
├── app
|
||||
| ├── app.component.ts
|
||||
| └── boot.ts
|
||||
├── index.html
|
||||
├── tsconfig.json
|
||||
└── package.json
|
||||
|
||||
.filetree
|
||||
.file angular2-tour-of-heroes
|
||||
.children
|
||||
.file node_modules
|
||||
.file app
|
||||
.children
|
||||
.file app.component.ts
|
||||
.file boot.ts
|
||||
.file index.html
|
||||
.file package.json
|
||||
.file tsconfig.json
|
||||
:marked
|
||||
## Keep the app transpiling and running
|
||||
We want to start the TypeScript compiler, have it watch for changes, and start our server. We'll do this by typing
|
||||
@ -29,15 +32,9 @@ code-example(format="" language="bash").
|
||||
npm start
|
||||
|
||||
:marked
|
||||
This command starts the server, launches the app in a browser,
|
||||
This command runs the compiler in watch mode, starts the server, launches the app in a browser,
|
||||
and keeps the app running while we continue to build the Tour of Heroes.
|
||||
|
||||
.l-sub-section
|
||||
:marked
|
||||
These two steps watch all project files. They recompile TypeScript files and re-run
|
||||
the app when any file changes.
|
||||
If the watchers fail to detect renamed or new files,
|
||||
stop these commands in each terminal by typing `CTRL+C` and then re-run them.
|
||||
.l-main-section
|
||||
:marked
|
||||
## Show our Hero
|
||||
@ -46,13 +43,13 @@ code-example(format="" language="bash").
|
||||
Let's add two properties to our `AppComponent`, a `title` property for the application name and a `hero` property
|
||||
for a hero named "Windstorm".
|
||||
|
||||
+makeExample('toh-1/ts/app/app.component.snippets.pt1.ts', 'app-component-1', 'app.component.ts (AppComponent class)')(format=".")
|
||||
+makeExample('toh-1/ts-snippets/app.component.snippets.pt1.ts', 'app-component-1', 'app.component.ts (AppComponent class)')(format=".")
|
||||
|
||||
:marked
|
||||
Now we update the template in the `@Component` decoration with data bindings to these new properties.
|
||||
|
||||
+makeExample('toh-1/ts/app/app.component.snippets.pt1.ts', 'show-hero')
|
||||
|
||||
+makeExample('toh-1/ts-snippets/app.component.snippets.pt1.ts', 'show-hero')
|
||||
|
||||
:marked
|
||||
The browser should refresh and display our title and hero.
|
||||
|
||||
@ -70,23 +67,35 @@ code-example(format="" language="bash").
|
||||
Create a `Hero` interface with `id` and `name` properties.
|
||||
Keep this near the top of the `app.component.ts` file for now.
|
||||
|
||||
+makeExample('toh-1/ts/app/app.component.snippets.pt1.ts', 'hero-interface-1', 'app.component.ts (Hero interface)')(format=".")
|
||||
+makeExample('toh-1/ts/app/app.component.ts', 'hero-interface-1', 'app.component.ts (Hero interface)')(format=".")
|
||||
|
||||
.l-sub-section
|
||||
:marked
|
||||
Why an interface and not a class? The net result is that either option will allow us to check the types. The answer here lies in how we intend to use the Hero. We want something to check the types, so either option will suffice. If we wanted to create an instance of a Hero, a class may be more appropriate since we could add logic to a Hero constructor. But our scenario is for type checking, so the interface is adequate. The driving reason however, that leads us to a Hero interface is that the interface when transpiled from TypeScript to JavaScript produces no ES5 code. None at all. While a TypeScript class does generate ES5 code. For these reasons we choose an interface here.
|
||||
#### Interface or Class?
|
||||
Why a `Hero` interface and not a `Hero` class?
|
||||
We want a strongly typed `Hero`. We get strong typing with either option.
|
||||
Our choice depends on how we intend to use the `Hero`.
|
||||
|
||||
If we need a `Hero` that goes beyond simple properties, a `Hero` with logic and behavior,
|
||||
we must define a class.
|
||||
If we only need type checking, the interface is sufficient and lighter weight.
|
||||
|
||||
Lighter weight? Transpiling a class to JavaScript produces code.
|
||||
Transpiling an interface produces — nothing.
|
||||
If the class does nothing (and there is nothing for a `Hero` class to do right now),
|
||||
we prefer an interface.
|
||||
|
||||
:marked
|
||||
Now that we have a `Hero` interface, let’s refactor our component’s `hero` property to be of type `Hero`.
|
||||
Then initialize it with an id of `1` and the name, "Windstorm".
|
||||
|
||||
+makeExample('toh-1/ts/app/app.component.snippets.pt1.ts', 'hero-property-1', 'app.component.ts (Hero property)')(format=".")
|
||||
+makeExample('toh-1/ts-snippets/app.component.snippets.pt1.ts', 'hero-property-1', 'app.component.ts (Hero property)')(format=".")
|
||||
|
||||
:marked
|
||||
Because we changed the hero from a string to an object,
|
||||
we update the binding in the template to refer to the hero’s `name` property.
|
||||
|
||||
+makeExample('toh-1/ts/app/app.component.snippets.pt1.ts', 'show-hero-2')
|
||||
+makeExample('toh-1/ts-snippets/app.component.snippets.pt1.ts', 'show-hero-2')
|
||||
:marked
|
||||
The browser refreshes and continues to display our hero’s name.
|
||||
|
||||
@ -94,7 +103,7 @@ code-example(format="" language="bash").
|
||||
Displaying a name is good, but we want to see all of our hero’s properties.
|
||||
We’ll add a `<div>` for our hero’s `id` property and another `<div>` for our hero’s `name`.
|
||||
|
||||
+makeExample('toh-1/ts/app/app.component.snippets.pt1.ts', 'show-hero-properties')
|
||||
+makeExample('toh-1/ts-snippets/app.component.snippets.pt1.ts', 'show-hero-properties')
|
||||
:marked
|
||||
Uh oh, our template string is getting long. We better take care of that to avoid the risk of making a typo in the template.
|
||||
|
||||
@ -109,7 +118,7 @@ code-example(format="" language="bash").
|
||||
Change the quotes around the template to back-ticks and
|
||||
put the `<h1>`, `<h2>` and `<div>` elements on their own lines.
|
||||
|
||||
+makeExample('toh-1/ts/app/app.component.snippets.pt1.ts', 'multi-line-strings', 'app.component.ts (AppComponent\'s template)')
|
||||
+makeExample('toh-1/ts-snippets/app.component.snippets.pt1.ts', 'multi-line-strings', 'app.component.ts (AppComponent\'s template)')
|
||||
|
||||
.callout.is-important
|
||||
header A back-tick is not a single quote
|
||||
@ -122,63 +131,67 @@ code-example(format="" language="bash").
|
||||
is part of a single template string.
|
||||
|
||||
.l-main-section
|
||||
:marked
|
||||
## Editing Our Hero
|
||||
|
||||
We want to be able to edit the hero name in a textbox.
|
||||
|
||||
Refactor the hero name `<label>` with `<label>` and `<input>` elements as shown below:
|
||||
|
||||
+makeExample('toh-1/ts-snippets/app.component.snippets.pt1.ts', 'editing-Hero', 'app.component.ts (input element)')
|
||||
:marked
|
||||
We see in the browser that the hero’s name does appear in the `<input>` textbox.
|
||||
But something doesn’t feel right.
|
||||
When we change the name, we notice that our change
|
||||
is not reflected in the `<h2>`. We won't get the desired behavior
|
||||
with a one-way binding to `<input>`.
|
||||
|
||||
### Two-Way Binding
|
||||
|
||||
We intend to display the name of the hero in the `<input>`, change it,
|
||||
and see those changes wherever we bind to the hero’s name.
|
||||
In short, we want two-way data binding.
|
||||
|
||||
Let’s update the template to use the **`ngModel`** built-in directive for two-way binding.
|
||||
|
||||
.l-sub-section
|
||||
:marked
|
||||
## Editing Our Hero
|
||||
Learn more about `ngModel` in the
|
||||
[Forms](../guide/forms.html#ngModel) and
|
||||
[Template Syntax](../guide/template-syntax.html#ngModel) chapters.
|
||||
:marked
|
||||
Replace the `<input>` with the following HTML
|
||||
|
||||
We want to be able to edit the hero name in a textbox.
|
||||
code-example(language="html").
|
||||
<input [(ngModel)]="hero.name" placeholder="name">
|
||||
|
||||
Refactor the hero name `<label>` with `<label>` and `<input>` elements as shown below:
|
||||
|
||||
+makeExample('toh-1/ts/app/app.component.snippets.pt1.ts', 'editing-Hero', 'app.component.ts (input element)')
|
||||
:marked
|
||||
We see in the browser that the hero’s name does appear in the `<input>` textbox.
|
||||
But something doesn’t feel right.
|
||||
When we change the name, we notice that our change
|
||||
is not reflected in the `<h2>`. We won't get the desired behavior
|
||||
with a one-way binding to `<input>`.
|
||||
|
||||
### Two-Way Binding
|
||||
|
||||
We intend to display the name of the hero in the `<input>`, change it,
|
||||
and see those changes wherever we bind to the hero’s name.
|
||||
In short, we want two-way data binding.
|
||||
|
||||
Let’s update the template to use the **`ngModel`** built-in directive for two-way binding.
|
||||
|
||||
.l-sub-section
|
||||
:marked
|
||||
Learn more about `ngModel` in the [Template Syntax chapter](../guide/template-syntax.html#ng-model)
|
||||
:marked
|
||||
Replace the `<input>` with the following HTML
|
||||
|
||||
code-example(language="html").
|
||||
<input [(ngModel)]="hero.name" placeholder="name">
|
||||
|
||||
:marked
|
||||
The browser refreshes. We see our hero again. We can edit the hero’s name and
|
||||
see the changes reflected immediately in the `<h2>`.
|
||||
|
||||
.l-main-section
|
||||
:marked
|
||||
## The Road We’ve Travelled
|
||||
Let’s take stock of what we’ve built.
|
||||
|
||||
* Our Tour of Heroes uses the double curly braces of interpolation (a form of one-way data binding)
|
||||
to display the application title and properties of a `Hero` object.
|
||||
* We wrote a multi-line template using ES2015’s template strings to make our template readable.
|
||||
* We can both display and change the hero’s name after adding a two-way data binding to the `<input>` element
|
||||
using the built-in `ngModel` directive.
|
||||
* The `ngModel` directive also propagates changes to every other binding of the `hero.name`.
|
||||
|
||||
Here's the complete `app.component.ts` as it stands now:
|
||||
|
||||
+makeExample('toh-1/ts/app/app.component.ts', 'pt1', 'app.component.ts')
|
||||
:marked
|
||||
The browser refreshes. We see our hero again. We can edit the hero’s name and
|
||||
see the changes reflected immediately in the `<h2>`.
|
||||
|
||||
.l-main-section
|
||||
:marked
|
||||
## The Road Ahead
|
||||
Our Tour of Heroes only displays one hero and we really want to display a list of heroes.
|
||||
We also want to allow the user to select a hero and display their details.
|
||||
We’ll learn more about how to retrieve lists, bind them to the
|
||||
template, and allow a user to select it in the
|
||||
[next tutorial chapter](./toh-pt2.html).
|
||||
:marked
|
||||
## The Road We’ve Travelled
|
||||
Let’s take stock of what we’ve built.
|
||||
|
||||
* Our Tour of Heroes uses the double curly braces of interpolation (a form of one-way data binding)
|
||||
to display the application title and properties of a `Hero` object.
|
||||
* We wrote a multi-line template using ES2015’s template strings to make our template readable.
|
||||
* We can both display and change the hero’s name after adding a two-way data binding to the `<input>` element
|
||||
using the built-in `ngModel` directive.
|
||||
* The `ngModel` directive also propagates changes to every other binding of the `hero.name`.
|
||||
|
||||
[Run the live example for part 1](/resources/live-examples/toh-1/ts/plnkr.html)
|
||||
|
||||
Here's the complete `app.component.ts` as it stands now:
|
||||
|
||||
+makeExample('toh-1/ts/app/app.component.ts', 'pt1', 'app.component.ts')
|
||||
|
||||
.l-main-section
|
||||
:marked
|
||||
## The Road Ahead
|
||||
Our Tour of Heroes only displays one hero and we really want to display a list of heroes.
|
||||
We also want to allow the user to select a hero and display their details.
|
||||
We’ll learn more about how to retrieve lists, bind them to the
|
||||
template, and allow a user to select it in the
|
||||
[next tutorial chapter](./toh-pt2.html).
|
@ -6,6 +6,8 @@ include ../../../../_includes/_util-fns
|
||||
We’ll expand our Tour of Heroes app to display a list of heroes,
|
||||
allow the user to select a hero, and display the hero’s details.
|
||||
|
||||
[Run the live example for part 2](/resources/live-examples/toh-2/ts/plnkr.html)
|
||||
|
||||
Let’s take stock of what we’ll need to display a list of heroes.
|
||||
First, we need a list of heroes. We want to display those heroes in the view’s template,
|
||||
so we’ll need a way to do that.
|
||||
@ -17,16 +19,17 @@ include ../../../../_includes/_util-fns
|
||||
let’s verify we have the following structure after [Part 1](./toh-pt1.html).
|
||||
If not, we’ll need to go back to Part 1 and figure out what we missed.
|
||||
|
||||
code-example(format="").
|
||||
angular2-tour-of-heroes
|
||||
├── node_modules
|
||||
├── app
|
||||
| ├── app.component.ts
|
||||
| └── boot.ts
|
||||
├── index.html
|
||||
├── tsconfig.json
|
||||
└── package.json
|
||||
|
||||
.filetree
|
||||
.file angular2-tour-of-heroes
|
||||
.children
|
||||
.file node_modules
|
||||
.file app
|
||||
.children
|
||||
.file app.component.ts
|
||||
.file boot.ts
|
||||
.file index.html
|
||||
.file package.json
|
||||
.file tsconfig.json
|
||||
:marked
|
||||
### Keep the app transpiling and running
|
||||
We want to start the TypeScript compiler, have it watch for changes, and start our server. We'll do this by typing
|
||||
@ -46,30 +49,30 @@ code-example(format="." language="bash").
|
||||
+makeExample('toh-2/ts/app/app.component.ts', 'hero-array', 'app.component.ts (Hero array)')
|
||||
|
||||
:marked
|
||||
The `HEROES` array is of type `Hero`.
|
||||
We are taking advantage of the `Hero` class we coded previously to create an array of our heroes.
|
||||
We aspire to get this list of heroes from a web service, but let’s take small steps
|
||||
on this road and start by displaying these mock heroes in the browser.
|
||||
The `HEROES` array is of type `Hero`, the interface defined in part one,
|
||||
to create an array of heroes.
|
||||
We aspire to fetch this list of heroes from a web service, but let’s take small steps
|
||||
first and display mock heroes.
|
||||
|
||||
### Exposing heroes
|
||||
Let’s create a public property in `AppComponent` that exposes the heroes for binding.
|
||||
|
||||
+makeExample('toh-2/ts/app/app.component.snippets.pt2.ts', 'hero-array-1', 'app.component.ts (Hero array property)')
|
||||
+makeExample('toh-2/ts-snippets/app.component.snippets.pt2.ts', 'hero-array-1', 'app.component.ts (Hero array property)')
|
||||
|
||||
:marked
|
||||
We did not have to define the `heroes` type. TypeScript can infer it from the `HEROES` array.
|
||||
.l-sub-section
|
||||
:marked
|
||||
We could have defined the heroes list here in this component class.
|
||||
But we know that we’ll get the heroes from a data service.
|
||||
But we know that ultimately we’ll get the heroes from a data service.
|
||||
Because we know where we are heading, it makes sense to separate the hero data
|
||||
from the class implementation from the start.
|
||||
:marked
|
||||
### Displaying heroes in a template
|
||||
Our component has `heroes`. Let’s create an unordered list in our template to display them.
|
||||
We’ll insert the following chunk of HTML below the title and above the hero details.
|
||||
|
||||
+makeExample('toh-2/ts/app/app.component.snippets.pt2.ts', 'heroes-template-1', 'app.component.ts (Heroes template)')
|
||||
|
||||
+makeExample('toh-2/ts-snippets/app.component.snippets.pt2.ts', 'heroes-template-1', 'app.component.ts (Heroes template)')
|
||||
|
||||
:marked
|
||||
Now we have a template that we can fill with our heroes.
|
||||
@ -82,7 +85,7 @@ code-example(format="." language="bash").
|
||||
|
||||
First modify the `<li>` tag by adding the built-in directive `*ngFor`.
|
||||
|
||||
+makeExample('toh-2/ts/app/app.component.snippets.pt2.ts', 'heroes-ngfor-1', 'app.component.ts (ngFor)')
|
||||
+makeExample('toh-2/ts-snippets/app.component.snippets.pt2.ts', 'heroes-ngfor-1', 'app.component.ts (ngFor)')
|
||||
|
||||
.alert.is-critical
|
||||
:marked
|
||||
@ -104,13 +107,14 @@ code-example(format="." language="bash").
|
||||
We can reference this variable within the template to access a hero’s properties.
|
||||
|
||||
Learn more about `ngFor` and local template variables in the
|
||||
[Template Syntax chapter](../guide/template-syntax.html#ng-for).
|
||||
[Displaying Data](../guide/displaying-data.html#ngFor) and
|
||||
[Template Syntax](../guide/template-syntax.html#ngFor) chapters.
|
||||
|
||||
:marked
|
||||
With this background in mind, we now insert some content between the `<li>` tags
|
||||
Now we insert some content between the `<li>` tags
|
||||
that uses the `hero` template variable to display the hero’s properties.
|
||||
|
||||
+makeExample('toh-2/ts/app/app.component.snippets.pt2.ts', 'ng-for', 'app.component.ts (ngFor template)')(format=".")
|
||||
+makeExample('toh-2/ts-snippets/app.component.snippets.pt2.ts', 'ng-for', 'app.component.ts (ngFor template)')(format=".")
|
||||
|
||||
:marked
|
||||
When the browser refreshes, we see a list of heroes!
|
||||
@ -122,7 +126,7 @@ code-example(format="." language="bash").
|
||||
Let’s add some styles to our component by setting the `styles` property on the `@Component` decorator
|
||||
to the following CSS classes:
|
||||
|
||||
+makeExample('toh-2/ts/app/app.component.snippets.pt2.ts', 'styles-1', 'app.component.ts (Styling)')
|
||||
+makeExample('toh-2/ts/app/app.component.ts', 'styles-1', 'app.component.ts (Styling)')
|
||||
|
||||
:marked
|
||||
Notice that we again use the back-tick notation for multi-line strings.
|
||||
@ -132,7 +136,7 @@ code-example(format="." language="bash").
|
||||
|
||||
Our template for displaying the heroes should now look like this:
|
||||
|
||||
+makeExample('toh-2/ts/app/app.component.snippets.pt2.ts', 'heroes-styled', 'app.component.ts (Styled heroes)')
|
||||
+makeExample('toh-2/ts-snippets/app.component.snippets.pt2.ts', 'heroes-styled', 'app.component.ts (Styled heroes)')
|
||||
|
||||
:marked
|
||||
Our styled list of heroes should look like this:
|
||||
@ -154,8 +158,8 @@ figure.image-display
|
||||
### Click event
|
||||
We modify the `<li>` by inserting an Angular event binding to its click event.
|
||||
|
||||
+makeExample('toh-2/ts/app/app.component.snippets.pt2.ts', 'selectedHero-click', 'app.component.ts (Capturing the click event)')
|
||||
|
||||
+makeExample('toh-2/ts-snippets/app.component.snippets.pt2.ts', 'selectedHero-click', 'app.component.ts (Capturing the click event)')
|
||||
|
||||
:marked
|
||||
Focus on the event binding
|
||||
code-example(format="." language="bash").
|
||||
@ -167,7 +171,9 @@ figure.image-display
|
||||
That’s the same `hero` variable we defined previously in the `ngFor`.
|
||||
.l-sub-section
|
||||
:marked
|
||||
Learn more about Event Binding in the [Templating Syntax chapter](../guide/template-syntax.html#event-binding).
|
||||
Learn more about Event Binding in the
|
||||
[User Input](../guide/user-input.html) and
|
||||
[Templating Syntax](../guide/template-syntax.html#event-binding) chapters.
|
||||
:marked
|
||||
### Add the click handler
|
||||
Our event binding refers to an `onSelect` method that doesn’t exist yet.
|
||||
@ -181,21 +187,21 @@ figure.image-display
|
||||
We no longer need the static `hero` property of the `AppComponent`.
|
||||
**Replace** it with this simple `selectedHero` property:
|
||||
|
||||
+makeExample('toh-2/ts/app/app.component.snippets.pt2.ts', 'selected-hero-1', 'app.component.ts (selectedHero)')
|
||||
+makeExample('toh-2/ts/app/app.component.ts', 'selected-hero-1', 'app.component.ts (selectedHero)')
|
||||
|
||||
:marked
|
||||
We’ve decided that none of the heroes should be selected before the user picks a hero so
|
||||
we won’t initialize the `selectedHero` as we were doing with `hero`.
|
||||
|
||||
Now **add an `onSelect` method** that sets the `selectedHero` property to the `hero` the user clicked.
|
||||
+makeExample('toh-2/ts/app/app.component.snippets.pt2.ts', 'on-select-1', 'app.component.ts (onSelect)')
|
||||
+makeExample('toh-2/ts/app/app.component.ts', 'on-select-1', 'app.component.ts (onSelect)')
|
||||
|
||||
:marked
|
||||
We will be showing the selected hero's details in our template.
|
||||
At the moment, it is still referring to the old `hero` property.
|
||||
Let’s fix the template to bind to the new `selectedHero` property.
|
||||
|
||||
+makeExample('toh-2/ts/app/app.component.snippets.pt2.ts', 'selectedHero-details', 'app.compontent.ts (Binding to the selectedHero\'s name)')
|
||||
+makeExample('toh-2/ts-snippets/app.component.snippets.pt2.ts', 'selectedHero-details', 'app.component.ts (Binding to the selectedHero\'s name)')
|
||||
:marked
|
||||
### Hide the empty detail with ngIf
|
||||
|
||||
@ -215,8 +221,8 @@ figure.image-display
|
||||
We wrap the HTML hero detail content of our template with a `<div>`.
|
||||
Then we add the `ngIf` built-in directive and set it to the `selectedHero` property of our component.
|
||||
|
||||
+makeExample('toh-2/ts/app/app.component.snippets.pt2.ts', 'ng-if', 'app.component.ts (ngIf)')
|
||||
|
||||
+makeExample('toh-2/ts-snippets/app.component.snippets.pt2.ts', 'ng-if', 'app.component.ts (ngIf)')
|
||||
|
||||
.alert.is-critical
|
||||
:marked
|
||||
Remember that the leading asterisk (`*`) in front of `ngIf` is
|
||||
@ -234,7 +240,8 @@ figure.image-display
|
||||
In other words, they give structure to the way Angular displays content in the DOM.
|
||||
|
||||
Learn more about `ngIf`, `ngFor` and other structural directives in the
|
||||
[Template Syntax chapter](../guide/template-syntax.html#directives).
|
||||
[Structural Directives](../guide/structural-directives.html) and
|
||||
[Template Syntax](../guide/template-syntax.html#directives) chapters.
|
||||
|
||||
:marked
|
||||
The browser refreshes and we see the list of heroes but not the selected hero detail.
|
||||
@ -256,12 +263,12 @@ figure.image-display
|
||||
|
||||
The key is the name of the CSS class (`selected`). The value is `true` if the two heroes match and `false` otherwise.
|
||||
We’re saying “*apply the `selected` class if the heroes match, remove it if they don’t*”.
|
||||
+makeExample('toh-2/ts/app/app.component.snippets.pt2.ts', 'class-selected-1', 'app.component.ts (Setting the CSS class)')(format=".")
|
||||
+makeExample('toh-2/ts-snippets/app.component.snippets.pt2.ts', 'class-selected-1', 'app.component.ts (Setting the CSS class)')(format=".")
|
||||
:marked
|
||||
Notice in the template that the `class.selected` is surrounded in square brackets (`[]`).
|
||||
This is the syntax for a Property Binding, a binding in which data flows one way
|
||||
from the data source (the expression `hero === selectedHero`) to a property of `class`.
|
||||
+makeExample('toh-2/ts/app/app.component.snippets.pt2.ts', 'class-selected-2', 'app.component.ts (Styling each hero)')(format=".")
|
||||
+makeExample('toh-2/ts-snippets/app.component.snippets.pt2.ts', 'class-selected-2', 'app.component.ts (Styling each hero)')(format=".")
|
||||
|
||||
.l-sub-section
|
||||
:marked
|
||||
@ -283,15 +290,18 @@ figure.image-display
|
||||
+makeExample('toh-2/ts/app/app.component.ts', 'pt2', 'app.component.ts')
|
||||
|
||||
.l-main-section
|
||||
:marked
|
||||
## The Road We’ve Travelled
|
||||
Here’s what we achieved in this chapter:
|
||||
:marked
|
||||
## The Road We’ve Travelled
|
||||
Here’s what we achieved in this chapter:
|
||||
|
||||
* Our Tour of Heroes now displays a list of selectable heroes
|
||||
* We added the ability to select a hero and show the hero’s details
|
||||
* We learned how to use the built-in directives `ngIf` and `ngFor` in a component’s template
|
||||
* Our Tour of Heroes now displays a list of selectable heroes
|
||||
* We added the ability to select a hero and show the hero’s details
|
||||
* We learned how to use the built-in directives `ngIf` and `ngFor` in a component’s template
|
||||
|
||||
### The Road Ahead
|
||||
Our Tour of Heroes has grown, but it’s far from complete.
|
||||
We want to get data from an asynchronous source using promises, use shared services, and create reusable components.
|
||||
We’ll learn more about these tasks in the coming tutorial chapters.
|
||||
[Run the live example for part 2](/resources/live-examples/toh-2/ts/plnkr.html)
|
||||
|
||||
### The Road Ahead
|
||||
Our Tour of Heroes has grown, but it’s far from complete.
|
||||
We can't put the entire app into a single component.
|
||||
We need to break it up into sub-components and teach them to work together
|
||||
as we learn in the [next chapter](toh-pt3.html).
|
||||
|
@ -1,363 +1,175 @@
|
||||
include ../../../../_includes/_util-fns
|
||||
|
||||
:marked
|
||||
# Shared Components and Services
|
||||
Our app is growing.
|
||||
Use cases are flowing in for reusing components, passing data to components, sharing the hero data, and preparing to retrieve the data asynchronously via a promise.
|
||||
|
||||
:marked
|
||||
Our app is growing.
|
||||
Use cases are flowing in for reusing components, passing data to components, and creating more reusable assets. Let's separate the heroes list from the hero details and make the details component reusable.
|
||||
|
||||
[Run the live example for part 3](/resources/live-examples/toh-3/ts/plnkr.html)
|
||||
|
||||
.l-main-section
|
||||
:marked
|
||||
## Where We Left Off
|
||||
Before we continue with our Tour of Heroes, let’s verify we have the following structure. If not, we’ll need to go back and follow the previous chapters.
|
||||
|
||||
.filetree
|
||||
.file angular2-tour-of-heroes
|
||||
.children
|
||||
.file node_modules
|
||||
.file app
|
||||
.children
|
||||
.file app.component.ts
|
||||
.file boot.ts
|
||||
.file index.html
|
||||
.file package.json
|
||||
.file tsconfig.json
|
||||
:marked
|
||||
### Keep the app transpiling and running
|
||||
We want to start the TypeScript compiler, have it watch for changes, and start our server. We'll do this by typing
|
||||
|
||||
code-example(format="." language="bash").
|
||||
npm run go
|
||||
|
||||
:marked
|
||||
This will keep the application running while we continue to build the Tour of Heroes.
|
||||
|
||||
## Making a Hero Detail Component
|
||||
Our heroes list and our hero details are all in the same component. What if we want to reuse the hero details somewhere else in our app? This would be difficult since it is intermixed with the heroes list. Let’s make this easier and separate the hero details into its own component to make this more reusable.
|
||||
|
||||
### Separating the Hero Detail Component
|
||||
We’ll need a new file and a new component to host our hero details. Let’s create a new file named `hero-detail.component.ts` in the `app` folder, and create a component named `HeroDetailComponent`.
|
||||
|
||||
+makeExample('toh-3/ts-snippets/hero-detail.component.pt3.ts', 'hero-detail-component-1', 'hero-detail.component.ts (HeroDetailComponent)')
|
||||
|
||||
:marked
|
||||
We want to use `HeroDetailComponent` from our original `AppComponent`. We’ll need the selector name when we refer to it in `AppComponent`’s template. We export the `HeroDetailComponent` here so we can later import it into our `AppComponent`, which we’ll do after we finish creating our `HeroDetailComponent`.
|
||||
|
||||
We anticipate our template will contain multiple lines. So let’s initialize the `template` property to an empty string between back-ticks.
|
||||
|
||||
+makeExample('toh-3/ts-snippets/hero-detail.component.pt3.ts', 'hero-detail-component-2', 'hero-detail.component.ts (Empty template)')
|
||||
|
||||
:marked
|
||||
Remember, we want to refer to our `HeroDetailComponent` in the `AppComponent`. This is why we export the `HeroDetailComponent` class.
|
||||
|
||||
#### Hero Detail Template
|
||||
Our heroes and hero details are combined in one template in `AppComponent` so we need to separate them. Let’s move the appropriate template content from `AppComponent` and paste it in the template property of `HeroDetailComponent`.
|
||||
|
||||
Let’s also change the name of the property in the template from `selectedHero` to `hero`, as it is more appropriate for a reusable component.
|
||||
|
||||
+makeExample('toh-3/ts-snippets/hero-detail.component.pt3.ts', 'hero-detail-component-3', 'hero-detail.component.ts (Detail template)')
|
||||
|
||||
:marked
|
||||
Now our hero detail template exists only in our `HeroDetailComponent`.
|
||||
|
||||
#### Importing
|
||||
Now that we have the foundation for the component, we need to make sure that we import everything we are using in the component. Let’s add the following import statement to the top of our `hero-detail.component.ts` file to get the exports from Angular that we are using.
|
||||
|
||||
+makeExample('toh-3/ts-snippets/hero-detail.component.pt3.ts', 'imports-1', 'hero-detail.component.ts (Importing Component)')
|
||||
|
||||
:marked
|
||||
#### Declaring our Hero
|
||||
Our `HeroDetailComponent`’s template refers to a hero, so let’s add a property on the component to hold the hero.
|
||||
|
||||
+makeExample('toh-3/ts-snippets/hero-detail.component.pt3.ts', 'hero-property', 'hero-detail.component.ts (Hero property)')
|
||||
|
||||
:marked
|
||||
Uh oh. We declare the `hero` property as being of type `Hero` but our `Hero` interface is over in the `app.component.ts` file. We now have two components, each in their own file, that need to reference the `Hero` interface. Let’s solve this problem by removing the `Hero` interface from `app.component.ts` and moving it to its own file named `hero.ts`.
|
||||
|
||||
+makeExample('toh-3/ts/app/hero.ts', 'hero-interface', 'hero.ts (Exported Hero interface)')
|
||||
|
||||
:marked
|
||||
We export the `Hero` interface from `hero.ts` because we will need to import it in both of our component files. Let’s add the following import statement to the top of both `app.component.ts` and `hero.-detail.component.ts`.
|
||||
|
||||
+makeExample('toh-3/ts/app/app.component.ts', 'hero-import', 'hero-detail.component.ts and app.component.ts (Import the Hero interface)')
|
||||
|
||||
:marked
|
||||
Now we can also use the `Hero` interface from other files by importing it.
|
||||
|
||||
#### Defining the Input for HeroDetailComponent
|
||||
Our `HeroDetailComponent` needs to be told what hero to use. We have a `hero` property, but we need a way for the `AppComponent` to tell the `HeroDetailComponent` the hero it should use.
|
||||
|
||||
Let’s declare the inputs for our component in the `@Component` decorator’s `inputs` property. We will set the input to the `hero` property so it matches the `hero` property on the `HeroDetailComponent`.
|
||||
|
||||
+makeExample('toh-3/ts/app/hero-detail.component.ts', 'inputs', 'hero-detail.component.ts (Component input)')
|
||||
|
||||
:marked
|
||||
Now our `AppComponent`, or any component that refers to `HeroDetailComponent`, can tell the `HeroDetailComponent` which hero to use.
|
||||
|
||||
### Making AppComponent Refer to the HeroDetailComponent
|
||||
Our `HeroDetailComponent` is ready, but we need to go back to the `AppComponent` and clean up some loose ends.
|
||||
|
||||
First we need to tell our `AppComponent` about our new component. Let’s add an import statement so we can refer to the `HeroDetailComponent`.
|
||||
|
||||
+makeExample('toh-3/ts/app/app.component.ts', 'hero-detail-import', 'app.component.ts (Importing HeroDetailComponent)')
|
||||
|
||||
:marked
|
||||
Let’s find the location of the template content we removed from `AppComponent` and refer to our new component.
|
||||
|
||||
+makeExample('toh-3/ts-snippets/app.component.pt3.html', 'component-1', 'app.component.ts (Using HeroDetailComponent)')
|
||||
|
||||
:marked
|
||||
This would be good enough if the component did not have any inputs. But we do, so we want to pass the selected hero to the `hero` input of the `HeroDetailComponent`, as shown below:
|
||||
+makeExample('toh-3/ts-snippets/app.component.pt3.html', 'component-2', 'app.component.ts (Passing the hero to HeroDetailComponent)')
|
||||
|
||||
:marked
|
||||
### Declaring HeroDetailComponent
|
||||
We've imported `HeroDetailComponent`, we've used it in the template, but we haven't told our `AppComponent` that we intend to use it in the template. We must declare our intention to use `HeroDetailComponent` in `AppComponent`.
|
||||
|
||||
Let's add the `directives` property to our `@Component` decorator and set it to an array which contains the `HeroDetailComponent`. We'll do this after our `template` and `styles` properties in the `@Component` decorator.
|
||||
+makeExample('toh-3/ts/app/app.component.ts', 'declaring', 'app.component.ts (Declaring HeroDetailComponent)')
|
||||
|
||||
:marked
|
||||
Our `AppComponent`’s template should now look like this
|
||||
|
||||
+makeExample('toh-3/ts/app/app.component.ts', 'hero-detail-template', 'app.component.ts (Template)')
|
||||
|
||||
.l-sub-section
|
||||
:marked
|
||||
## Reviewing Where We Left Off
|
||||
Before we continue with our Tour of Heroes, let’s verify we have the following structure. If not, we’ll need to go back and follow the previous chapters.
|
||||
|
||||
code-example.
|
||||
angular2-tour-of-heroes
|
||||
├── node_modules
|
||||
├── app
|
||||
| ├── app.component.ts
|
||||
| └── boot.ts
|
||||
├── index.html
|
||||
├── tsconfig.json
|
||||
└── package.json
|
||||
Naming conventions can be useful.
|
||||
We want to identify which files are components. Our `AppComponent` is named `app.component.ts` and our `HeroDetailComponent` is `hero-detail.component.ts`. We follow this consistent pattern which makes it easy to know what is in each file, by following a naming convention where we identify which files contain a component.
|
||||
|
||||
:marked
|
||||
### Keep the app transpiling and running
|
||||
We want to start the TypeScript compiler, have it watch for changes, and start our server. We'll do this by typing
|
||||
|
||||
code-example(format="." language="bash").
|
||||
npm start
|
||||
|
||||
:marked
|
||||
This will keep the application running while we continue to build the Tour of Heroes.
|
||||
|
||||
## Making a Hero Detail Component
|
||||
Our heroes list and our hero details are all in the same component. What if we want to reuse the hero details somewhere else in our app? This would be difficult since it is intermixed with the heroes list. Let’s make this easier and separate the hero details into its own component to make this more reusable.
|
||||
|
||||
### Separating the Hero Detail Component
|
||||
We’ll need a new file and a new component to host our hero details. Let’s create a new file named `hero-detail.component.ts` with a component named `HeroDetailComponent`.
|
||||
```
|
||||
@Component({
|
||||
selector: 'my-hero-detail'
|
||||
})
|
||||
export class HeroDetailComponent { }
|
||||
```
|
||||
We want to use `HeroDetailComponent` from our original `AppComponent`. We’ll need the selector name when we refer to it in `AppComponent`’s template. We export the `HeroDetailComponent` here so we can later import it into our `AppComponent`, which we’ll do after we finish creating our `HeroDetailComponent`.
|
||||
|
||||
We anticipate our template will contain multiple lines. So let’s initialize the `template` property to an empty string between back-ticks.
|
||||
```
|
||||
@Component({
|
||||
selector: 'my-hero-detail',
|
||||
template: ``
|
||||
})
|
||||
export class HeroDetailComponent { }
|
||||
```
|
||||
Remember, we want to refer to our `HeroDetailComponent` in the `AppComponent`. This is why we export the `HeroDetailComponent` class.
|
||||
|
||||
#### Hero Detail Template
|
||||
Our heroes and hero details are combined in one template in `AppComponent` so we need to separate them. Let’s move the appropriate template content from `AppComponent` and paste it in the template property of `HeroDetailComponent`.
|
||||
|
||||
Let’s also change the name of the property in the template from `selectedHero` to `hero`, as it is more appropriate for a reusable component.
|
||||
|
||||
+makeExample('toh-3/ts/app/hero-detail.snippets.pt3.ts', 'template')
|
||||
|
||||
:marked
|
||||
Our `HeroDetailComponent` uses `ng-model` (which is in the `FORM_DIRECTIVES` array) and `ng-if` (which is in the `CORE_DIRECTIVES` array). We have to tell our component about these directives, so we declare them in the `directives` property of the `@Component` decorator.
|
||||
|
||||
Now our hero detail template exists only in our `HeroDetailComponent`.
|
||||
|
||||
#### Importing
|
||||
Now that we have the foundation for the component, we need to make sure that we import everything we are using in the component. Let’s add the following import statement to the top of our `hero-detail.component.ts` file to get the exports from Angular that we are using.
|
||||
```
|
||||
import {Component, CORE_DIRECTIVES, FORM_DIRECTIVES} from 'angular2/angular2';
|
||||
```
|
||||
#### Declaring our Hero
|
||||
Our `HeroDetailComponent`’s template refers to a hero, so let’s add a property on the component to hold the hero.
|
||||
```
|
||||
export class HeroDetailComponent {
|
||||
public hero: Hero;
|
||||
}
|
||||
```
|
||||
Uh oh. We declare the `hero` property as being of type `Hero` but our `Hero` class is over in the `app.ts` file. We now have two components, each in their own file, that need to reference the `Hero` class. Let’s solve this problem by removing the `Hero` class from `app.ts` and moving it to its own file named `hero.ts`.
|
||||
```
|
||||
export class Hero {
|
||||
id: number;
|
||||
name: string;
|
||||
}
|
||||
```
|
||||
We export the `Hero` class from `hero.ts` because we will need to import it in both of our component files. Let’s add the following import statement to the top of both `app.ts` and `hero.-detail.component.ts`.
|
||||
```
|
||||
import {Hero} from './hero';
|
||||
```
|
||||
Now we can also use the `Hero` class from other files by importing it.
|
||||
|
||||
#### Defining the Input for HeroDetailComponent
|
||||
Our `HeroDetailComponent` needs to be told what hero to use. We have a `hero` property, but we need a way for the `AppComponent` to tell the `HeroDetailComponent` the hero it should use.
|
||||
|
||||
Let’s declare the inputs for our component in the `@Component` decorator’s `inputs` property. We will set the input to the `hero` property so it matches the `hero` property on the `HeroDetailComponent`.
|
||||
|
||||
+makeExample('toh-3/ts/app/hero-detail.snippets.pt3.ts', 'inputs')
|
||||
|
||||
:marked
|
||||
Now our `AppComponent`, or any component that refers to `HeroDetailComponent`, can tell the `HeroDetailComponent` which hero to use.
|
||||
|
||||
### Making AppComponent Refer to the HeroDetailComponent
|
||||
Our `HeroDetailComponent` is ready, but we need to go back to the `AppComponent` and clean up some loose ends.
|
||||
|
||||
First we need to tell our `AppComponent` about our new component. Let’s add an import statement so we can refer to the `HeroDetailComponent`.
|
||||
```
|
||||
import {HeroDetailComponent} from './hero-detail.component';
|
||||
```
|
||||
Let’s find the location of the template content we removed from `AppComponent` and refer to our new component.
|
||||
```
|
||||
<my-hero-detail></my-hero-detail>
|
||||
```
|
||||
This would be good enough if the component did not have any inputs. But we do, so we want to pass the selected hero to the `hero` input of the `HeroDetailComponent`, as shown below:
|
||||
```
|
||||
<my-hero-detail [hero]="selectedHero"></my-hero-detail>
|
||||
```
|
||||
Our `AppComponent`’s template should now look like this
|
||||
|
||||
+makeExample('toh-3/ts/app/hero-detail.component.pt3.html')
|
||||
|
||||
:marked
|
||||
#### Naming Convention
|
||||
We want to identify which files are components. Our `AppComponent` is named `app.ts` while our `HeroDetailComponent` is `hero-detail.component.ts`. That’s not very consistent and we can make it easier to know what is in each file by following a naming convention where we identify which files contain a component.
|
||||
|
||||
Let’s rename `app.ts` to `app.component.ts`.
|
||||
|
||||
<!-- TODO
|
||||
.l-sub-section
|
||||
:marked
|
||||
Learn more about naming conventions in the chapter [Naming Conventions]
|
||||
:marked
|
||||
-->
|
||||
|
||||
Remember that our application kicks off by entering our starting point, which is `app.component.ts`. We just renamed this file, but we also have to change every place we import this module. Our entry point is in our `index.html` file. Let’s change the following statement to import `app.component`.
|
||||
```
|
||||
System.import('app/app.component');
|
||||
```
|
||||
|
||||
### Checking Our Work
|
||||
When we view our app in the browser we see the list of heroes. When we select a hero we can see the selected hero’s details. If we want to show hero details somewhere else in our app we can use the `HeroDetailComponent` and pass in a hero.
|
||||
|
||||
We’ve created our first reusable component!
|
||||
|
||||
<!-- TODO
|
||||
.l-sub-section
|
||||
:marked
|
||||
Learn more about reusable components in the chapter [Reusable Components]
|
||||
:marked -->
|
||||
|
||||
## Creating a Hero Service
|
||||
Our stakeholders have shared their larger vision for our app. They tell us they want to show the heroes in various ways in different pages. We have a way to select a hero from a list, but we will also need a dashboard with the top heroes and a separate view for editing hero details.
|
||||
|
||||
All of these views need hero data. Our `AppComponent` defines and uses a list of heroes, but it is not ideal to create our AppComponent when just the heroes data elsewhere. Fortunately we can create a shared service that will provide the heroes.
|
||||
|
||||
### Creating the HeroService
|
||||
Were going to create a service that can be used by any component that wants hero data. Let’s start by creating a file and naming it `hero.service.ts`. We name the class `HeroService` and export it, so our components can import it.
|
||||
```
|
||||
export class HeroService { }
|
||||
```
|
||||
#### The getHeroes Method
|
||||
We create a method named `getHeroes` in our `HeroService`. It will return an array of `Hero` objects, so let’s import the `Hero` class and define our method.
|
||||
```
|
||||
import { Hero } from './hero';
|
||||
|
||||
export class HeroService {
|
||||
getHeroes() : Hero[] {
|
||||
}
|
||||
}
|
||||
```
|
||||
#### Mocking the Heroes
|
||||
Our `HeroService` shouldn’t be defining the hero data. Instead, the service should handle retrieving the data from another source. That source could be a mock data, a web service, or even local storage. We will design our `HeroService` to get the data from any of these sources and not affect the calling component. This will make it more reusable and more testable.
|
||||
|
||||
Once.
|
||||
|
||||
We have a list of heroes in `AppComponent`. We will move it to a new file named `mock-heroes.ts` and export the list.
|
||||
|
||||
+makeExample('toh-3/ts/app/mock-heroes.ts', 'mocking-heroes')
|
||||
|
||||
:marked
|
||||
### Returning the Mocked Heroes
|
||||
Our `HeroService` needs to get the list of heroes, so let’s import the mocked heroes module. Then we’ll return the HEROES array.
|
||||
```
|
||||
import {Hero} from './hero';
|
||||
import {HEROES} from './mock-heroes';
|
||||
|
||||
export class HeroService {
|
||||
getHeroes() {
|
||||
return HEROES;
|
||||
}
|
||||
}
|
||||
```
|
||||
TypeScript can implicitly determine that that return type is `Hero[]` since the return value is of that same type. This allows use to remove the explicit return type from the `getHeroes` method.
|
||||
|
||||
### Injecting the Hero Service
|
||||
We’ve set ourselves up so we can use the `HeroService` from other components. Let’s import the `HeroService` in our `AppComponent`.
|
||||
```
|
||||
import {HeroService} from './hero.service';
|
||||
```
|
||||
Importing the service allows us to reference it, but we need to make sure the `HeroService` dependency is instantiated when our component needs it. We inject the `HeroService` into our `AppComponent`’s constructor.
|
||||
```
|
||||
constructor(private _heroService: HeroService) { }
|
||||
```
|
||||
We just injected our dependency into the component, thus we performed Dependency Injection.
|
||||
|
||||
.l-sub-section
|
||||
:marked
|
||||
Learn more about Dependency Injection in chapter [Dependency Injection](dependency-injection.html)
|
||||
:marked
|
||||
### Checking Our Work
|
||||
When we view our app in the browser we see the list of heroes. When we select a hero we can see the selected hero’s details. If we want to show hero details somewhere else in our app we can use the `HeroDetailComponent` and pass in a hero.
|
||||
|
||||
:marked
|
||||
We made our instance of the injected `HeroService` be a private property on our `AppComponent` class. As a convention we prefixed the private property with an underscore.
|
||||
|
||||
Since we are not defining the heroes in the `AppComponent` any longer, let’s refactor the `hero` property declaration to be an uninitialized array of `Hero`.
|
||||
```
|
||||
public heroes: Hero[];
|
||||
```
|
||||
|
||||
### The OnInit Lifecycle Hook
|
||||
When our `AppComponent` is created we want it to get the list of heroes. We need to know when the component is initialized and activated, so we’ll use the `OnInit` lifecycle event to tell us this.
|
||||
|
||||
Let’s import Angular’s `OnInit` interface, implement it on our `AppComponent` and define its required `onInit` method. First we add the `OnInit` interface to the import statement.
|
||||
```
|
||||
import {bootstrap, Component, CORE_DIRECTIVES, FORM_DIRECTIVES, OnInit} from 'angular2/angular2';
|
||||
```
|
||||
Now we implement the interface.
|
||||
```
|
||||
class AppComponent implements OnInit {
|
||||
```
|
||||
Then we define the `onInit` method and get our heroes from our `HeroService`.
|
||||
```
|
||||
onInit() {
|
||||
this.heroes = this._heroService.getHeroes();
|
||||
}
|
||||
```
|
||||
<!-- TODO
|
||||
.l-sub-section
|
||||
:marked
|
||||
Learn more about lifecycle hooks in chapter [Lifecycle Hooks]
|
||||
:marked
|
||||
-->
|
||||
We’ve created our first reusable component!
|
||||
|
||||
Why not use the constructor to get the heroes? When we test our application we want an opportunity to create the class without any state being set. This will make it easier to test and reduce external factors, such as calling a service in the constructor. Therefore the constructor is best suited to help us inject dependencies and initialize variables. We need a place to get our heroes right after our class is constructed but before the view is rendered. The OnInit lifecycle hook gives us this opportunity.
|
||||
<!-- TODO
|
||||
.l-sub-section
|
||||
:marked
|
||||
Learn more about testing components in chapter [Testing Components]
|
||||
:marked
|
||||
-->
|
||||
### Binding the Hero Service
|
||||
When we view our app in the browser we see we have an error displayed in the developer console
|
||||
|
||||
code-example(format="." language="html").
|
||||
EXCEPTION: No provider for HeroService! (AppComponent -> HeroService)
|
||||
|
||||
:marked
|
||||
We used Dependency Injection to tell our `AppComponent` that it should inject the `HeroService`. However we need to tell our app about the `HeroService` so it can provide it when needed. The way we do this is by declaring the `HeroService` as a binding when we bootstrap our app.
|
||||
Let’s pass a second argument to the `bootstrap` method to declare the `HeroService` as an application binding.
|
||||
```
|
||||
bootstrap(AppComponent, [HeroService]);
|
||||
```
|
||||
We can add other bindings here, as needed.
|
||||
|
||||
When we view our app in the browser the error is gone and our application runs as expected showing our list of heroes.
|
||||
|
||||
## Promises
|
||||
Our `HeroService` synchronously returns a list of heroes. It operates synchronously because the list of heroes is mocked. What happens when we want to switch that to get the heroes from a web service? The web service call over http would happen asynchronously.
|
||||
|
||||
We don’t yet call http, but we aspire to in later chapters. So how do we write our `HeroService` so that it won’t require refactoring the consumers of `HeroService` later? We make our `HeroService`’s `getHeroes` method return a promise to provide the heroes.
|
||||
|
||||
The key is that our components won’t know how the data is being retrieved. We can return mock heroes or heroes from http, and the component will call the service’s method the same way.
|
||||
|
||||
### Returning a Promise
|
||||
Let’s refactor the `getHeroes` method in `HeroService` to return the heroes in a promise.
|
||||
```
|
||||
import { Hero } from './hero';
|
||||
import { HEROES } from './mock-heroes';
|
||||
|
||||
export class HeroService {
|
||||
getHeroes() {
|
||||
return Promise.resolve(HEROES);
|
||||
}
|
||||
}
|
||||
```
|
||||
The `Promise` is immediately resolving and passing the the hero data back in the promise.
|
||||
|
||||
### Acting on a Promise
|
||||
Let’s refactor the `getHeroes` method in `HeroService` to return the heroes in a promise. First, we create a new method in the `AppComponent` to get the heroes and named it `getHeroes`.
|
||||
|
||||
When we call our heroes we start by resetting the `selectedHero` and `heroes` properties.
|
||||
```
|
||||
getHeroes() {
|
||||
this.selectedHero = undefined;
|
||||
this.heroes = [];
|
||||
}
|
||||
```
|
||||
The `getHeroes` method in `HeroService` returns a promise. So we cannot simply set the return value to `this.heroes`. The method returns a promise and the `heroes` property expects an array of `Hero`. What do we do?
|
||||
|
||||
We define a `then` to handle the response from the promise when it resolves. We will set the heroes inside of the `then`.
|
||||
```
|
||||
this._heroService.getHeroes()
|
||||
.then(heroes => this.heroes = heroes);
|
||||
```
|
||||
The `then` accepts a function, in this case a lambda that passes in the heroes and sets them to the `heroes` property on `AppComponent`.
|
||||
|
||||
We need to return a value for the heroes from the method so a caller can get the heroes when they are ready. Let’s return our component’s `heroes` property, which we first reset to an empty array.
|
||||
```
|
||||
return this.heroes;
|
||||
```
|
||||
When we put this all together we see we are setting our heroes to an empty array. Then we call the service and get a promise. Finally we return the reference to our `heroes` property, which has the empty array.
|
||||
```
|
||||
getHeroes() {
|
||||
this.selectedHero = undefined;
|
||||
this.heroes = [];
|
||||
|
||||
this._heroService.getHeroes()
|
||||
.then(heroes => this.heroes = heroes);
|
||||
|
||||
return this.heroes;
|
||||
}
|
||||
```
|
||||
So how do the heroes get populated? When the promise resolves, the `heroes` are updated to include the response from the promise.
|
||||
|
||||
Finally we call the method we just created in our `onInit` method.
|
||||
```
|
||||
onInit() {
|
||||
this.heroes = this.getHeroes();
|
||||
}
|
||||
```
|
||||
When we view our app in the browser we can see the heroes are displayed.
|
||||
|
||||
We are using mock data right now, but we aspire to call a web service over http asynchronously in the future. When we do refactor to use http, the beauty of the promise we created here is that our component won’t have to change at all!
|
||||
|
||||
### Reviewing the App Structure
|
||||
Let’s verify that we have the following structure after all of our good refactoring in this chapter:
|
||||
|
||||
code-example.
|
||||
angular2-tour-of-heroes
|
||||
|---- node_modules
|
||||
|---- app
|
||||
| |---- app.component.ts
|
||||
| |---- boot.ts
|
||||
| |---- hero.ts
|
||||
| |---- hero-detail.component.ts
|
||||
| |---- hero.service.ts
|
||||
| |---- mock-heroes.ts
|
||||
|---- index.html
|
||||
|---- tsconfig.json
|
||||
|---- package.json
|
||||
|
||||
:marked
|
||||
## Recap
|
||||
### The Road We’ve Travelled
|
||||
Let’s take stock in what we’ve built.
|
||||
- We created a reusable component
|
||||
- We learned how to make a component accept input
|
||||
- We created a service class that can be shared by many components
|
||||
- We created mock hero data and imported them into our service
|
||||
- We designed our service to return a promise and our component to get our data from the promise
|
||||
|
||||
### The Road Ahead
|
||||
. . . We’ll learn more about all of these in the next chapter.
|
||||
|
||||
Our Tour of Heroes has become more reusable using shared components and services. We want to create a dashboard, add menu links that route between the views, and format data in a template. As our app evolves, we’ll learn how to design it to make it easier to grow and maintain. We’ll learn more about these tasks in the coming chapters.
|
||||
### Reviewing the App Structure
|
||||
Let’s verify that we have the following structure after all of our good refactoring in this chapter:
|
||||
|
||||
.filetree
|
||||
.file angular2-tour-of-heroes
|
||||
.children
|
||||
.file node_modules
|
||||
.file app
|
||||
.children
|
||||
.file app.component.ts
|
||||
.file boot.ts
|
||||
.file hero.ts
|
||||
.file hero-detail.component.ts
|
||||
.file index.html
|
||||
.file package.json
|
||||
.file tsconfig.json
|
||||
|
||||
.l-main-section
|
||||
:marked
|
||||
## The Road We’ve Travelled
|
||||
Let’s take stock in what we’ve built.
|
||||
|
||||
* We created a reusable component
|
||||
* We learned how to make a component accept input
|
||||
|
||||
[Run the live example for part 3](/resources/live-examples/toh-3/ts/plnkr.html)
|
||||
|
||||
### The Road Ahead
|
||||
Our Tour of Heroes has become more reusable using shared components.
|
||||
We want to create reusable services that retrieve our data. As our app evolves,
|
||||
we’ll learn how to design it to make it easier to grow and maintain.
|
||||
We’ll learn more about these tasks in the coming chapters.
|
@ -1,473 +1,250 @@
|
||||
include ../../../../_includes/_util-fns
|
||||
|
||||
:marked
|
||||
# Routing Around the App
|
||||
Our Tour of Heroes is a single view, but we have new requirements to create other views, such as a dashboard, and navigate between them. We’ll add a dashboard component and use Angular’s router to handle navigation between views. We have another requirement to allow selecting a hero from either the dashboard or the heroes view and route directly to the hero details. We’ll need to learn about and use route parameters to tackle this.
|
||||
|
||||
When we’re done, users will be able to navigate the app like this:
|
||||
figure.image-display
|
||||
img(src='/resources/images/devguide/toh/nav-diagram.png' alt="View navigations")
|
||||
:marked
|
||||
Finally, we’ll want to filter and format data in our app using Angular’s Pipes.
|
||||
## Reviewing Where We Left Off
|
||||
Let’s verify that we have the following structure after adding our hero service and hero detail component in the previous chapter:
|
||||
code-example.
|
||||
angular2-tour-of-heroes
|
||||
├── node_modules
|
||||
├── app
|
||||
| ├── app.component.ts
|
||||
| ├── boot.ts
|
||||
| ├── hero.ts
|
||||
| ├── hero-detail.component.ts
|
||||
| ├── hero.service.ts
|
||||
| └── mock-heroes.ts
|
||||
├── index.html
|
||||
├── tsconfig.json
|
||||
└── package.json
|
||||
:marked
|
||||
# Services
|
||||
Our app is growing.
|
||||
Use cases are flowing in for reusing components, passing data to components, sharing the hero data, and preparing to retrieve the data asynchronously via a promise.
|
||||
|
||||
:marked
|
||||
### Keep the app transpiling and running
|
||||
We want to start the TypeScript compiler, have it watch for changes, and start our server. We'll do this by typing
|
||||
.l-main-section
|
||||
:marked
|
||||
## Reviewing Where We Left Off
|
||||
Before we continue with our Tour of Heroes, let’s verify we have the following structure. If not, we’ll need to go back and follow the previous chapters.
|
||||
|
||||
code-example(format="." language="bash").
|
||||
npm start
|
||||
.filetree
|
||||
.file angular2-tour-of-heroes
|
||||
.children
|
||||
.file node_modules
|
||||
.file app
|
||||
.children
|
||||
.file app.component.ts
|
||||
.file boot.ts
|
||||
.file index.html
|
||||
.file package.json
|
||||
.file tsconfig.json
|
||||
:marked
|
||||
### Keep the app transpiling and running
|
||||
We want to start the TypeScript compiler, have it watch for changes, and start our server. We'll do this by typing
|
||||
|
||||
:marked
|
||||
## Flying Overhead
|
||||
Before we dash into routing for our Tour of Heroes, let’s fly over what we’re going to need to do. Since we’ll want to be routing to entirely different views, we’ll want to separate a few of the components and their template content. We’re also going to be adding two entirely new components. The first for our new requirement, the dashboard. The second component will be to host our menus with routing links and configuration.
|
||||
Here is our checklist of what we’ll need to tackle
|
||||
1. Move the heroes from AppComponent to the more aptly named HeroesComponent
|
||||
1. Create a new AppComponent that will host the menu links and routing
|
||||
1. Create the DashboardComponent to show our top heroes
|
||||
1. Create a class to handle routing configuration
|
||||
We’ll tackle these by separating the components, creating our new components, and adding routing so we can navigate around our Tour of Heroes.
|
||||
|
||||
## Separating the Components
|
||||
We’ll want a component to host the menu links for the heroes and the dashboard. This will be the first component that our app loads. But currently our app loads `AppComponent` first, which has our list of heroes. It’s time to separate our components so we have a component that hosts our menu links and a component that lists our heroes. We’ll call these `AppComponent` and `HeroesComponent`.
|
||||
|
||||
### Creating the HeroesComponent
|
||||
Since we have an `AppComponent` that lists heroes, let’s start by renaming `app.component.ts` to `hero.component.ts`. Then we’ll rename the component from `AppComponent` to `HeroComponent` and we’ll rename the selector to `my-heroes`.
|
||||
code-example.
|
||||
selector: 'my-heroes',
|
||||
:marked
|
||||
Finally, we’ll export the `HeroesComponent` as we will want to use it from another module when we define our routing.
|
||||
code-example.
|
||||
export class HeroesComponent {
|
||||
:marked
|
||||
## Creating the New AppComponent
|
||||
Our app needs a menu and a place to show the dashboard and heroes views. This is effectively the shell for our app.
|
||||
code-example(format="." language="bash").
|
||||
npm run go
|
||||
|
||||
We’ll create a new file named `app.component.ts` and create our new `AppComponent` inside of the file. This will be the first component we load in our app. It will host the menu links, when we create them.
|
||||
|
||||
We assign our `AppComponent` a selector of `my-app`.
|
||||
+makeExample('toh-4/ts/app/app.component.pt4.ts')
|
||||
:marked
|
||||
`AppComponent` is the entry point of our app. This makes it the ideal place to bootstrap our app, which is why we pass in the `AppComponent` and the shared `HeroService` that all many of our components will use.
|
||||
|
||||
We export our `AppComponent` as we’ll want to refer to it from our bootstrapping process.
|
||||
|
||||
## Bootstrapping the Tour of Heroes
|
||||
The start-up of an app is also known as bootstrapping. We are currently bootstrapping in our `HeroesComponent`, which no longer makes sense. So let’s change that and separate this startup logic.
|
||||
:marked
|
||||
This will keep the application running while we continue to build the Tour of Heroes.
|
||||
|
||||
Let’s move the bootstrapping logic into a new file. We’ll create a new file named `boot.ts` in the `app` folder.
|
||||
|
||||
Let’s add the following lines to `boot.ts`:
|
||||
+makeExample('toh-4/ts/app/bootstrap.pt4.ts')
|
||||
:marked
|
||||
The bootstrap function accepts as its first parameter, the first component that the app will use.
|
||||
|
||||
Now let’s do a little cleanup work. Let’s remove the bootstrap logic and remove `bootstrap` from the import statement in the `heroes.component.ts`.
|
||||
|
||||
We had already exported our `AppComponent`, which is why we can now import it and bootstrap it here. We pass in the shared `HeroService` that many of our components will use, so it will be ready when we need it.
|
||||
|
||||
Now let’s tell our module loader to start by loading our `bootstrap` module. We’ll do this in our`index.html` file
|
||||
+makeExample('toh-4/ts/app/index.pt4.html','bootstrap')
|
||||
:marked
|
||||
### Viewing our Progress
|
||||
Let’s add a title for our app which we’ll bind to a `title` property on our component. We’ll set the title to “Tour of Heroes”.
|
||||
+makeExample('toh-4/ts/app/index.pt4.html','title')
|
||||
:marked
|
||||
Our title now belongs in the `AppComponent`, but it also still exists in `HeroesComponent`. So let’s tidy up by removing the `title` from the `HeroesComponent` class and template.
|
||||
|
||||
When we view our app in the browser we should now only see our title of “Tour of Heroes”.
|
||||
But where is the rest of our app? We haven’t shown it yet!
|
||||
|
||||
Our app’s entry point is the `bootstrap` module which loads the `AppComponent`. `AppComponent` in turn only shows a title.
|
||||
|
||||
Our next step is to configure the menu links and routes that will show our views.
|
||||
|
||||
## Adding the Router to our App
|
||||
The Angular router is a separate and distinct module that we can include as needed. Well, our Tour of Heroes app needs routing, so let’s add it!
|
||||
|
||||
### Including the Router
|
||||
We add a script tag referencing the router code. We’ll make sure this comes after the angular script reference.
|
||||
## Creating a Hero Service
|
||||
Our stakeholders have shared their larger vision for our app. They tell us they want to show the heroes in various ways in different pages. We have a way to select a hero from a list, but we will also need a dashboard with the top heroes and a separate view for editing hero details.
|
||||
|
||||
Then let’s set our base href to `/src/` since that is where our source code is located. Our `index.html`’s head section should nw look like this:
|
||||
+makeExample('toh-4/ts/app/index.pt4.html','head')
|
||||
:marked
|
||||
Now we’ll be loading the Angular router!
|
||||
|
||||
### Configuring Routes
|
||||
We want to display a menu that has links to a dashboard and to our list of heroes. Let’s configure the first route to show our `HeroesComponent`.
|
||||
All of these views need hero data. Our `AppComponent` defines and uses a list of heroes, but it is not ideal to create our AppComponent when just the heroes data elsewhere. Fortunately we can create a shared service that will provide the heroes.
|
||||
|
||||
Our app will have a few routes and we may want to use them in more than one module. Let’s create a file named `route.config.ts` to host our routes.
|
||||
|
||||
#### Defining Routes
|
||||
We want to show our heroes list. So let’s define our first route to show our `HeroesComponent` template.
|
||||
+makeExample('toh-4/ts/app/route.config.pt4.ts','first-route')
|
||||
:marked
|
||||
Our route needs a path. This is what will show up in the address bar of the browser. Our path for our `HeroesComponent` will be `/`.
|
||||
### Creating the HeroService
|
||||
Were going to create a service that can be used by any component that wants hero data. Let’s start by creating a file and naming it `hero.service.ts`. We name the class `HeroService` and export it, so our components can import it.
|
||||
```
|
||||
export class HeroService { }
|
||||
```
|
||||
#### The getHeroes Method
|
||||
We create a method named `getHeroes` in our `HeroService`. It will return an array of `Hero` objects, so let’s import the `Hero` class and define our method.
|
||||
```
|
||||
import { Hero } from './hero';
|
||||
|
||||
The `component` property identifies the component we will load when we go to this route. In this case we want to load the `HeroesComponent`.
|
||||
|
||||
The `as` property is what the route is known as. In other words, it is the name of the route.
|
||||
We set these three properties in an object and export that object in an array named `APP_ROUTES`. Right now we have one route. But as we add more routes, this technique of hosting them in a `route.config.ts` file and exporting an array of them will make it easier to manage our routes.
|
||||
|
||||
#### The RouteConfig Decorator
|
||||
Now that we have defined a route, we need to tell Angular where to find it. Let’s go to our `AppComponent` and add the `RouteConfig` decorator the class. We’ll need to import the `RouteConfig` decorator from Angular’s router module, too.
|
||||
export class HeroService {
|
||||
getHeroes() : Hero[] {
|
||||
}
|
||||
}
|
||||
```
|
||||
#### Mocking the Heroes
|
||||
Our `HeroService` shouldn’t be defining the hero data. Instead, the service should handle retrieving the data from another source. That source could be a mock data, a web service, or even local storage. We will design our `HeroService` to get the data from any of these sources and not affect the calling component. This will make it more reusable and more testable.
|
||||
|
||||
Now we import the `APP_ROUTES`array of routes that we just created. We’ll pass these into the `RouteConfig` decorator.
|
||||
+makeExample('toh-4/ts/app/index.pt4.html','routes-title')
|
||||
:marked
|
||||
Our app now has its first route, but we need a place to show the heroes view.
|
||||
|
||||
<!-- Learn more about RouteConfig in the chapter [Router] -->
|
||||
|
||||
### Showing the View with the Router-Outlet
|
||||
Angular knows we have a route when we navigate to `/heroes`. But where does the view show in the HTML? We haven’t told our app how to do that yet!
|
||||
|
||||
Let’s add the `<router-outlet>` directive to the template of `AppComponent`. The `<router-outlet>` is a placeholder that the router uses to place the views. When we go to the `/heroes` route, the router will show the `HeroesComponent` template where we place the `<router-outlet>`.
|
||||
+makeExample('toh-4/ts/app/index.pt4.html','router-outlet')
|
||||
:marked
|
||||
When need to declare to the `AppComponent` that we are using the `router-outlet` directive. To do this we’ll import a special `ROUTER_DIRECTIVES` array of router specific directives.
|
||||
```
|
||||
import {RouteConfig, ROUTER_DIRECTIVES} from 'angular2/router';
|
||||
```
|
||||
Then we’ll declare them to the component in the `@Component` decorator’s `directives` property.
|
||||
```
|
||||
directives: [ROUTER_DIRECTIVES]
|
||||
```
|
||||
Let’s go view our app in the browser and see where we are. Uh oh, we see an error in the developer console.
|
||||
code-example(language="html").
|
||||
EXCEPTION: No provider for Router! (RouterOutlet -> Router)
|
||||
:marked
|
||||
Angular is warning us that we are using the router and the `router-outlet` but we did not inject the router’s provider. We can fix this by injecting this in the bootstrap module. First we’ll import the `ROUTER_PROVIDERS` from the router module.
|
||||
```
|
||||
import {ROUTER_PROVIDERS} from 'angular2/router';
|
||||
```
|
||||
Then we’ll pass the `ROUTER_PROVIDERS` to the `bootstrap` method.
|
||||
```
|
||||
bootstrap(AppComponent, [ROUTER_PROVIDERS, HeroService]);
|
||||
```
|
||||
Now when we view our app in the browser we see our heroes list!
|
||||
|
||||
## Creating Navigation Links
|
||||
Okay, our Tour of Heroes is not quite where we want it yet. We’ve created the `AppComponent` which hosts the routing and we move the heroes list to the `HeroesComponent`. But to fulfill our requirements we need to create a dashboard and add a way to navigate between the different views. Let’s continue by adding the new dashboard component and the navigation links.
|
||||
|
||||
### Empty Dashboard
|
||||
Routing makes a lot more sense once we have multiple views. We have a heroes view to show but now we need to create our dashboard view. Let’s create the `DashboardComponent`so we can finish creating the navigation between the components.
|
||||
|
||||
Let’s create a super simple dashboard component.
|
||||
+makeExample('toh-4/ts/app/index.pt4.html','simple-dashboard-component')
|
||||
:marked
|
||||
We’ll come back to the dashboard once we complete routing. For now this will do nicely to help us make sure we can navigate between the `HeroesComponent` and the `DashboardComponent`.
|
||||
|
||||
### Configuring the Dashboard’s Routes
|
||||
Now that we have a component for our dashboard, let’s go configure a route that will take us there.
|
||||
|
||||
We’ll open `route.config.ts` and add another route for the dashboard.
|
||||
|
||||
Our dashboard should be the first thing we see when load our app. So the dashboard route will be the default route of `/` while our heroes will now be accessible via the path `/heroes`.
|
||||
|
||||
We’ll also import the `DashboarComponent` so we can route to it with the dashboard route. And we’ll add the dashboard route to the `APP_ROUTES` export.
|
||||
|
||||
Our `route.config.ts` should now look like the following code:
|
||||
+makeExample('toh-4/ts/app/route.config.pt4.ts','dashboard-route')
|
||||
:marked
|
||||
Now we two components we can route between and we have defined the routes for both components. Next up, we’ll add navigation links to route between them.
|
||||
|
||||
### Navigation Links
|
||||
Let’s add the navigation links to the`AppComponent`’s template. The Angular router uses a special `router-link` directive to navigate to the routes we defined. We can think of these as links that will navigate to another component using the router.
|
||||
|
||||
We also add a title for our app which we’ll bind to a `title` property on our component.
|
||||
+makeExample('toh-4/ts/app/index.pt4.html','router-link')
|
||||
:marked
|
||||
The `router-link` is not set to url. We bound the `router-link` to the routes that we specified in the `as` property of our `@RouteConfig`. The `as` is the key that we use to reference the routes.
|
||||
|
||||
#### Reusable Routing Config
|
||||
We just hard-coded the routes that the `router-link` properties are bound. We can do better. We recall that we previously created route configuration in `route.config.ts`. We import its `Routes`.
|
||||
+makeExample('toh-4/ts/app/index.pt4.html','import-app-routes')
|
||||
:marked
|
||||
And then we use it to initialize a `routes` property on our `AppComponent`.
|
||||
+makeExample('toh-4/ts/app/app.component.pt4.ts','initialize-routes-property')
|
||||
:marked
|
||||
Then we can simply reference the routes as shown below using our route configuration variables, such as `routes.heroes.as`.
|
||||
+makeExample('toh-4/ts/app/index.pt4.html','router-link')
|
||||
:marked
|
||||
When we view our app in the browser we are brought directly to our dashboard. We can navigate between the dashboard and the heroes til our hearts are content.
|
||||
|
||||
## Adding the Top Heroes to Our Dashboard
|
||||
Our dashboard view is a bit bland as it only contains a title. Let’s spice it up by adding the top 4 heroes at a glance.
|
||||
<!-- Ward sweep section below -->
|
||||
### Top Heroes Template Content
|
||||
Let’s add the template to our dashboard to show the top four heroes. We’ll use the `ng-for` directive to iterate over a list of heroes (which we have not retrieved yet) and display them. We’ll use `<div>` elements as we’re going to custom style them.
|
||||
+makeExample('toh-4/ts/app/index.pt4.html','ng-for')
|
||||
:marked
|
||||
We’ve been down this road before. We are using the `ng-for` directive, so we have to declare it in the component. Let’s do that now by first importing `CORE_DIRECTIVES`. (Remember that `CORE_DIRECTIVES` is a convenience array containing the most common directives such as `ng-for`.)
|
||||
```
|
||||
import {Component, CORE_DIRECTIVES} from 'angular2/angular2';
|
||||
```
|
||||
Then we declare the `CORE_DIRECTIVES` to the component.
|
||||
```
|
||||
directives: [CORE_DIRECTIVES]
|
||||
```
|
||||
### Using the Shared HeroService
|
||||
We just iterated over a list of heroes, but we don’t have any heroes in the `DashboardComponent`. We do have a `HeroService` that provides heroes. In fact, we already used this service in the `HeroComponent`. Let’s re-use this same service for the `DashboardComponent` to get a list of heroes.
|
||||
|
||||
We’ll create a `heroes` property in our `DashboardComponent`.
|
||||
```
|
||||
public heroes: Hero[];
|
||||
```
|
||||
And we import the `Hero`
|
||||
```
|
||||
import {Hero} from './hero';
|
||||
```
|
||||
#### Injecting a Service
|
||||
We’ll be needing the `HeroService`, so let’s import it and inject it into our `DashboardComponent`. Here we import it.
|
||||
```
|
||||
import {HeroService} from './hero.service';
|
||||
```
|
||||
And here we inject it into our component’s constructor.
|
||||
```
|
||||
constructor(private _heroService: HeroService) { }
|
||||
```
|
||||
#### Getting the Heroes on Initialization
|
||||
We want our heroes to be loaded when the component is initialized, just like we did in the `HeroesComponent`. We’ll tackle this the same way, by using the onInit Lifecycle hook.
|
||||
|
||||
We can implement the `OnInit` interface and code the `onInit` method.
|
||||
```
|
||||
export class DashboardComponent implements OnInit {
|
||||
```
|
||||
Here we implement the `onInit` method to get our heroes, again just like we did for the `HeroesComponent`.
|
||||
+makeExample('toh-4/ts/app/app.component.pt4.ts','oninit')
|
||||
:marked
|
||||
Notice we did not have to know how to get the heroes, we just needed to know the method to call from the `HeroService`. This is an advantage of using shared services.
|
||||
Once.
|
||||
|
||||
When we view our app in the browser we see the dashboard light up with all of our heroes. This isn’t exactly what we want so let’s trim that down to the top four heroes.
|
||||
|
||||
### Slicing with Pipes
|
||||
Our requirement is to show the top four heroes. If only we had something that would automatically get a subset of the data. Well, we do! They are called Pipes.
|
||||
We have a list of heroes in `AppComponent`. We will move it to a new file named `mock-heroes.ts` and export the list.
|
||||
|
||||
Angular has various built-in pipes that make formatting and filtering data easy. We’ll take advantage of a pipe named `slice` to get a slice of our heroes array.
|
||||
```
|
||||
<div *ng-for="#hero of heroes | slice:0:4">
|
||||
```
|
||||
After the `ng-for` we added a pipe character and then the `slice` pipe. We tell the `slice` pipe to start at the 0th item in the array and get four items.
|
||||
+makeExample('toh-3/ts-snippets/app/mock-heroes.ts', 'mocking-heroes')
|
||||
|
||||
:marked
|
||||
### Returning the Mocked Heroes
|
||||
Our `HeroService` needs to get the list of heroes, so let’s import the mocked heroes module. Then we’ll return the HEROES array.
|
||||
```
|
||||
import {Hero} from './hero';
|
||||
import {HEROES} from './mock-heroes';
|
||||
|
||||
export class HeroService {
|
||||
getHeroes() {
|
||||
return HEROES;
|
||||
}
|
||||
}
|
||||
```
|
||||
TypeScript can implicitly determine that that return type is `Hero[]` since the return value is of that same type. This allows use to remove the explicit return type from the `getHeroes` method.
|
||||
|
||||
### Injecting the Hero Service
|
||||
We’ve set ourselves up so we can use the `HeroService` from other components. Let’s import the `HeroService` in our `AppComponent`.
|
||||
```
|
||||
import {HeroService} from './hero.service';
|
||||
```
|
||||
Importing the service allows us to reference it, but we need to make sure the `HeroService` dependency is instantiated when our component needs it. We inject the `HeroService` into our `AppComponent`’s constructor.
|
||||
```
|
||||
constructor(private _heroService: HeroService) { }
|
||||
```
|
||||
We just injected our dependency into the component, thus we performed Dependency Injection.
|
||||
|
||||
.l-sub-section
|
||||
:marked
|
||||
Learn more about Dependency Injection in the [Dependency Injection](../guide/dependency-injection.html) chapter.
|
||||
|
||||
:marked
|
||||
We made our instance of the injected `HeroService` be a private property on our `AppComponent` class. As a convention we prefixed the private property with an underscore.
|
||||
|
||||
Since we are not defining the heroes in the `AppComponent` any longer, let’s refactor the `hero` property declaration to be an uninitialized array of `Hero`.
|
||||
```
|
||||
public heroes: Hero[];
|
||||
```
|
||||
|
||||
### The OnInit Lifecycle Hook
|
||||
When our `AppComponent` is created we want it to get the list of heroes. We need to know when the component is initialized and activated, so we’ll use the `OnInit` lifecycle event to tell us this.
|
||||
|
||||
Let’s import Angular’s `OnInit` interface, implement it on our `AppComponent` and define its required `onInit` method. First we add the `OnInit` interface to the import statement.
|
||||
```
|
||||
import {bootstrap, Component, CORE_DIRECTIVES, FORM_DIRECTIVES, OnInit} from 'angular2/angular2';
|
||||
```
|
||||
Now we implement the interface.
|
||||
```
|
||||
class AppComponent implements OnInit {
|
||||
```
|
||||
Then we define the `onInit` method and get our heroes from our `HeroService`.
|
||||
```
|
||||
onInit() {
|
||||
this.heroes = this._heroService.getHeroes();
|
||||
}
|
||||
```
|
||||
|
||||
.l-sub-section
|
||||
:marked
|
||||
Learn more about lifecycle hooks in the [Lifecycle Hooks](../guide/lifecycle-hooks.html) chapter.
|
||||
:marked
|
||||
Why not use the constructor to get the heroes? When we test our application we want an opportunity to create the class without any state being set. This will make it easier to test and reduce external factors, such as calling a service in the constructor. Therefore the constructor is best suited to help us inject dependencies and initialize variables. We need a place to get our heroes right after our class is constructed but before the view is rendered. The OnInit lifecycle hook gives us this opportunity.
|
||||
<!-- TODO
|
||||
.l-sub-section
|
||||
:marked
|
||||
Learn more about Pipes in the chapter [Pipes](../guide/pipes.html)
|
||||
Learn more about testing components in chapter [Testing Components]
|
||||
:marked
|
||||
When we view our app we now see the first four heroes are displayed.
|
||||
|
||||
### Heroes with Style
|
||||
Our creative designers have added a requirement that the dashboard should show the heroes in a row of rectangles. We’ve written some CSS to achieve this along with some simple media queries to achieve responsive design.
|
||||
|
||||
We could put the CSS in the component, but there are over 30 lines of CSS and it would get crowded fast. Most editors make it easier to code CSS in a *.css file too. Fortunately, we can separate the styles into their own file and reference them.
|
||||
|
||||
#### Adding the Dashboard’s CSS File
|
||||
Let’s create a file to hold the `DashboardComponent`’s CSS. We’ll name it `dashboard.component.css` and put it in the `app` folder.
|
||||
|
||||
Now let’s add the following CSS to the file.
|
||||
+makeExample('toh-4/ts/app/index.pt4.html','css')
|
||||
:marked
|
||||
We need to reference the file from our component so the styles will be applied properly. Let’s add this reference to the component’s `styleUrls` property.
|
||||
```
|
||||
styleUrls: ['app/dashboard.component.css'],
|
||||
```
|
||||
The `styleUrls` property is an array, which we might guess suggests that we can add multiple styles from different locations. And we would be right! In this case we have one file, but we could add more for our component if needed.
|
||||
|
||||
#### Template Urls
|
||||
While we are at it, let’s move our HTML for the `DashboardComponent` to its own file. We’ll create a file named `dashboard.component.html` in the `app` folder and move the HTML there.
|
||||
We need to reference the the template, so let’s change our component’s `template` property to `templateUrl` and set it to the location of the file.
|
||||
```
|
||||
template: 'app/dashboard.component.html',
|
||||
```
|
||||
Notice we are now using single quotes and not the back-ticks since we only have the need for a single line string.
|
||||
|
||||
#### Applying the Styles to Our Template
|
||||
-->
|
||||
### Binding the Hero Service
|
||||
When we view our app in the browser we see we have an error displayed in the developer console
|
||||
|
||||
Now that we have some style, let’s take advantage of it by applying it to our template. Our template should now look like this:
|
||||
+makeExample('toh-4/ts/app/index.pt4.html','template-styled')
|
||||
:marked
|
||||
When we view our app in the browser it now shows our dashboard with our four top heroes in a row.
|
||||
|
||||
## Styling the Navigation Links
|
||||
Our creative design team requested that the navigation links also be styled. Let’s make the navigation links look more like selectable buttons.
|
||||
|
||||
### Defining Styles for Navigation Links
|
||||
Let’s add the following styles to our `AppComponent`’s `styles` property. We’ll define some classes named `router-link` that style the default, active, visited and hover selectors. The active selector changes the color of the link to make it easy to identify which link is selected.
|
||||
+makeExample('toh-4/ts/app/app.component.pt4.ts','styles')
|
||||
:marked
|
||||
This time we define the styles in our component because there are only a few of them.
|
||||
code-example(format="." language="html").
|
||||
EXCEPTION: No provider for HeroService! (AppComponent -> HeroService)
|
||||
|
||||
### Applying Styles to the Navigation Links
|
||||
Now that we have styles for our navigation links, let’s apply them in the `AppComponent` template. When we are done, our component will look like the following:
|
||||
+makeExample('toh-4/ts/app/index.pt4.html','styled-nav-links')
|
||||
:marked
|
||||
#### Where Did the Active Route Come From?
|
||||
The Angular Router makes it easy for us to style our active navigation link. The `router-link-active` class is automatically added to the Router’s active route for us. So all we have to do is define the style for it.
|
||||
|
||||
Sweet! When we view our app in the browser we see the navigation links are now styled, as are our top heroes!
|
||||
figure.image-display
|
||||
img(src='/resources/images/devguide/toh/dashboard-top-heroes.png' alt="View navigations")
|
||||
:marked
|
||||
## Add Route to Hero Details
|
||||
We can navigate between the dashboard and the heroes view, but we have a requirement from our users to be able to select a hero from either of those views and go directly to the selected hero’s details. Let’s configure a route to go directly to the `HeroDetailComponent` passing the hero’s id as a parameter.
|
||||
|
||||
### Configuring a Route with a Parameter
|
||||
We’ve already added a few routes to the `routes.config.ts` file, so it’s natural that we’d start there to add the route to go to the `HeroDetailComponent`. Let’s start by adding the import statement for the component.
|
||||
+makeExample('toh-4/ts/app/route.config.pt4.ts','route-parameter-import')
|
||||
:marked
|
||||
Now we add a route for the details to the `Routes` object.
|
||||
+makeExample('toh-4/ts/app/route.config.pt4.ts','route-parameter-detail')
|
||||
:marked
|
||||
The route will lead to the `HeroDetailComponent`, passing along the value for the hero’s id. The routing configuration identifies parameters as parts of the path that are prefixed with a `:` such as `:id`.
|
||||
|
||||
### Receiving a Parameter
|
||||
We want to navigate to the `HeroDetailComponent`, so let’s modify it to accept the `id` parameter. We import the `RouteParams` so we can access the parameter.
|
||||
We assign our `AppComponent` a selector of `my-app`.
|
||||
+makeExample('toh-4/ts/app/app.component.pt4.ts','import-params')
|
||||
:marked
|
||||
Now we inject the `RouteParams` into the `HeroDetailComponent` constructor.
|
||||
+makeExample('toh-4/ts/app/app.component.pt4.ts','inject-routeparams')
|
||||
:marked
|
||||
We want to immediately access the parameter, so let’s implement the `OnInit` interface and its `onInit` method.
|
||||
+makeExample('toh-4/ts/app/app.component.pt4.ts','access-params')
|
||||
:marked
|
||||
And let’s not forget to import `OnInit`.
|
||||
+makeExample('toh-4/ts/app/app.component.pt4.ts','import-onit')
|
||||
:marked
|
||||
Using the `onInit`method, we can grab the parameter as soon as the component initializes. We’ll access the parameter by name and later we’ll get the hero by its `id`.
|
||||
+makeExample('toh-4/ts/app/app.component.pt4.ts','onit-id-param')
|
||||
:marked
|
||||
Our `HeroDetailComponent` is already used in the `HeroesComponent`. When we select a hero from the list we are passing the hero object from the list to the `HeroDetailComponent`. We want this component to support that functionality or be able to accept the hero’s id. Let’s revise the logic in the `onInit` to handle this.
|
||||
+makeExample('toh-4/ts/app/app.component.pt4.ts','onit-hero-id')
|
||||
:marked
|
||||
Our component will first check if it has a hero. If it doesn’t it will then check for the routing parameter so it can get the hero.
|
||||
.l-sub-section
|
||||
:marked
|
||||
Learn more about RouteParams in the chapter [Router](../guide/router.html)
|
||||
:marked
|
||||
Getting the Hero
|
||||
When we pass the id to the `HeroDetailComponent` we need to go get the hero from our `HeroService`. Let’s import the `HeroService` so we can use it to get our hero.
|
||||
+makeExample('toh-4/ts/app/bootstrap.pt4.ts','import-hero-service')
|
||||
:marked
|
||||
And then we inject the `HeroService` into the constructor.
|
||||
+makeExample('toh-4/ts/app/app.component.pt4.ts','inject-hero-service')
|
||||
:marked
|
||||
We then stub out the call to the `HeroService` to get the hero by the hero’s id. But wait a second, we don’t have a way to get the hero by id … yet.
|
||||
|
||||
Our `HeroService` is the right place to get a single hero. We’ll create a method named `getHero` that will accept a parameter, find the hero, and return the hero in a promise.
|
||||
|
||||
We add this method to the `HeroService`.
|
||||
+makeExample('toh-4/ts/app/hero.service.pt4.ts','get-hero-method')
|
||||
:marked
|
||||
Then we go back to our `HeroDetailComponent` and we can call the `getHero` method.
|
||||
+makeExample('toh-4/ts/app/app.component.pt4.ts','onit-hero-method')
|
||||
:marked
|
||||
We grab the hero and set it to the local `hero` property. Now we have everything in place to receive the parameter.
|
||||
|
||||
### Select a Hero on the Dashboard
|
||||
When a user selects a hero in the dashboard, we want to route to the details. Let’s open our dashboard’s template and add a click event to each hero in the template.
|
||||
+makeExample('toh-4/ts/app/index.pt4.html','select-hero-click-event')
|
||||
:marked
|
||||
The click event will call the `gotoDetail` method in the `DashboardComponent`. We don’t have that method yet, so let’s create it. We’ll want to use the router to navigate to the details route we created. So we have to import the router, and while we are at it, we’ll import the `Routes` object we created that describe our routes.
|
||||
+makeExample('toh-4/ts/app/bootstrap.pt4.ts','import-router')
|
||||
:marked
|
||||
Now we can write our method to navigate to the route and pass the parameter. We’ll use the router’s `navigate` method and pass an array that has 2 parameters. The first is the name of the route (the `as` property in the `RouteConfig`). The second is an object with the parameters and values.
|
||||
+makeExample('toh-4/ts/app/route.config.pt4.ts','router-navigate-method')
|
||||
:marked
|
||||
Now when we view our app in the browser and select a hero from the dashboard, we go directly to the hero’s details!
|
||||
.l-sub-section
|
||||
:marked
|
||||
Learn more about RouteParams in the chapter [Router](../guide/router.html)
|
||||
:marked
|
||||
### Select a Hero on the HeroesComponent
|
||||
When a user selects a hero in the dashboard, we go to the details. But we also want this to happen from the `HeroesComponent`. Let’s add the same changes to the `HeroesComponent` that we made to the dashboard.
|
||||
+makeExample('toh-4/ts/app/app.component.pt4.ts','select-hero')
|
||||
:marked
|
||||
The requirement here is to show the hero when selected and allow the user to the details via a button. So when a user selects a hero we want to show the hero’s name and provide a button to navigate to the details.
|
||||
|
||||
Let’s open the `HeroesComponent`, remove the `my-hero-detail` component, and change the template to display the hero’s name instead.
|
||||
+makeExample('toh-4/ts/app/index.pt4.html','display-hero-name')
|
||||
:marked
|
||||
We also added a button with a click event that will call our `gotoDetail` method in our `HeroesComponent`.
|
||||
|
||||
Notice we also used the `uppercase` pipe to format the selected hero’s name. Pipes are extremely helpful at formatting and filtering.
|
||||
.l-sub-section
|
||||
:marked
|
||||
Learn more about Pipes in the chapter [Pipes](../guide/pipes.html)
|
||||
:marked
|
||||
When we view the app in our browser we can now navigate from the dashboard or the heroes component directly to the selected hero’s details!
|
||||
|
||||
### Cleaning Up Templates and Styles
|
||||
We’ve added a lot of HTML and CSS in our template and styles, respectively, in the `HeroesComponent`. Let’s move each of these to their own files.
|
||||
|
||||
We move the HTML for the `HeroesComponent` template to `heroes.component.html`. Then we reference the file in the component’s `templateUrl` property.
|
||||
|
||||
Now our `HeroesComponent` looks much cleaner and easier to maintain since our template and styles are in another file.
|
||||
+makeExample('toh-4/ts/app/app.component.pt4.ts','reference-heroes-component')
|
||||
:marked
|
||||
We’ll also move the HTML out of the `HeroDetailComponent` and into its own file named `hero-detail.component.html`. Then we reference the file from the `templateUrl` property.
|
||||
+makeExample('toh-4/ts/app/app.component.pt4.ts','reference-hero-detail-component')
|
||||
:marked
|
||||
### Adding Styles to the App
|
||||
When we add styles to a component we are making it easier to package everything a component needs together. The HTML, the CSS, and the code are all together in one convenient place. However we can also add styles at an app level outside of a component.
|
||||
|
||||
Our designers just gave us a few more basic styles to apply to our entire app. Let’s add some CSS in a file `styles.css` to the `src` folder to style the app’s basic elements.
|
||||
+makeExample('toh-4/ts/app/index.pt4.html','basic-styles')
|
||||
:marked
|
||||
And let’s reference the stylesheet from the `index.html`.
|
||||
+makeExample('toh-4/ts/app/index.pt4.html','stylesheet')
|
||||
:marked
|
||||
When we view our app in the browser we can see everything still works as expected!
|
||||
|
||||
### Reviewing the App Structure
|
||||
Let’s verify that we have the following structure after all of our good refactoring in this chapter:
|
||||
code-example.
|
||||
angular2-tour-of-heroes
|
||||
|---- node_modules
|
||||
|---- app
|
||||
| |---- app.component.ts
|
||||
| |---- boot.ts
|
||||
| |---- dashboard.component.css
|
||||
| |---- dashboard.component.html
|
||||
| |---- dashboard.component.ts
|
||||
| |---- hero.ts
|
||||
| |---- hero-detail.component.html
|
||||
| |---- hero-detail.component.ts
|
||||
| |---- hero.service.ts
|
||||
| |---- heroes.component.css
|
||||
| |---- heroes.component.html
|
||||
| |---- heroes.component.ts
|
||||
| |---- mock-heroes.ts
|
||||
| |---- route.config.ts
|
||||
|---- index.html
|
||||
|---- styles.css
|
||||
|---- tsconfig.json
|
||||
|---- package.json
|
||||
|
||||
.l-main-section
|
||||
:marked
|
||||
## Recap
|
||||
|
||||
### The Road We’ve Travelled
|
||||
Let’s take stock in what we’ve built.
|
||||
- We added the router to navigate between different components and their templates
|
||||
- We learned how to create router links to represent navigation menu items
|
||||
- We extended a component to either accept a hero as input or accept a router parameter to get the hero
|
||||
- We extended our shared service by adding a new method to it
|
||||
- We added the `slice` pipe to filter the top heroes, and the `uppercase` pipe to format data
|
||||
|
||||
### The Road Ahead
|
||||
Our Tour of Heroes has grown to reuse services, share components, route between components and their templates, and filter and format data with pipes. We have many of the foundations to build an application. In the next chapter we’ll explore how to replace our mock data with real data using http.
|
||||
:marked
|
||||
We used Dependency Injection to tell our `AppComponent` that it should inject the `HeroService`. However we need to tell our app about the `HeroService` so it can provide it when needed. The way we do this is by declaring the `HeroService` as a binding when we bootstrap our app.
|
||||
Let’s pass a second argument to the `bootstrap` method to declare the `HeroService` as an application binding.
|
||||
```
|
||||
bootstrap(AppComponent, [HeroService]);
|
||||
```
|
||||
We can add other bindings here, as needed.
|
||||
|
||||
When we view our app in the browser the error is gone and our application runs as expected showing our list of heroes.
|
||||
|
||||
## Promises
|
||||
Our `HeroService` synchronously returns a list of heroes. It operates synchronously because the list of heroes is mocked. What happens when we want to switch that to get the heroes from a web service? The web service call over http would happen asynchronously.
|
||||
|
||||
We don’t yet call http, but we aspire to in later chapters. So how do we write our `HeroService` so that it won’t require refactoring the consumers of `HeroService` later? We make our `HeroService`’s `getHeroes` method return a promise to provide the heroes.
|
||||
|
||||
The key is that our components won’t know how the data is being retrieved. We can return mock heroes or heroes from http, and the component will call the service’s method the same way.
|
||||
|
||||
### Returning a Promise
|
||||
Let’s refactor the `getHeroes` method in `HeroService` to return the heroes in a promise.
|
||||
```
|
||||
import { Hero } from './hero';
|
||||
import { HEROES } from './mock-heroes';
|
||||
|
||||
export class HeroService {
|
||||
getHeroes() {
|
||||
return Promise.resolve(HEROES);
|
||||
}
|
||||
}
|
||||
```
|
||||
The `Promise` is immediately resolving and passing the the hero data back in the promise.
|
||||
|
||||
### Acting on a Promise
|
||||
Let’s refactor the `getHeroes` method in `HeroService` to return the heroes in a promise. First, we create a new method in the `AppComponent` to get the heroes and named it `getHeroes`.
|
||||
|
||||
When we call our heroes we start by resetting the `selectedHero` and `heroes` properties.
|
||||
```
|
||||
getHeroes() {
|
||||
this.selectedHero = undefined;
|
||||
this.heroes = [];
|
||||
}
|
||||
```
|
||||
The `getHeroes` method in `HeroService` returns a promise. So we cannot simply set the return value to `this.heroes`. The method returns a promise and the `heroes` property expects an array of `Hero`. What do we do?
|
||||
|
||||
We define a `then` to handle the response from the promise when it resolves. We will set the heroes inside of the `then`.
|
||||
```
|
||||
this._heroService.getHeroes()
|
||||
.then(heroes => this.heroes = heroes);
|
||||
```
|
||||
The `then` accepts a function, in this case a lambda that passes in the heroes and sets them to the `heroes` property on `AppComponent`.
|
||||
|
||||
We need to return a value for the heroes from the method so a caller can get the heroes when they are ready. Let’s return our component’s `heroes` property, which we first reset to an empty array.
|
||||
```
|
||||
return this.heroes;
|
||||
```
|
||||
When we put this all together we see we are setting our heroes to an empty array. Then we call the service and get a promise. Finally we return the reference to our `heroes` property, which has the empty array.
|
||||
```
|
||||
getHeroes() {
|
||||
this.selectedHero = undefined;
|
||||
this.heroes = [];
|
||||
|
||||
this._heroService.getHeroes()
|
||||
.then(heroes => this.heroes = heroes);
|
||||
|
||||
return this.heroes;
|
||||
}
|
||||
```
|
||||
So how do the heroes get populated? When the promise resolves, the `heroes` are updated to include the response from the promise.
|
||||
|
||||
Finally we call the method we just created in our `onInit` method.
|
||||
```
|
||||
onInit() {
|
||||
this.heroes = this.getHeroes();
|
||||
}
|
||||
```
|
||||
When we view our app in the browser we can see the heroes are displayed.
|
||||
|
||||
We are using mock data right now, but we aspire to call a web service over http asynchronously in the future. When we do refactor to use http, the beauty of the promise we created here is that our component won’t have to change at all!
|
||||
|
||||
### Reviewing the App Structure
|
||||
Let’s verify that we have the following structure after all of our good refactoring in this chapter:
|
||||
|
||||
.filetree
|
||||
.file angular2-tour-of-heroes
|
||||
.children
|
||||
.file node_modules
|
||||
.file app
|
||||
.children
|
||||
.file app.component.ts
|
||||
.file boot.ts
|
||||
.file hero.ts
|
||||
.file hero-detail.component.ts
|
||||
.file hero.service.ts
|
||||
.file mock-heroes.ts
|
||||
.file index.html
|
||||
.file package.json
|
||||
.file tsconfig.json
|
||||
:marked
|
||||
## Recap
|
||||
### The Road We’ve Travelled
|
||||
Let’s take stock in what we’ve built.
|
||||
- We created a reusable component
|
||||
- We learned how to make a component accept input
|
||||
- We created a service class that can be shared by many components
|
||||
- We created mock hero data and imported them into our service
|
||||
- We designed our service to return a promise and our component to get our data from the promise
|
||||
|
||||
### The Road Ahead
|
||||
. . . We’ll learn more about all of these in the next chapter.
|
||||
|
||||
Our Tour of Heroes has become more reusable using shared components and services.
|
||||
We want to create a dashboard, add menu links that route between the views, and format data in a template.
|
||||
As our app evolves, we’ll learn how to design it to make it easier to grow and maintain.
|
||||
We’ll learn more about these tasks in the coming chapters.
|
479
public/docs/ts/latest/tutorial/toh-pt5.jade
Normal file
479
public/docs/ts/latest/tutorial/toh-pt5.jade
Normal file
@ -0,0 +1,479 @@
|
||||
include ../../../../_includes/_util-fns
|
||||
|
||||
:marked
|
||||
# Routing Around the App
|
||||
Our Tour of Heroes is a single view, but we have new requirements to create other views, such as a dashboard, and navigate between them. We’ll add a dashboard component and use Angular’s router to handle navigation between views. We have another requirement to allow selecting a hero from either the dashboard or the heroes view and route directly to the hero details. We’ll need to learn about and use route parameters to tackle this.
|
||||
|
||||
When we’re done, users will be able to navigate the app like this:
|
||||
figure.image-display
|
||||
img(src='/resources/images/devguide/toh/nav-diagram.png' alt="View navigations")
|
||||
:marked
|
||||
Finally, we’ll want to filter and format data in our app using Angular’s Pipes.
|
||||
## Reviewing Where We Left Off
|
||||
Let’s verify that we have the following structure after adding our hero service and hero detail component in the previous chapter:
|
||||
|
||||
.filetree
|
||||
.file angular2-tour-of-heroes
|
||||
.children
|
||||
.file node_modules
|
||||
.file app
|
||||
.children
|
||||
.file app.component.ts
|
||||
.file boot.ts
|
||||
.file hero.ts
|
||||
.file hero-detail.component.ts
|
||||
.file hero.service.ts
|
||||
.file mock-heroes.ts
|
||||
.file index.html
|
||||
.file package.json
|
||||
.file tsconfig.json
|
||||
|
||||
:marked
|
||||
### Keep the app transpiling and running
|
||||
We want to start the TypeScript compiler, have it watch for changes, and start our server. We'll do this by typing
|
||||
|
||||
code-example(format="." language="bash").
|
||||
npm run go
|
||||
|
||||
:marked
|
||||
## Flying Overhead
|
||||
Before we dash into routing for our Tour of Heroes, let’s fly over what we’re going to need to do. Since we’ll want to be routing to entirely different views, we’ll want to separate a few of the components and their template content. We’re also going to be adding two entirely new components. The first for our new requirement, the dashboard. The second component will be to host our menus with routing links and configuration.
|
||||
Here is our checklist of what we’ll need to tackle
|
||||
1. Move the heroes from AppComponent to the more aptly named HeroesComponent
|
||||
1. Create a new AppComponent that will host the menu links and routing
|
||||
1. Create the DashboardComponent to show our top heroes
|
||||
1. Create a class to handle routing configuration
|
||||
We’ll tackle these by separating the components, creating our new components, and adding routing so we can navigate around our Tour of Heroes.
|
||||
|
||||
## Separating the Components
|
||||
We’ll want a component to host the menu links for the heroes and the dashboard. This will be the first component that our app loads. But currently our app loads `AppComponent` first, which has our list of heroes. It’s time to separate our components so we have a component that hosts our menu links and a component that lists our heroes. We’ll call these `AppComponent` and `HeroesComponent`.
|
||||
|
||||
### Creating the HeroesComponent
|
||||
Since we have an `AppComponent` that lists heroes, let’s start by renaming `app.component.ts` to `hero.component.ts`. Then we’ll rename the component from `AppComponent` to `HeroComponent` and we’ll rename the selector to `my-heroes`.
|
||||
code-example.
|
||||
selector: 'my-heroes',
|
||||
:marked
|
||||
Finally, we’ll export the `HeroesComponent` as we will want to use it from another module when we define our routing.
|
||||
code-example.
|
||||
export class HeroesComponent {
|
||||
:marked
|
||||
## Creating the New AppComponent
|
||||
Our app needs a menu and a place to show the dashboard and heroes views. This is effectively the shell for our app.
|
||||
|
||||
We’ll create a new file named `app.component.ts` and create our new `AppComponent` inside of the file. This will be the first component we load in our app. It will host the menu links, when we create them.
|
||||
|
||||
We assign our `AppComponent` a selector of `my-app`.
|
||||
+makeExample('toh-4/ts-snippets/app/app.component.pt4.ts')
|
||||
:marked
|
||||
`AppComponent` is the entry point of our app. This makes it the ideal place to bootstrap our app, which is why we pass in the `AppComponent` and the shared `HeroService` that all many of our components will use.
|
||||
|
||||
We export our `AppComponent` as we’ll want to refer to it from our bootstrapping process.
|
||||
|
||||
## Bootstrapping the Tour of Heroes
|
||||
The start-up of an app is also known as bootstrapping. We are currently bootstrapping in our `HeroesComponent`, which no longer makes sense. So let’s change that and separate this startup logic.
|
||||
|
||||
Let’s move the bootstrapping logic into a new file. We’ll create a new file named `boot.ts` in the `app` folder.
|
||||
|
||||
Let’s add the following lines to `boot.ts`:
|
||||
+makeExample('toh-4/ts-snippets/app/bootstrap.pt4.ts')
|
||||
:marked
|
||||
The bootstrap function accepts as its first parameter, the first component that the app will use.
|
||||
|
||||
Now let’s do a little cleanup work. Let’s remove the bootstrap logic and remove `bootstrap` from the import statement in the `heroes.component.ts`.
|
||||
|
||||
We had already exported our `AppComponent`, which is why we can now import it and bootstrap it here. We pass in the shared `HeroService` that many of our components will use, so it will be ready when we need it.
|
||||
|
||||
Now let’s tell our module loader to start by loading our `bootstrap` module. We’ll do this in our`index.html` file
|
||||
+makeExample('toh-4/ts-snippets/app/index.pt4.html','bootstrap')
|
||||
:marked
|
||||
### Viewing our Progress
|
||||
Let’s add a title for our app which we’ll bind to a `title` property on our component. We’ll set the title to “Tour of Heroes”.
|
||||
+makeExample('toh-4/ts-snippets/app/index.pt4.html','title')
|
||||
:marked
|
||||
Our title now belongs in the `AppComponent`, but it also still exists in `HeroesComponent`. So let’s tidy up by removing the `title` from the `HeroesComponent` class and template.
|
||||
|
||||
When we view our app in the browser we should now only see our title of “Tour of Heroes”.
|
||||
But where is the rest of our app? We haven’t shown it yet!
|
||||
|
||||
Our app’s entry point is the `bootstrap` module which loads the `AppComponent`. `AppComponent` in turn only shows a title.
|
||||
|
||||
Our next step is to configure the menu links and routes that will show our views.
|
||||
|
||||
## Adding the Router to our App
|
||||
The Angular router is a separate and distinct module that we can include as needed. Well, our Tour of Heroes app needs routing, so let’s add it!
|
||||
|
||||
### Including the Router
|
||||
We add a script tag referencing the router code. We’ll make sure this comes after the angular script reference.
|
||||
|
||||
Then let’s set our base href to `/src/` since that is where our source code is located. Our `index.html`’s head section should nw look like this:
|
||||
+makeExample('toh-4/ts-snippets/app/index.pt4.html','head')
|
||||
:marked
|
||||
Now we’ll be loading the Angular router!
|
||||
|
||||
### Configuring Routes
|
||||
We want to display a menu that has links to a dashboard and to our list of heroes. Let’s configure the first route to show our `HeroesComponent`.
|
||||
|
||||
Our app will have a few routes and we may want to use them in more than one module. Let’s create a file named `route.config.ts` to host our routes.
|
||||
|
||||
#### Defining Routes
|
||||
We want to show our heroes list. So let’s define our first route to show our `HeroesComponent` template.
|
||||
+makeExample('toh-4/ts-snippets/app/route.config.pt4.ts','first-route')
|
||||
:marked
|
||||
Our route needs a path. This is what will show up in the address bar of the browser. Our path for our `HeroesComponent` will be `/`.
|
||||
|
||||
The `component` property identifies the component we will load when we go to this route. In this case we want to load the `HeroesComponent`.
|
||||
|
||||
The `as` property is what the route is known as. In other words, it is the name of the route.
|
||||
We set these three properties in an object and export that object in an array named `APP_ROUTES`. Right now we have one route. But as we add more routes, this technique of hosting them in a `route.config.ts` file and exporting an array of them will make it easier to manage our routes.
|
||||
|
||||
#### The RouteConfig Decorator
|
||||
Now that we have defined a route, we need to tell Angular where to find it. Let’s go to our `AppComponent` and add the `RouteConfig` decorator the class. We’ll need to import the `RouteConfig` decorator from Angular’s router module, too.
|
||||
|
||||
Now we import the `APP_ROUTES`array of routes that we just created. We’ll pass these into the `RouteConfig` decorator.
|
||||
+makeExample('toh-4/ts-snippets/app/index.pt4.html','routes-title')
|
||||
:marked
|
||||
Our app now has its first route, but we need a place to show the heroes view.
|
||||
|
||||
<!-- Learn more about RouteConfig in the chapter [Router] -->
|
||||
|
||||
### Showing the View with the Router-Outlet
|
||||
Angular knows we have a route when we navigate to `/heroes`. But where does the view show in the HTML? We haven’t told our app how to do that yet!
|
||||
|
||||
Let’s add the `<router-outlet>` directive to the template of `AppComponent`. The `<router-outlet>` is a placeholder that the router uses to place the views. When we go to the `/heroes` route, the router will show the `HeroesComponent` template where we place the `<router-outlet>`.
|
||||
+makeExample('toh-4/ts-snippets/app/index.pt4.html','router-outlet')
|
||||
:marked
|
||||
When need to declare to the `AppComponent` that we are using the `router-outlet` directive. To do this we’ll import a special `ROUTER_DIRECTIVES` array of router specific directives.
|
||||
```
|
||||
import {RouteConfig, ROUTER_DIRECTIVES} from 'angular2/router';
|
||||
```
|
||||
Then we’ll declare them to the component in the `@Component` decorator’s `directives` property.
|
||||
```
|
||||
directives: [ROUTER_DIRECTIVES]
|
||||
```
|
||||
Let’s go view our app in the browser and see where we are. Uh oh, we see an error in the developer console.
|
||||
code-example(language="html").
|
||||
EXCEPTION: No provider for Router! (RouterOutlet -> Router)
|
||||
:marked
|
||||
Angular is warning us that we are using the router and the `router-outlet` but we did not inject the router’s provider. We can fix this by injecting this in the bootstrap module. First we’ll import the `ROUTER_PROVIDERS` from the router module.
|
||||
```
|
||||
import {ROUTER_PROVIDERS} from 'angular2/router';
|
||||
```
|
||||
Then we’ll pass the `ROUTER_PROVIDERS` to the `bootstrap` method.
|
||||
```
|
||||
bootstrap(AppComponent, [ROUTER_PROVIDERS, HeroService]);
|
||||
```
|
||||
Now when we view our app in the browser we see our heroes list!
|
||||
|
||||
## Creating Navigation Links
|
||||
Okay, our Tour of Heroes is not quite where we want it yet. We’ve created the `AppComponent` which hosts the routing and we move the heroes list to the `HeroesComponent`. But to fulfill our requirements we need to create a dashboard and add a way to navigate between the different views. Let’s continue by adding the new dashboard component and the navigation links.
|
||||
|
||||
### Empty Dashboard
|
||||
Routing makes a lot more sense once we have multiple views. We have a heroes view to show but now we need to create our dashboard view. Let’s create the `DashboardComponent`so we can finish creating the navigation between the components.
|
||||
|
||||
Let’s create a super simple dashboard component.
|
||||
+makeExample('toh-4/ts-snippets/app/index.pt4.html','simple-dashboard-component')
|
||||
:marked
|
||||
We’ll come back to the dashboard once we complete routing. For now this will do nicely to help us make sure we can navigate between the `HeroesComponent` and the `DashboardComponent`.
|
||||
|
||||
### Configuring the Dashboard’s Routes
|
||||
Now that we have a component for our dashboard, let’s go configure a route that will take us there.
|
||||
|
||||
We’ll open `route.config.ts` and add another route for the dashboard.
|
||||
|
||||
Our dashboard should be the first thing we see when load our app. So the dashboard route will be the default route of `/` while our heroes will now be accessible via the path `/heroes`.
|
||||
|
||||
We’ll also import the `DashboarComponent` so we can route to it with the dashboard route. And we’ll add the dashboard route to the `APP_ROUTES` export.
|
||||
|
||||
Our `route.config.ts` should now look like the following code:
|
||||
+makeExample('toh-4/ts-snippets/app/route.config.pt4.ts','dashboard-route')
|
||||
:marked
|
||||
Now we two components we can route between and we have defined the routes for both components. Next up, we’ll add navigation links to route between them.
|
||||
|
||||
### Navigation Links
|
||||
Let’s add the navigation links to the`AppComponent`’s template. The Angular router uses a special `router-link` directive to navigate to the routes we defined. We can think of these as links that will navigate to another component using the router.
|
||||
|
||||
We also add a title for our app which we’ll bind to a `title` property on our component.
|
||||
+makeExample('toh-4/ts-snippets/app/index.pt4.html','router-link')
|
||||
:marked
|
||||
The `router-link` is not set to url. We bound the `router-link` to the routes that we specified in the `as` property of our `@RouteConfig`. The `as` is the key that we use to reference the routes.
|
||||
|
||||
#### Reusable Routing Config
|
||||
We just hard-coded the routes that the `router-link` properties are bound. We can do better. We recall that we previously created route configuration in `route.config.ts`. We import its `Routes`.
|
||||
+makeExample('toh-4/ts-snippets/app/index.pt4.html','import-app-routes')
|
||||
:marked
|
||||
And then we use it to initialize a `routes` property on our `AppComponent`.
|
||||
+makeExample('toh-4/ts-snippets/app/app.component.pt4.ts','initialize-routes-property')
|
||||
:marked
|
||||
Then we can simply reference the routes as shown below using our route configuration variables, such as `routes.heroes.as`.
|
||||
+makeExample('toh-4/ts-snippets/app/index.pt4.html','router-link')
|
||||
:marked
|
||||
When we view our app in the browser we are brought directly to our dashboard. We can navigate between the dashboard and the heroes til our hearts are content.
|
||||
|
||||
## Adding the Top Heroes to Our Dashboard
|
||||
Our dashboard view is a bit bland as it only contains a title. Let’s spice it up by adding the top 4 heroes at a glance.
|
||||
<!-- Ward sweep section below -->
|
||||
### Top Heroes Template Content
|
||||
Let’s add the template to our dashboard to show the top four heroes. We’ll use the `ngFor` directive to iterate over a list of heroes (which we have not retrieved yet) and display them. We’ll use `<div>` elements as we’re going to custom style them.
|
||||
+makeExample('toh-4/ts-snippets/app/index.pt4.html','ng-for')
|
||||
:marked
|
||||
We’ve been down this road before. We are using the `ngFor` directive, so we have to declare it in the component. Let’s do that now by first importing `CORE_DIRECTIVES`. (Remember that `CORE_DIRECTIVES` is a convenience array containing the most common directives such as `ngFor`.)
|
||||
```
|
||||
import {Component, CORE_DIRECTIVES} from 'angular2/angular2';
|
||||
```
|
||||
Then we declare the `CORE_DIRECTIVES` to the component.
|
||||
```
|
||||
directives: [CORE_DIRECTIVES]
|
||||
```
|
||||
### Using the Shared HeroService
|
||||
We just iterated over a list of heroes, but we don’t have any heroes in the `DashboardComponent`. We do have a `HeroService` that provides heroes. In fact, we already used this service in the `HeroComponent`. Let’s re-use this same service for the `DashboardComponent` to get a list of heroes.
|
||||
|
||||
We’ll create a `heroes` property in our `DashboardComponent`.
|
||||
```
|
||||
public heroes: Hero[];
|
||||
```
|
||||
And we import the `Hero`
|
||||
```
|
||||
import {Hero} from './hero';
|
||||
```
|
||||
#### Injecting a Service
|
||||
We’ll be needing the `HeroService`, so let’s import it and inject it into our `DashboardComponent`. Here we import it.
|
||||
```
|
||||
import {HeroService} from './hero.service';
|
||||
```
|
||||
And here we inject it into our component’s constructor.
|
||||
```
|
||||
constructor(private _heroService: HeroService) { }
|
||||
```
|
||||
#### Getting the Heroes on Initialization
|
||||
We want our heroes to be loaded when the component is initialized, just like we did in the `HeroesComponent`. We’ll tackle this the same way, by using the onInit Lifecycle hook.
|
||||
|
||||
We can implement the `OnInit` interface and code the `onInit` method.
|
||||
```
|
||||
export class DashboardComponent implements OnInit {
|
||||
```
|
||||
Here we implement the `onInit` method to get our heroes, again just like we did for the `HeroesComponent`.
|
||||
+makeExample('toh-4/ts-snippets/app/app.component.pt4.ts','oninit')
|
||||
:marked
|
||||
Notice we did not have to know how to get the heroes, we just needed to know the method to call from the `HeroService`. This is an advantage of using shared services.
|
||||
|
||||
When we view our app in the browser we see the dashboard light up with all of our heroes. This isn’t exactly what we want so let’s trim that down to the top four heroes.
|
||||
|
||||
### Slicing with Pipes
|
||||
Our requirement is to show the top four heroes. If only we had something that would automatically get a subset of the data. Well, we do! They are called Pipes.
|
||||
|
||||
Angular has various built-in pipes that make formatting and filtering data easy. We’ll take advantage of a pipe named `slice` to get a slice of our heroes array.
|
||||
```
|
||||
<div *ngFor="#hero of heroes | slice:0:4">
|
||||
```
|
||||
After the `ngFor` we added a pipe character and then the `slice` pipe. We tell the `slice` pipe to start at the 0th item in the array and get four items.
|
||||
.l-sub-section
|
||||
:marked
|
||||
Learn more about Pipes in the chapter [Pipes](../guide/pipes.html)
|
||||
:marked
|
||||
When we view our app we now see the first four heroes are displayed.
|
||||
|
||||
### Heroes with Style
|
||||
Our creative designers have added a requirement that the dashboard should show the heroes in a row of rectangles. We’ve written some CSS to achieve this along with some simple media queries to achieve responsive design.
|
||||
|
||||
We could put the CSS in the component, but there are over 30 lines of CSS and it would get crowded fast. Most editors make it easier to code CSS in a *.css file too. Fortunately, we can separate the styles into their own file and reference them.
|
||||
|
||||
#### Adding the Dashboard’s CSS File
|
||||
Let’s create a file to hold the `DashboardComponent`’s CSS. We’ll name it `dashboard.component.css` and put it in the `app` folder.
|
||||
|
||||
Now let’s add the following CSS to the file.
|
||||
+makeExample('toh-4/ts-snippets/app/index.pt4.html','css')
|
||||
:marked
|
||||
We need to reference the file from our component so the styles will be applied properly. Let’s add this reference to the component’s `styleUrls` property.
|
||||
```
|
||||
styleUrls: ['app/dashboard.component.css'],
|
||||
```
|
||||
The `styleUrls` property is an array, which we might guess suggests that we can add multiple styles from different locations. And we would be right! In this case we have one file, but we could add more for our component if needed.
|
||||
|
||||
#### Template Urls
|
||||
While we are at it, let’s move our HTML for the `DashboardComponent` to its own file. We’ll create a file named `dashboard.component.html` in the `app` folder and move the HTML there.
|
||||
We need to reference the the template, so let’s change our component’s `template` property to `templateUrl` and set it to the location of the file.
|
||||
```
|
||||
template: 'app/dashboard.component.html',
|
||||
```
|
||||
Notice we are now using single quotes and not the back-ticks since we only have the need for a single line string.
|
||||
|
||||
#### Applying the Styles to Our Template
|
||||
|
||||
Now that we have some style, let’s take advantage of it by applying it to our template. Our template should now look like this:
|
||||
+makeExample('toh-4/ts-snippets/app/index.pt4.html','template-styled')
|
||||
:marked
|
||||
When we view our app in the browser it now shows our dashboard with our four top heroes in a row.
|
||||
|
||||
## Styling the Navigation Links
|
||||
Our creative design team requested that the navigation links also be styled. Let’s make the navigation links look more like selectable buttons.
|
||||
|
||||
### Defining Styles for Navigation Links
|
||||
Let’s add the following styles to our `AppComponent`’s `styles` property. We’ll define some classes named `router-link` that style the default, active, visited and hover selectors. The active selector changes the color of the link to make it easy to identify which link is selected.
|
||||
+makeExample('toh-4/ts-snippets/app/app.component.pt4.ts','styles')
|
||||
:marked
|
||||
This time we define the styles in our component because there are only a few of them.
|
||||
|
||||
### Applying Styles to the Navigation Links
|
||||
Now that we have styles for our navigation links, let’s apply them in the `AppComponent` template. When we are done, our component will look like the following:
|
||||
+makeExample('toh-4/ts-snippets/app/index.pt4.html','styled-nav-links')
|
||||
:marked
|
||||
#### Where Did the Active Route Come From?
|
||||
The Angular Router makes it easy for us to style our active navigation link. The `router-link-active` class is automatically added to the Router’s active route for us. So all we have to do is define the style for it.
|
||||
|
||||
Sweet! When we view our app in the browser we see the navigation links are now styled, as are our top heroes!
|
||||
figure.image-display
|
||||
img(src='/resources/images/devguide/toh/dashboard-top-heroes.png' alt="View navigations")
|
||||
:marked
|
||||
## Add Route to Hero Details
|
||||
We can navigate between the dashboard and the heroes view, but we have a requirement from our users to be able to select a hero from either of those views and go directly to the selected hero’s details. Let’s configure a route to go directly to the `HeroDetailComponent` passing the hero’s id as a parameter.
|
||||
|
||||
### Configuring a Route with a Parameter
|
||||
We’ve already added a few routes to the `routes.config.ts` file, so it’s natural that we’d start there to add the route to go to the `HeroDetailComponent`. Let’s start by adding the import statement for the component.
|
||||
+makeExample('toh-4/ts-snippets/app/route.config.pt4.ts','route-parameter-import')
|
||||
:marked
|
||||
Now we add a route for the details to the `Routes` object.
|
||||
+makeExample('toh-4/ts-snippets/app/route.config.pt4.ts','route-parameter-detail')
|
||||
:marked
|
||||
The route will lead to the `HeroDetailComponent`, passing along the value for the hero’s id. The routing configuration identifies parameters as parts of the path that are prefixed with a `:` such as `:id`.
|
||||
|
||||
### Receiving a Parameter
|
||||
We want to navigate to the `HeroDetailComponent`, so let’s modify it to accept the `id` parameter. We import the `RouteParams` so we can access the parameter.
|
||||
We assign our `AppComponent` a selector of `my-app`.
|
||||
+makeExample('toh-4/ts-snippets/app/app.component.pt4.ts','import-params')
|
||||
:marked
|
||||
Now we inject the `RouteParams` into the `HeroDetailComponent` constructor.
|
||||
+makeExample('toh-4/ts-snippets/app/app.component.pt4.ts','inject-routeparams')
|
||||
:marked
|
||||
We want to immediately access the parameter, so let’s implement the `OnInit` interface and its `onInit` method.
|
||||
+makeExample('toh-4/ts-snippets/app/app.component.pt4.ts','access-params')
|
||||
:marked
|
||||
And let’s not forget to import `OnInit`.
|
||||
+makeExample('toh-4/ts-snippets/app/app.component.pt4.ts','import-onit')
|
||||
:marked
|
||||
Using the `onInit`method, we can grab the parameter as soon as the component initializes. We’ll access the parameter by name and later we’ll get the hero by its `id`.
|
||||
+makeExample('toh-4/ts-snippets/app/app.component.pt4.ts','onit-id-param')
|
||||
:marked
|
||||
Our `HeroDetailComponent` is already used in the `HeroesComponent`. When we select a hero from the list we are passing the hero object from the list to the `HeroDetailComponent`. We want this component to support that functionality or be able to accept the hero’s id. Let’s revise the logic in the `onInit` to handle this.
|
||||
+makeExample('toh-4/ts-snippets/app/app.component.pt4.ts','onit-hero-id')
|
||||
:marked
|
||||
Our component will first check if it has a hero. If it doesn’t it will then check for the routing parameter so it can get the hero.
|
||||
.l-sub-section
|
||||
:marked
|
||||
Learn more about RouteParams in the chapter [Router](../guide/router.html)
|
||||
:marked
|
||||
Getting the Hero
|
||||
When we pass the id to the `HeroDetailComponent` we need to go get the hero from our `HeroService`. Let’s import the `HeroService` so we can use it to get our hero.
|
||||
+makeExample('toh-4/ts-snippets/app/bootstrap.pt4.ts','import-hero-service')
|
||||
:marked
|
||||
And then we inject the `HeroService` into the constructor.
|
||||
+makeExample('toh-4/ts-snippets/app/app.component.pt4.ts','inject-hero-service')
|
||||
:marked
|
||||
We then stub out the call to the `HeroService` to get the hero by the hero’s id. But wait a second, we don’t have a way to get the hero by id … yet.
|
||||
|
||||
Our `HeroService` is the right place to get a single hero. We’ll create a method named `getHero` that will accept a parameter, find the hero, and return the hero in a promise.
|
||||
|
||||
We add this method to the `HeroService`.
|
||||
+makeExample('toh-4/ts-snippets/app/hero.service.pt4.ts','get-hero-method')
|
||||
:marked
|
||||
Then we go back to our `HeroDetailComponent` and we can call the `getHero` method.
|
||||
+makeExample('toh-4/ts-snippets/app/app.component.pt4.ts','onit-hero-method')
|
||||
:marked
|
||||
We grab the hero and set it to the local `hero` property. Now we have everything in place to receive the parameter.
|
||||
|
||||
### Select a Hero on the Dashboard
|
||||
When a user selects a hero in the dashboard, we want to route to the details. Let’s open our dashboard’s template and add a click event to each hero in the template.
|
||||
+makeExample('toh-4/ts-snippets/app/index.pt4.html','select-hero-click-event')
|
||||
:marked
|
||||
The click event will call the `gotoDetail` method in the `DashboardComponent`. We don’t have that method yet, so let’s create it. We’ll want to use the router to navigate to the details route we created. So we have to import the router, and while we are at it, we’ll import the `Routes` object we created that describe our routes.
|
||||
+makeExample('toh-4/ts-snippets/app/bootstrap.pt4.ts','import-router')
|
||||
:marked
|
||||
Now we can write our method to navigate to the route and pass the parameter. We’ll use the router’s `navigate` method and pass an array that has 2 parameters. The first is the name of the route (the `as` property in the `RouteConfig`). The second is an object with the parameters and values.
|
||||
+makeExample('toh-4/ts-snippets/app/route.config.pt4.ts','router-navigate-method')
|
||||
:marked
|
||||
Now when we view our app in the browser and select a hero from the dashboard, we go directly to the hero’s details!
|
||||
.l-sub-section
|
||||
:marked
|
||||
Learn more about RouteParams in the chapter [Router](../guide/router.html)
|
||||
:marked
|
||||
### Select a Hero on the HeroesComponent
|
||||
When a user selects a hero in the dashboard, we go to the details. But we also want this to happen from the `HeroesComponent`. Let’s add the same changes to the `HeroesComponent` that we made to the dashboard.
|
||||
+makeExample('toh-4/ts-snippets/app/app.component.pt4.ts','select-hero')
|
||||
:marked
|
||||
The requirement here is to show the hero when selected and allow the user to the details via a button. So when a user selects a hero we want to show the hero’s name and provide a button to navigate to the details.
|
||||
|
||||
Let’s open the `HeroesComponent`, remove the `my-hero-detail` component, and change the template to display the hero’s name instead.
|
||||
+makeExample('toh-4/ts-snippets/app/index.pt4.html','display-hero-name')
|
||||
:marked
|
||||
We also added a button with a click event that will call our `gotoDetail` method in our `HeroesComponent`.
|
||||
|
||||
Notice we also used the `uppercase` pipe to format the selected hero’s name. Pipes are extremely helpful at formatting and filtering.
|
||||
.l-sub-section
|
||||
:marked
|
||||
Learn more about Pipes in the chapter [Pipes](../guide/pipes.html)
|
||||
:marked
|
||||
When we view the app in our browser we can now navigate from the dashboard or the heroes component directly to the selected hero’s details!
|
||||
|
||||
### Cleaning Up Templates and Styles
|
||||
We’ve added a lot of HTML and CSS in our template and styles, respectively, in the `HeroesComponent`. Let’s move each of these to their own files.
|
||||
|
||||
We move the HTML for the `HeroesComponent` template to `heroes.component.html`. Then we reference the file in the component’s `templateUrl` property.
|
||||
|
||||
Now our `HeroesComponent` looks much cleaner and easier to maintain since our template and styles are in another file.
|
||||
+makeExample('toh-4/ts-snippets/app/app.component.pt4.ts','reference-heroes-component')
|
||||
:marked
|
||||
We’ll also move the HTML out of the `HeroDetailComponent` and into its own file named `hero-detail.component.html`. Then we reference the file from the `templateUrl` property.
|
||||
+makeExample('toh-4/ts-snippets/app/app.component.pt4.ts','reference-hero-detail-component')
|
||||
:marked
|
||||
### Adding Styles to the App
|
||||
When we add styles to a component we are making it easier to package everything a component needs together. The HTML, the CSS, and the code are all together in one convenient place. However we can also add styles at an app level outside of a component.
|
||||
|
||||
Our designers just gave us a few more basic styles to apply to our entire app. Let’s add some CSS in a file `styles.css` to the `src` folder to style the app’s basic elements.
|
||||
+makeExample('toh-4/ts-snippets/app/index.pt4.html','basic-styles')
|
||||
:marked
|
||||
And let’s reference the stylesheet from the `index.html`.
|
||||
+makeExample('toh-4/ts-snippets/app/index.pt4.html','stylesheet')
|
||||
:marked
|
||||
When we view our app in the browser we can see everything still works as expected!
|
||||
|
||||
### Reviewing the App Structure
|
||||
Let’s verify that we have the following structure after all of our good refactoring in this chapter:
|
||||
|
||||
.filetree
|
||||
.file angular2-tour-of-heroes
|
||||
.children
|
||||
.file node_modules
|
||||
.file app
|
||||
.children
|
||||
.file app.component.ts
|
||||
.file boot.ts
|
||||
.file dashboard.component.css
|
||||
.file dashboard.component.html
|
||||
.file dashboard.component.ts
|
||||
.file hero.ts
|
||||
.file hero-detail.component.html
|
||||
.file hero-detail.component.ts
|
||||
.file hero.service.ts
|
||||
.file heroes.component.css
|
||||
.file heroes.component.html
|
||||
.file heroes.component.ts
|
||||
.file mock-heroes.ts
|
||||
.file index.html
|
||||
.file package.json
|
||||
.file styles.css
|
||||
.file tsconfig.json
|
||||
:marked
|
||||
|
||||
.l-main-section
|
||||
:marked
|
||||
## Recap
|
||||
|
||||
### The Road We’ve Travelled
|
||||
Let’s take stock in what we’ve built.
|
||||
- We added the router to navigate between different components and their templates
|
||||
- We learned how to create router links to represent navigation menu items
|
||||
- We extended a component to either accept a hero as input or accept a router parameter to get the hero
|
||||
- We extended our shared service by adding a new method to it
|
||||
- We added the `slice` pipe to filter the top heroes, and the `uppercase` pipe to format data
|
||||
|
||||
### The Road Ahead
|
||||
Our Tour of Heroes has grown to reuse services, share components, route between components and their templates, and filter and format data with pipes. We have many of the foundations to build an application. In the next chapter we’ll explore how to replace our mock data with real data using http.
|
Loading…
x
Reference in New Issue
Block a user