This commit is contained in:
@@ -1,39 +0,0 @@
|
||||
// #docregion show-hero
|
||||
template: '<h1>{{title}}</h1><h2>{{hero}} details!</h2>'
|
||||
// #enddocregion show-hero
|
||||
|
||||
// #docregion show-hero-2
|
||||
template: '<h1>{{title}}</h1><h2>{{hero.name}} details!</h2>'
|
||||
// #enddocregion show-hero-2
|
||||
|
||||
// #docregion show-hero-properties
|
||||
template: '<h1>{{title}}</h1><h2>{{hero.name}} details!</h2><div><label>id: </label>{{hero.id}}</div><div><label>name: </label>{{hero.name}}</div>'
|
||||
// #enddocregion show-hero-properties
|
||||
|
||||
// #docregion multi-line-strings
|
||||
template:`
|
||||
<h1>{{title}}</h1>
|
||||
<h2>{{hero.name}} details!</h2>
|
||||
<div><label>id: </label>{{hero.id}}</div>
|
||||
<div><label>name: </label>{{hero.name}}</div>
|
||||
`
|
||||
// #enddocregion multi-line-strings
|
||||
|
||||
// #docregion editing-Hero
|
||||
template:`
|
||||
<h1>{{title}}</h1>
|
||||
<h2>{{hero.name}} details!</h2>
|
||||
<div><label>id: </label>{{hero.id}}</div>
|
||||
<div>
|
||||
<label>name: </label>
|
||||
<input value="{{hero.name}}" placeholder="name">
|
||||
</div>
|
||||
`
|
||||
// #enddocregion editing-Hero
|
||||
|
||||
// #docregion app-component-1
|
||||
export class AppComponent {
|
||||
title = 'Tour of Heroes';
|
||||
hero = 'Windstorm';
|
||||
}
|
||||
// #enddocregion app-component-1
|
||||
@@ -0,0 +1,44 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
let t = {
|
||||
// #docregion show-hero
|
||||
template: `<h1>{{title}}</h1><h2>{{hero}} details!</h2>`
|
||||
// #enddocregion show-hero
|
||||
};
|
||||
|
||||
t = {
|
||||
// #docregion show-hero-2
|
||||
template: `<h1>{{title}}</h1><h2>{{hero.name}} details!</h2>`
|
||||
// #enddocregion show-hero-2
|
||||
};
|
||||
|
||||
t = {
|
||||
// #docregion multi-line-strings
|
||||
template: `
|
||||
<h1>{{title}}</h1>
|
||||
<h2>{{hero.name}} details!</h2>
|
||||
<div><label>id: </label>{{hero.id}}</div>
|
||||
<div><label>name: </label>{{hero.name}}</div>
|
||||
`
|
||||
// #enddocregion multi-line-strings
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
// #docregion name-input
|
||||
<div>
|
||||
<label>name: </label>
|
||||
<input [(ngModel)]="hero.name" placeholder="name">
|
||||
</div>
|
||||
// #enddocregion name-input
|
||||
*/
|
||||
|
||||
/////////////////
|
||||
|
||||
@Component(t)
|
||||
// #docregion app-component-1
|
||||
export class AppComponent {
|
||||
title = 'Tour of Heroes';
|
||||
hero = 'Windstorm';
|
||||
}
|
||||
// #enddocregion app-component-1
|
||||
@@ -3,7 +3,8 @@
|
||||
"basePath": "src/",
|
||||
"files":[
|
||||
"!**/*.d.ts",
|
||||
"!**/*.js"
|
||||
"!**/*.js",
|
||||
"!**/*.[1].*"
|
||||
],
|
||||
"tags": ["tutorial", "tour", "heroes"]
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// #docregion pt1
|
||||
// #docregion
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
// #docregion hero-class-1
|
||||
@@ -10,6 +10,7 @@ export class Hero {
|
||||
|
||||
@Component({
|
||||
selector: 'my-app',
|
||||
// #docregion editing-Hero
|
||||
template: `
|
||||
<h1>{{title}}</h1>
|
||||
<h2>{{hero.name}} details!</h2>
|
||||
@@ -19,6 +20,7 @@ export class Hero {
|
||||
<input [(ngModel)]="hero.name" placeholder="name">
|
||||
</div>
|
||||
`
|
||||
// #enddocregion editing-Hero
|
||||
})
|
||||
export class AppComponent {
|
||||
title = 'Tour of Heroes';
|
||||
@@ -29,4 +31,3 @@ export class AppComponent {
|
||||
};
|
||||
// #enddocregion hero-property-1
|
||||
}
|
||||
// #enddocregion pt1
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
// #docregion
|
||||
import { NgModule } from '@angular/core';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { FormsModule } from '@angular/forms'; // <-- NgModel lives here
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
BrowserModule,
|
||||
FormsModule
|
||||
FormsModule // <-- import the FormsModule before binding with [(ngModel)]
|
||||
],
|
||||
declarations: [
|
||||
AppComponent
|
||||
|
||||
@@ -128,6 +128,6 @@ function getPageElts() {
|
||||
return {
|
||||
heroes: element.all(by.css('my-app li')),
|
||||
selected: element(by.css('my-app li.selected')),
|
||||
heroDetail: element(by.css('my-app > div, my-app > my-hero-detail > div'))
|
||||
heroDetail: element(by.css('my-app > div, my-app > hero-detail > div'))
|
||||
};
|
||||
}
|
||||
|
||||
+21
-21
@@ -1,34 +1,34 @@
|
||||
// #docregion ng-for
|
||||
<!-- #docregion ng-for -->
|
||||
<li *ngFor="let hero of heroes">
|
||||
<span class="badge">{{hero.id}}</span> {{hero.name}}
|
||||
</li>
|
||||
// #enddocregion ng-for
|
||||
<!-- #enddocregion ng-for -->
|
||||
|
||||
// #docregion heroes-styled
|
||||
<!-- #docregion heroes-styled -->
|
||||
<h2>My Heroes</h2>
|
||||
<ul class="heroes">
|
||||
<li *ngFor="let hero of heroes">
|
||||
<span class="badge">{{hero.id}}</span> {{hero.name}}
|
||||
</li>
|
||||
</ul>
|
||||
// #enddocregion heroes-styled
|
||||
<!-- #enddocregion heroes-styled -->
|
||||
|
||||
// #docregion selectedHero-click
|
||||
<!-- #docregion selectedHero-click -->
|
||||
<li *ngFor="let hero of heroes" (click)="onSelect(hero)">
|
||||
<span class="badge">{{hero.id}}</span> {{hero.name}}
|
||||
...
|
||||
</li>
|
||||
// #enddocregion selectedHero-click
|
||||
<!-- #enddocregion selectedHero-click -->
|
||||
|
||||
// #docregion selectedHero-details
|
||||
<!-- #docregion selectedHero-details -->
|
||||
<h2>{{selectedHero.name}} details!</h2>
|
||||
<div><label>id: </label>{{selectedHero.id}}</div>
|
||||
<div>
|
||||
<label>name: </label>
|
||||
<input [(ngModel)]="selectedHero.name" placeholder="name"/>
|
||||
</div>
|
||||
// #enddocregion selectedHero-details
|
||||
<!-- #enddocregion selectedHero-details -->
|
||||
|
||||
// #docregion ng-if
|
||||
<!-- #docregion ng-if -->
|
||||
<div *ngIf="selectedHero">
|
||||
<h2>{{selectedHero.name}} details!</h2>
|
||||
<div><label>id: </label>{{selectedHero.id}}</div>
|
||||
@@ -37,33 +37,33 @@
|
||||
<input [(ngModel)]="selectedHero.name" placeholder="name"/>
|
||||
</div>
|
||||
</div>
|
||||
// #enddocregion ng-if
|
||||
<!-- #enddocregion ng-if -->
|
||||
|
||||
// #docregion hero-array-1
|
||||
<!-- #docregion hero-array-1 -->
|
||||
heroes = HEROES;
|
||||
// #enddocregion hero-array-1
|
||||
<!-- #enddocregion hero-array-1 -->
|
||||
|
||||
// #docregion heroes-template-1
|
||||
<!-- #docregion heroes-template-1 -->
|
||||
<h2>My Heroes</h2>
|
||||
<ul class="heroes">
|
||||
<li>
|
||||
<!-- each hero goes here -->
|
||||
</li>
|
||||
</ul>
|
||||
// #enddocregion heroes-template-1
|
||||
<!-- #enddocregion heroes-template-1 -->
|
||||
|
||||
// #docregion heroes-ngfor-1
|
||||
<!-- #docregion heroes-ngfor-1 -->
|
||||
<li *ngFor="let hero of heroes">
|
||||
// #enddocregion heroes-ngfor-1
|
||||
<!-- #enddocregion heroes-ngfor-1 -->
|
||||
|
||||
// #docregion class-selected-1
|
||||
<!-- #docregion class-selected-1 -->
|
||||
[class.selected]="hero === selectedHero"
|
||||
// #enddocregion class-selected-1
|
||||
<!-- #enddocregion class-selected-1 -->
|
||||
|
||||
// #docregion class-selected-2
|
||||
<!-- #docregion class-selected-2 -->
|
||||
<li *ngFor="let hero of heroes"
|
||||
[class.selected]="hero === selectedHero"
|
||||
(click)="onSelect(hero)">
|
||||
<span class="badge">{{hero.id}}</span> {{hero.name}}
|
||||
</li>
|
||||
// #enddocregion class-selected-2
|
||||
<!-- #enddocregion class-selected-2 -->
|
||||
@@ -3,7 +3,8 @@
|
||||
"basePath": "src/",
|
||||
"files":[
|
||||
"!**/*.d.ts",
|
||||
"!**/*.js"
|
||||
"!**/*.js",
|
||||
"!**/*.[1].*"
|
||||
],
|
||||
"tags": ["tutorial", "tour", "heroes"]
|
||||
}
|
||||
|
||||
@@ -128,6 +128,6 @@ function getPageElts() {
|
||||
return {
|
||||
heroes: element.all(by.css('my-app li')),
|
||||
selected: element(by.css('my-app li.selected')),
|
||||
heroDetail: element(by.css('my-app > div, my-app > my-hero-detail > div'))
|
||||
heroDetail: element(by.css('my-app > div, my-app > hero-detail > div'))
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
<h1>{{title}}</h1>
|
||||
<h2>My Heroes</h2>
|
||||
<ul class="heroes">
|
||||
<li *ngFor="let hero of heroes"
|
||||
[class.selected]="hero === selectedHero"
|
||||
(click)="onSelect(hero)">
|
||||
<span class="badge">{{hero.id}}</span> {{hero.name}}
|
||||
</li>
|
||||
</ul>
|
||||
<!-- #docregion hero-detail-binding -->
|
||||
<hero-detail [hero]="selectedHero"></hero-detail>
|
||||
<!-- #enddocregion hero-detail-binding -->
|
||||
@@ -0,0 +1,35 @@
|
||||
// #docplaster
|
||||
// #docregion v1
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
// #enddocregion v1
|
||||
// #docregion hero-import
|
||||
import { Hero } from './hero';
|
||||
// #enddocregion hero-import
|
||||
|
||||
// #docregion v1
|
||||
@Component({
|
||||
selector: 'hero-detail',
|
||||
// #enddocregion v1
|
||||
// #docregion template
|
||||
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>
|
||||
`
|
||||
// #enddocregion template
|
||||
// #docregion v1
|
||||
})
|
||||
export class HeroDetailComponent {
|
||||
// #enddocregion v1
|
||||
// #docregion hero
|
||||
hero: Hero;
|
||||
// #enddocregion hero
|
||||
// #docregion v1
|
||||
}
|
||||
// #enddocregion v1
|
||||
@@ -3,7 +3,8 @@
|
||||
"basePath": "src/",
|
||||
"files":[
|
||||
"!**/*.d.ts",
|
||||
"!**/*.js"
|
||||
"!**/*.js",
|
||||
"!**/*.[1].*"
|
||||
],
|
||||
"tags": ["tutorial", "tour", "heroes"]
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ const HEROES: Hero[] = [
|
||||
<span class="badge">{{hero.id}}</span> {{hero.name}}
|
||||
</li>
|
||||
</ul>
|
||||
<my-hero-detail [hero]="selectedHero"></my-hero-detail>
|
||||
<hero-detail [hero]="selectedHero"></hero-detail>
|
||||
`,
|
||||
// #enddocregion hero-detail-template
|
||||
styles: [`
|
||||
|
||||
@@ -3,22 +3,22 @@ import { NgModule } from '@angular/core';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
import { AppComponent } from './app.component';
|
||||
// #docregion hero-detail-import
|
||||
import { HeroDetailComponent } from './hero-detail.component';
|
||||
// #enddocregion hero-detail-import
|
||||
|
||||
// #docregion declarations
|
||||
@NgModule({
|
||||
imports: [
|
||||
BrowserModule,
|
||||
FormsModule
|
||||
],
|
||||
// #docregion declarations
|
||||
declarations: [
|
||||
AppComponent,
|
||||
HeroDetailComponent
|
||||
],
|
||||
// #enddocregion declarations
|
||||
bootstrap: [ AppComponent ]
|
||||
})
|
||||
export class AppModule { }
|
||||
// #enddocregion declarations
|
||||
|
||||
@@ -1,18 +1,12 @@
|
||||
// #docplaster
|
||||
// #docregion
|
||||
// #docregion v1
|
||||
// #docregion import-input
|
||||
import { Component, Input } from '@angular/core';
|
||||
// #enddocregion import-input
|
||||
|
||||
// #enddocregion v1
|
||||
// #docregion hero-import
|
||||
import { Hero } from './hero';
|
||||
// #enddocregion hero-import
|
||||
|
||||
// #docregion v1
|
||||
// #docregion template
|
||||
@Component({
|
||||
selector: 'my-hero-detail',
|
||||
// #enddocregion v1
|
||||
// #docregion template
|
||||
selector: 'hero-detail',
|
||||
template: `
|
||||
<div *ngIf="hero">
|
||||
<h2>{{hero.name}} details!</h2>
|
||||
@@ -23,17 +17,13 @@ import { Hero } from './hero';
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
// #enddocregion template
|
||||
// #docregion v1
|
||||
})
|
||||
// #enddocregion template
|
||||
// #docregion class
|
||||
export class HeroDetailComponent {
|
||||
// #enddocregion v1
|
||||
// #docregion hero-input
|
||||
@Input()
|
||||
// #docregion hero
|
||||
hero: Hero;
|
||||
@Input() hero: Hero;
|
||||
// #enddocregion hero
|
||||
// #enddocregion hero-input
|
||||
// #docregion v1
|
||||
}
|
||||
// #enddocregion v1
|
||||
// #docregion class
|
||||
|
||||
|
||||
@@ -128,6 +128,6 @@ function getPageElts() {
|
||||
return {
|
||||
heroes: element.all(by.css('my-app li')),
|
||||
selected: element(by.css('my-app li.selected')),
|
||||
heroDetail: element(by.css('my-app > div, my-app > my-hero-detail > div'))
|
||||
heroDetail: element(by.css('my-app > div, my-app > hero-detail > div'))
|
||||
};
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ import { HeroService } from './hero.service.2';
|
||||
<div *ngFor="let hero of heroes" (click)="onSelect(hero)">
|
||||
{{hero.name}}
|
||||
</div>
|
||||
<my-hero-detail [hero]="selectedHero"></my-hero-detail>
|
||||
<hero-detail [hero]="selectedHero"></hero-detail>
|
||||
`,
|
||||
// #docregion providers
|
||||
providers: [HeroService]
|
||||
|
||||
@@ -20,7 +20,7 @@ import { HeroService } from './hero.service';
|
||||
<span class="badge">{{hero.id}}</span> {{hero.name}}
|
||||
</li>
|
||||
</ul>
|
||||
<my-hero-detail [hero]="selectedHero"></my-hero-detail>
|
||||
<hero-detail [hero]="selectedHero"></hero-detail>
|
||||
`,
|
||||
// #enddocregion template
|
||||
styles: [`
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Component, Input } from '@angular/core';
|
||||
import { Hero } from './hero';
|
||||
|
||||
@Component({
|
||||
selector: 'my-hero-detail',
|
||||
selector: 'hero-detail',
|
||||
template: `
|
||||
<div *ngIf="hero">
|
||||
<h2>{{hero.name}} details!</h2>
|
||||
|
||||
@@ -57,7 +57,7 @@ describe('Tutorial part 5', () => {
|
||||
selectedHero: element(by.css('my-app li.selected')),
|
||||
selectedHeroSubview: element(by.css('my-app my-heroes > div')),
|
||||
|
||||
heroDetail: element(by.css('my-app my-hero-detail > div'))
|
||||
heroDetail: element(by.css('my-app hero-detail > div'))
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import { Hero } from './hero';
|
||||
import { HeroService } from './hero.service';
|
||||
// #docregion metadata
|
||||
@Component({
|
||||
selector: 'my-hero-detail',
|
||||
selector: 'hero-detail',
|
||||
templateUrl: './hero-detail.component.html',
|
||||
// #enddocregion metadata, v2
|
||||
styleUrls: [ './hero-detail.component.css' ]
|
||||
|
||||
@@ -63,7 +63,7 @@ describe('Tutorial part 6', () => {
|
||||
selectedHero: element(by.css('my-app li.selected')),
|
||||
selectedHeroSubview: element(by.css('my-app my-heroes > div:last-child')),
|
||||
|
||||
heroDetail: element(by.css('my-app my-hero-detail > div')),
|
||||
heroDetail: element(by.css('my-app hero-detail > div')),
|
||||
|
||||
searchBox: element(by.css('#search-box')),
|
||||
searchResults: element.all(by.css('.search-result'))
|
||||
|
||||
@@ -8,7 +8,7 @@ import { Hero } from './hero';
|
||||
import { HeroService } from './hero.service';
|
||||
|
||||
@Component({
|
||||
selector: 'my-hero-detail',
|
||||
selector: 'hero-detail',
|
||||
templateUrl: './hero-detail.component.html',
|
||||
styleUrls: [ './hero-detail.component.css' ]
|
||||
})
|
||||
|
||||
@@ -865,7 +865,7 @@ a#heroes-functionality
|
||||
- Delete the `selector` (routed components don't need them).
|
||||
- Delete the `<h1>`.
|
||||
- Relabel the `<h2>` to `<h2>HEROES</h2>`.
|
||||
- Delete the `<my-hero-detail>` at the bottom of the template.
|
||||
- Delete the `<hero-detail>` at the bottom of the template.
|
||||
- Rename the `AppComponent` class to `HeroListComponent`.
|
||||
- Copy the `hero-detail.component.ts` and the `hero.service.ts` files into the `heroes` subfolder.
|
||||
- Create a (pre-routing) `heroes.module.ts` in the heroes folder that looks like this:
|
||||
|
||||
@@ -68,6 +68,8 @@ a(id='toc')
|
||||
to all components, services, and other symbols.
|
||||
This helps make the app cleaner, easier to read and maintain, and more testable.
|
||||
|
||||
a#rule-of-one
|
||||
:marked
|
||||
### <a id="01-01"></a>_Rule of One_
|
||||
#### <a href="#01-01">Style 01-01</a>
|
||||
.s-rule.do
|
||||
|
||||
@@ -7,33 +7,33 @@
|
||||
},
|
||||
"toh-pt1": {
|
||||
"title": "The Hero Editor",
|
||||
"intro": "We build a simple hero editor.",
|
||||
"intro": "Build a simple hero editor.",
|
||||
"nextable": true
|
||||
},
|
||||
"toh-pt2": {
|
||||
"title": "Master/Detail",
|
||||
"intro": "We build a master/detail page with a list of heroes.",
|
||||
"intro": "Build a master/detail page with a list of heroes.",
|
||||
"nextable": true
|
||||
},
|
||||
"toh-pt3": {
|
||||
"title": "Multiple Components",
|
||||
"intro": "We refactor the master/detail view into separate components.",
|
||||
"intro": "Refactor the master/detail view into separate components.",
|
||||
"nextable": true
|
||||
},
|
||||
"toh-pt4": {
|
||||
"title": "Services",
|
||||
"intro": "We create a reusable service to manage our hero data calls.",
|
||||
"intro": "Create a reusable service to manage the hero data calls.",
|
||||
"nextable": true
|
||||
},
|
||||
"toh-pt5": {
|
||||
"title": "Routing",
|
||||
"intro": "We add the Angular Router and learn to navigate among the views.",
|
||||
"intro": "Add the Angular component router and learn to navigate among the views.",
|
||||
"nextable": true
|
||||
},
|
||||
"toh-pt6": {
|
||||
"title": "HTTP",
|
||||
"subtitle": "Getting and saving data",
|
||||
"intro": "We convert our service and components to use Angular's HTTP service.",
|
||||
"intro": "Convert the service and components to use Angular's HTTP service.",
|
||||
"nextable": true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,80 +2,82 @@ block includes
|
||||
include ../_util-fns
|
||||
|
||||
:marked
|
||||
Our grand plan for this tutorial is to build an app to help a staffing agency manage its stable of heroes.
|
||||
Even heroes need to find work.
|
||||
The grand plan for this tutorial is to build an app that helps a staffing agency manage its stable of heroes.
|
||||
|
||||
Of course we'll only make a little progress in this tutorial. What we do build will
|
||||
have many of the features we expect to find in a full-blown, data-driven application: acquiring and displaying
|
||||
The Tour of Heroes app covers the core fundamentals of Angular. You'll build a basic app that
|
||||
has many of the features you'd expect to find in a full-blown, data-driven app: acquiring and displaying
|
||||
a list of heroes, editing a selected hero's detail, and navigating among different
|
||||
views of heroic data.
|
||||
|
||||
The Tour of Heroes covers the core fundamentals of Angular.
|
||||
We’ll use built-in directives to show/hide elements and display lists of hero data.
|
||||
We’ll create a component to display hero details and another to show an array of heroes.
|
||||
We'll use one-way data binding for read-only data. We'll add editable fields to update a model
|
||||
with two-way data binding. We'll bind component methods to user events like key strokes and clicks.
|
||||
We’ll learn to select a hero from a master list and edit that hero in the details view. We'll
|
||||
format data with pipes. We'll create a shared service to assemble our heroes. And we'll use routing to navigate among different views and their components.
|
||||
You'll use built-in directives to show and hide elements and display lists of hero data.
|
||||
You'll create components to display hero details and show an array of heroes.
|
||||
You'll use one-way data binding for read-only data. You'll add editable fields to update a model
|
||||
with two-way data binding. You'll bind component methods to user events, like keystrokes and clicks.
|
||||
You'll enable users to select a hero from a master list and edit that hero in the details view. You'll
|
||||
format data with pipes. You'll create a shared service to assemble the heroes.
|
||||
And you'll use routing to navigate among different views and their components.
|
||||
<!-- CF: Should this be a bullet list? -->
|
||||
|
||||
We’ll learn enough core Angular to get started and gain confidence that
|
||||
Angular can do whatever we need it to do.
|
||||
We'll be covering a lot of ground at an introductory level but we’ll find plenty of links
|
||||
to chapters with greater depth.
|
||||
You'll learn enough core Angular to get started and gain confidence that
|
||||
Angular can do whatever you need it to do.
|
||||
You'll cover a lot of ground at an introductory level, and you'll find many links
|
||||
to pages with greater depth.
|
||||
|
||||
When you're done with this tutorial, the app will look like this <live-example name="toh-6"></live-example>.
|
||||
|
||||
Run the <live-example name="toh-6"></live-example>.
|
||||
|
||||
.l-main-section
|
||||
:marked
|
||||
## The End Game
|
||||
## The end game
|
||||
|
||||
Here's a visual idea of where we're going in this tour, beginning with the "Dashboard"
|
||||
view and our most heroic heroes:
|
||||
Here's a visual idea of where this tutorial leads, beginning with the "Dashboard"
|
||||
view and the most heroic heroes:
|
||||
|
||||
figure.image-display
|
||||
img(src='/resources/images/devguide/toh/heroes-dashboard-1.png' alt="Output of heroes dashboard")
|
||||
|
||||
:marked
|
||||
Above the dashboard are two links ("Dashboard" and "Heroes").
|
||||
We could click them to navigate between this Dashboard and a Heroes view.
|
||||
You can click the two links above the dashboard ("Dashboard" and "Heroes")
|
||||
to navigate between this Dashboard view and a Heroes view.
|
||||
|
||||
Instead we click the dashboard hero named "Magneta" and the router takes us to a "Hero Details" view
|
||||
of that hero where we can change the hero's name.
|
||||
If you click the dashboard hero "Magneta," the router opens a "Hero Details" view
|
||||
where you can change the hero's name.
|
||||
|
||||
figure.image-display
|
||||
img(src='/resources/images/devguide/toh/hero-details-1.png' alt="Details of hero in app")
|
||||
|
||||
:marked
|
||||
Clicking the "Back" button would return us to the "Dashboard".
|
||||
Links at the top can take us to either of the main views.
|
||||
We'll click "Heroes". The app takes to the "Heroes" master list view.
|
||||
Clicking the "Back" button returns you to the Dashboard.
|
||||
Links at the top take you to either of the main views.
|
||||
If you click "Heroes," the app displays the "Heroes" master list view.
|
||||
|
||||
figure.image-display
|
||||
img(src='/resources/images/devguide/toh/heroes-list-2.png' alt="Output of heroes list app")
|
||||
// CF: The ability to add heroes isn't shown in the images or discussed in this page. Should that be added?
|
||||
|
||||
:marked
|
||||
We click a different hero and the readonly mini-detail beneath the list reflects our new choice.
|
||||
When you click a different hero name, the read-only mini detail beneath the list reflects the new choice.
|
||||
|
||||
We click the "View Details" button to drill into the
|
||||
editable details of our selected hero.
|
||||
You can click the "View Details" button to drill into the
|
||||
editable details of the selected hero.
|
||||
|
||||
The following diagram captures all of our navigation options.
|
||||
The following diagram captures all of the navigation options.
|
||||
|
||||
figure.image-display
|
||||
img(src='/resources/images/devguide/toh/nav-diagram.png' alt="View navigations")
|
||||
|
||||
:marked
|
||||
Here's our app in action
|
||||
Here's the app in action:
|
||||
|
||||
figure.image-display
|
||||
img(src='/resources/images/devguide/toh/toh-anim.gif' alt="Tour of Heroes in Action")
|
||||
|
||||
.l-main-section
|
||||
:marked
|
||||
## Up Next
|
||||
## Up next
|
||||
|
||||
We’ll build this Tour of Heroes together, step by step.
|
||||
We'll motivate each step with a requirement that we've
|
||||
met in countless applications. Everything has a reason.
|
||||
You'll build the Tour of Heroes app, step by step.
|
||||
Each step is motivated with a requirement that you've likely
|
||||
met in many applications. Everything has a reason.
|
||||
|
||||
And we’ll meet many of the core fundamentals of Angular along the way.
|
||||
Along the way, you'll become familiar with many of the core fundamentals of Angular.
|
||||
|
||||
@@ -2,11 +2,10 @@ include ../_util-fns
|
||||
|
||||
:marked
|
||||
## Setup to develop locally
|
||||
Real application development takes place in a local development environment like your machine.
|
||||
|
||||
Follow the [setup](../guide/setup.html) instructions for creating a new project
|
||||
named <ngio-ex path="angular-tour-of-heroes"></ngio-ex>
|
||||
after which the file structure should look like this:
|
||||
named <ngio-ex path="angular-tour-of-heroes"></ngio-ex>.
|
||||
|
||||
The file structure should look like this:
|
||||
|
||||
.filetree
|
||||
.file angular-tour-of-heroes
|
||||
@@ -22,172 +21,159 @@ include ../_util-fns
|
||||
.file styles.css
|
||||
.file systemjs.config.js
|
||||
.file tsconfig.json
|
||||
.file node_modules ...
|
||||
.file node_modules ...
|
||||
.file package.json
|
||||
:marked
|
||||
When we're done with this first episode, the app runs like this <live-example></live-example>.
|
||||
When you're done with this page, the app should look like this <live-example></live-example>.
|
||||
|
||||
a#keep-transpiling
|
||||
:marked
|
||||
## Keep the app transpiling and running
|
||||
We want to start the TypeScript compiler, have it watch for changes, and start our server.
|
||||
Do this by entering the following command in the terminal window.
|
||||
|
||||
Enter the following command in the terminal window:
|
||||
|
||||
code-example(language="sh" class="code-shell").
|
||||
npm start
|
||||
|
||||
:marked
|
||||
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.
|
||||
This command runs the TypeScript compiler in "watch mode", recompiling automatically when the code changes.
|
||||
The command simultaneously launches the app in a browser and refreshes the browser when the code changes.
|
||||
|
||||
You can keep building the Tour of Heroes without pausing to recompile or refresh the browser.
|
||||
|
||||
.l-main-section
|
||||
:marked
|
||||
## Show our Hero
|
||||
We want to display Hero data in our app
|
||||
## Show the hero
|
||||
Add two properties to the `AppComponent`: a `title` property for the app name and a `hero` property
|
||||
for a hero named "Windstorm."
|
||||
|
||||
Update the `AppComponent` so it has two properties: a `title` property for the application name and a `hero` property
|
||||
for a hero named "Windstorm".
|
||||
|
||||
+makeExample('toh-1/ts-snippets/app.component.snippets.pt1.ts', 'app-component-1', 'src/app/app.component.ts (AppComponent class)')(format=".")
|
||||
+makeExample('toh-1/ts/app/app.component.1.ts', 'app-component-1', 'app.component.ts (AppComponent class)')(format=".")
|
||||
|
||||
:marked
|
||||
Now update the template in the `@Component` decoration with data bindings to these new properties.
|
||||
Now update the template in the `@Component` decorator with data bindings to these new properties.
|
||||
|
||||
+makeExample('toh-1/ts-snippets/app.component.snippets.pt1.ts', 'show-hero')
|
||||
+makeExample('toh-1/ts/app/app.component.1.ts', 'show-hero', 'app.component.ts (@Component)')(format='.')
|
||||
|
||||
:marked
|
||||
The browser should refresh and display our title and hero.
|
||||
The browser refreshes and displays the title and hero name.
|
||||
|
||||
The double curly braces are Angular's *interpolation binding* syntax.
|
||||
These interpolation bindings present the component's `title` and `hero` property values,
|
||||
as strings, inside the HTML header tags.
|
||||
|
||||
The double curly braces tell our app to read the `title` and `hero` properties from the component and render them.
|
||||
This is the "interpolation" form of one-way data binding.
|
||||
.l-sub-section
|
||||
:marked
|
||||
Learn more about interpolation in the [Displaying Data chapter](../guide/displaying-data.html).
|
||||
Read more about interpolation in the [Displaying Data](../guide/displaying-data.html) page.
|
||||
|
||||
:marked
|
||||
### Hero object
|
||||
|
||||
At the moment, our hero is just a name. Our hero needs more properties.
|
||||
Let's convert the `hero` from a literal string to a class.
|
||||
The hero needs more properties.
|
||||
Convert the `hero` from a literal string to a class.
|
||||
|
||||
Create a `Hero` class with `id` and `name` properties.
|
||||
For now put this near the top of the `app.component.ts` file, just below the import statement.
|
||||
Add these properties near the top of the `app.component.ts` file, just below the import statement.
|
||||
|
||||
+makeExample('toh-1/ts/src/app/app.component.ts', 'hero-class-1', 'src/app/app.component.ts (Hero class)')(format=".")
|
||||
|
||||
:marked
|
||||
Now that we have a `Hero` class, 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".
|
||||
In the `Hero` class, refactor the 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/src/app/app.component.ts', 'hero-property-1', 'src/app/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.
|
||||
Because you changed the hero from a string to an object,
|
||||
update the binding in the template to refer to the hero's `name` property.
|
||||
|
||||
+makeExample('toh-1/ts-snippets/app.component.snippets.pt1.ts', 'show-hero-2')
|
||||
+makeExample('toh-1/ts/app/app.component.1.ts', 'show-hero-2')
|
||||
:marked
|
||||
The browser refreshes and continues to display our hero’s name.
|
||||
The browser refreshes and continues to display the hero's name.
|
||||
|
||||
### Adding more HTML
|
||||
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`.
|
||||
### Adding HTML with multi-line template strings
|
||||
|
||||
+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.
|
||||
To show all of the hero's properties,
|
||||
add a `<div>` for the hero's `id` property and another `<div>` for the hero's `name`.
|
||||
To keep the template readable, place each `<div>` on its own line.
|
||||
|
||||
### Multi-line template strings
|
||||
The backticks around the component template let you put the `<h1>`, `<h2>`, and `<div>` elements on their own lines,
|
||||
thanks to the <i>template literals</i> feature in ES2015 and TypeScript. For more information, see
|
||||
<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals" target="_blank" title="template literal">Template literals</a>.
|
||||
|
||||
We could make a more readable template with string concatenation
|
||||
but that gets ugly fast, it is harder to read, and
|
||||
it is easy to make a spelling error. Instead,
|
||||
let’s take advantage of the template strings feature
|
||||
in ES2015 and TypeScript to maintain our sanity.
|
||||
|
||||
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-snippets/app.component.snippets.pt1.ts', 'multi-line-strings', 'src/app/app.component.ts (AppComponent\'s template)')
|
||||
|
||||
.callout.is-important
|
||||
header A back-tick is not a single quote
|
||||
:marked
|
||||
**Be careful!** A back-tick (`) looks a lot like a single quote (').
|
||||
It's actually a completely different character.
|
||||
Back-ticks can do more than demarcate a string.
|
||||
Here we use them in a limited way to spread the template over multiple lines.
|
||||
Everything between the back-ticks at the beginning and end of the template
|
||||
is part of a single template string.
|
||||
+makeExample('toh-1/ts/app/app.component.1.ts', 'multi-line-strings', 'app.component.ts (AppComponent\'s template)')(format='.')
|
||||
|
||||
.l-main-section
|
||||
:marked
|
||||
## Editing Our Hero
|
||||
## Edit the hero name
|
||||
|
||||
We want to be able to edit the hero name in a textbox.
|
||||
Users should be able to edit the hero name in an `<input>` textbox.
|
||||
The textbox should both _display_ the hero's `name` property
|
||||
and _update_ that property as the user types.
|
||||
|
||||
Refactor the hero name `<label>` with `<label>` and `<input>` elements as shown below:
|
||||
You need a two-way binding between the `<input>` form element and the `hero.name` property.
|
||||
|
||||
### Two-way binding
|
||||
|
||||
Refactor the hero name in the template so it looks like this:
|
||||
+makeExample('toh-1/ts/app/app.component.1.ts', 'name-input')(format='.')
|
||||
|
||||
+makeExample('toh-1/ts-snippets/app.component.snippets.pt1.ts', 'editing-Hero', 'src/appapp.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>`.
|
||||
`[(ngModel)]` is the Angular syntax to bind the `hero.name` property
|
||||
to the textbox.
|
||||
Data flow _in both directions_: from the property to the textbox;
|
||||
and from the textbox back to the property.
|
||||
|
||||
### Two-Way Binding
|
||||
Unfortunately, immediately after this change, the application breaks.
|
||||
If you looked in the browser console, you'd see Angular complaining that
|
||||
"`ngModel` ... isn't a known property of `input`."
|
||||
|
||||
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.
|
||||
Although `NgModel` is a valid Angular directive, it isn't available by default.
|
||||
It belongs to the optional `FormsModule`.
|
||||
You must opt-in to using that module.
|
||||
|
||||
Before we can use two-way data binding for **form inputs**, we need to import the `FormsModule`
|
||||
package in our Angular module. We add it to the `NgModule` decorator's `imports` array. This array contains the list
|
||||
of external modules used by our application.
|
||||
Now we have included the forms package which includes `ngModel`.
|
||||
### Import the _FormsModule_
|
||||
|
||||
+makeExample('toh-1/ts/src/app/app.module.ts', '', 'src/app/app.module.ts (FormsModule import)')
|
||||
Open the `app.module.ts` file and import the `FormsModule` symbol from the `@angular/forms` library.
|
||||
Then add the `FormsModule` to the `@NgModule` metadata's `imports` array, which contains the list
|
||||
of external modules that the app uses.
|
||||
|
||||
The updated `AppModule` looks like this:
|
||||
+makeExample('toh-1/ts/src/app/app.module.ts', '', 'app.module.ts (FormsModule import)')
|
||||
|
||||
.l-sub-section
|
||||
:marked
|
||||
Learn more about the `FormsModule` and `ngModel` in the
|
||||
[Forms](../guide/forms.html#ngModel) and
|
||||
[Template Syntax](../guide/template-syntax.html#ngModel) chapters.
|
||||
Read more about `FormsModule` and `ngModel` in the
|
||||
[Two-way data binding with ngModel](../guide/forms.html#ngModel) section of the
|
||||
[Forms](../guide/forms.html) guide and the
|
||||
[Two-way binding with NgModel](../guide/template-syntax.html#ngModel) section of the
|
||||
[Template Syntax](../guide/template-syntax.html) guide.
|
||||
|
||||
:marked
|
||||
Let’s update the template to use the **`ngModel`** built-in directive for two-way binding.
|
||||
|
||||
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>`.
|
||||
When the browser refreshes, the app should work again.
|
||||
You can edit the hero's name and see the changes reflected immediately in the `<h2>` above the textbox.
|
||||
|
||||
.l-main-section
|
||||
:marked
|
||||
## The Road We’ve Travelled
|
||||
Let’s take stock of what we’ve built.
|
||||
## The road you've travelled
|
||||
Take stock of what you've built.
|
||||
|
||||
* Our Tour of Heroes uses the double curly braces of interpolation (a kind 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`.
|
||||
* The Tour of Heroes app uses the double curly braces of interpolation (a type of one-way data binding)
|
||||
to display the app title and properties of a `Hero` object.
|
||||
* You wrote a multi-line template using ES2015's template literals to make the template readable.
|
||||
* You added a two-way data binding to the `<input>` element
|
||||
using the built-in `ngModel` directive. This binding both displays the hero's name and allows users to change it.
|
||||
* The `ngModel` directive propagates changes to every other binding of the `hero.name`.
|
||||
|
||||
Run the <live-example></live-example> for this part.
|
||||
Your app should look like this <live-example></live-example>.
|
||||
|
||||
Here's the complete `app.component.ts` as it stands now:
|
||||
|
||||
+makeExample('toh-1/ts/src/app/app.component.ts', 'pt1', 'src/app/app.component.ts')
|
||||
+makeExample('toh-1/ts/src/app/app.component.ts', '', 'src/app/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 a hero in the
|
||||
[next tutorial chapter](./toh-pt2.html).
|
||||
## The road ahead
|
||||
In the [next tutorial page](./toh-pt2.html), you'll build on the Tour of Heroes app to display a list of heroes.
|
||||
You'll also allow the user to select heroes and display their details.
|
||||
You'll learn more about how to retrieve lists and bind them to the template.
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user