(docs) Move ToH-pt2 to Jade and break toh-pt1 out of the tutorial index

This commit is contained in:
Ward Bell 2015-10-15 00:51:24 -07:00 committed by Naomi Black
parent b2de9a1067
commit c25314a2c4
7 changed files with 776 additions and 314 deletions

View File

@ -0,0 +1,7 @@
include ../../../../_includes/_util-fns
.l-main-section
:markdown
# Template Syntax
<h1>Coming Soon</h1>

View File

@ -2,7 +2,15 @@
"_listtype": "ordered",
"index": {
"title": "Tutorial",
"title": "Tutorial: Tour of Heroes",
"intro": "The Tour of Heroes tutorial takes us through the steps of creating an Angular application in TypeScript."
},
"toh-pt1": {
"title": "The Hero Editor",
"intro": "We build a simple hero editor"
},
"toh-pt2": {
"title": "Master/Detail",
"intro": "We build a master/detail page with a list of heroes "
}
}

View File

@ -1,11 +1,10 @@
include ../../../../_includes/_util-fns
.l-main-section
:markdown
# Tour of Heroes
# Tour of Heroes: the vision
Our grand vision is to build an app to help a staffing agency manage its stable of heroes.
Our grand plan is to build an app to help a staffing agency manage its stable of heroes.
Even heroes need to find work.
Of course we'll only make a little progress in this tutorial. What we do build will
@ -28,7 +27,7 @@ include ../../../../_includes/_util-fns
## The End Game
Let's get a visual idea of where we're going in this tour, beginning with the "Heroes"
Here's a visual idea of where we're going in this tour, beginning with the "Heroes"
view and its list of heroes:
figure.image-display
@ -56,312 +55,5 @@ include ../../../../_includes/_util-fns
met in countless applications. Everything has a reason.
And well meet many of the core fundamentals of Angular along the way.
.l-main-section
:markdown
# Once Upon a Time
Every story starts somewhere. Our story starts where the [QuickStart](../quickstart) ends.
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.
angular2-tour-of-heroes
├── node_modules
├── src
| ├── app
| | └── app.ts
| ├── index.html
| └── tsconfig.json
└── package.json
:markdown
## Keep the App Running
Start the TypeScript compiler and have it watch for changes in one terminal window by typing
pre.prettyprint.lang-bash
code tsc -p src -w
:markdown
Now open another terminal window and start the server by typing
pre.prettyprint.lang-bash
code live-server --open=src
:markdown
This command starts the server, launches the app in a browser,
and keeps the app running while we continue to build the Tour of Heroes.
.alert.is-helpful
:markdown
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
:markdown
# Show our Hero
We want to display Hero data in our app
Let's add two properties to our `AppComponent`, a `title` property for the application name and a `hero` property
for a hero named "Windstorm".
```
class AppComponent {
public title = 'Tour of Heroes';
public hero = 'Windstorm';
}
```
:markdown
Now we update the template in the `@Component` decoration with data bindings to these new properties.
code-example(format="linenums").
template: '&lt;h1>{{title}}&lt/h1>&lth2>{{hero}} details!&lt/h2>'
:markdown
The browser should refresh and display our title and hero.
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;
we can learn more about interpolation in the [Displaying Data chapter](displaying-data).
## 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.
Create a `Hero` class with `id` and `name` properties.
Keep this near the top of hte `app.ts` file for now.
```
class Hero {
id: number;
name: string;
}
```
Now that we have a `Hero` class, lets refactor our components `hero` property to be of type `Hero`.
Then initialize it with an id of `1` and the name, "Windstorm".
```
public hero: Hero = {
id: 1,
name: 'Windstorm'
};
```
Because we changed the hero from a string to an object,
we update the binding in the template to refer to the heros `name` property.
code-example(format="linenums").
template: '&lt;h1>{{title}}&lt/h1>&lth2>{{hero.name}} details!&lt/h2>'
:markdown
The browser refreshes and continues to display our heros name.
## **Adding more HTML**
Displaying a name is good, but we want to see all of our heros properties.
Well add a `<div>` for our heros `id` property and another `<div>` for our heros `name`.
code-example(format="linenums").
template: '&lt;h1>{{title}}&lt/h1>&lth2>{{hero.name}} details!&lt/h2>&ltdiv>&ltlabel>id: &lt/label>{{hero.id}}&lt/div>&ltdiv>&ltlabel>name: &lt/label>{{hero.name}}&lt/div>'
:markdown
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.
### Multi-line Template Strings
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,
lets 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.
code-example(format="linenums").
template:`
&lt;h1>{{title}}&lt/h1>
&lth2>{{hero.name}} details!&lt/h2>
&ltdiv>&ltlabel>id: &lt/label>{{hero.id}}&lt/div>
&ltdiv>&ltlabel>name: &lt/label>{{hero.name}}&lt/div>
`
.callout.is-important
header A back-tick is not a single quote
:markdown
**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.
.l-main-section
:markdown
# Editing Our Hero
We want to be able to edit the hero name in a textbox.
Replace the hero name `<label>` with an `<input>` element as shown below:
code-example(format="linenums").
template:`
&lt;h1>{{title}}&lt/h1>
&lth2>{{hero.name}} details!&lt/h2>
&ltdiv>&ltlabel>id: &lt/label>{{hero.id}}&lt/div>
&ltdiv>
&ltlabel>name: &lt/label>
&ltdiv>&ltinput value="{{hero.name}}" placeholder="name">&lt/input>&lt/div>
&lt/div>
`
:markdown
We see in the browser that the heros name does appear in the `<input>` textbox.
But something doesnt 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 heros name.
In short, we want two-way data binding.
Lets update the template to use the **`ng-model`** built-in directive for two-way binding.
<!-- TODO style with a class and add a link
*Sidenote: Learn more about ng-model in the [Using Forms chapter]*
-->
Replace the `<input>` with the following HTML
```
<input [(ng-model)]="hero.name" placeholder="name"></input>
```
Unfortunately, that change broke our application and we're no longer displaying the hero in the browser.
Lets fix that next.
.l-main-section
:markdown
# Declaring Template Directives
We added the `ng-model` directive but we didn't tell Angular about it.
A component must disclose every directive that appears in its template.
Lets first gain access to the `NgModel` directive class by importing it from Angular as shown below:
````
import {bootstrap, Component, NgModel} from 'angular2/angular2';
```
Now tell the component that we will use the `ng-model` directive in the template
by adding the `directives` property to the `@Component` decoration
immediately below the `template` string:
```
directives: [NgModel]
```
The `directives` property is an array holding all directive classes that
are used by the components template.
Unfortunately when we view the app in the browser we still have an error:
```
*EXCEPTION: No value accessor for ' ' in [null]*
```
Apparently declaring the `NgModel` is not quite enough.
## Declare Multiple Form Directives
We learned from our latest error message that we cant the import `NgModel` alone.
We need additional directives to enable two-way data binding with `NgModel`.
We could hunt them down and add each of them to the `directives` array one by one.
That's painful. No one wants to remember all of the necessary directives and
type them correctly. Fortunately, there is a shortcut.
The `ng-model` directive is one of many Forms directives which happen to be
bundled in a convenient array called `FORM_DIRECTIVES`.
<!-- TODO *Sidenote: Learn more about Angular Forms in the [Forms chapter]* -->
Lets forget about importing `NgModel` and import the `FORM_DIRECTIVES` array instead:
```
import {bootstrap, Component, FORM_DIRECTIVES} from 'angular2/angular2';
```
Now we tell the component that our template can use `FORM_DIRECTIVES`
by updating the `directives` property of the `@Component` decorator.
```
directives: [FORM_DIRECTIVES]
```
The browser refreshes. We see our hero again. We can edit the heros name and
see the changes reflected immediately in the `<h2>`.
### Bundled Directives
Angular bundled the Form-related directives together in a convenient `FORM_DIRECTIVES` array.
That's all we need to remember to light up our template.
We may wish to use this trick ourselves someday.
We too can bundle a collection of directives in an array, give it a catchy name,
and plug that array into the `directives` property.
# The Road Weve Travelled
Lets take stock of what weve 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 ES2015s template strings to make our template readable.
* We can both display and change the heros name after adding a two-way data binding to the `<input>` element
using the built-in `ng-model` directive.
* The `ng-model` directive also propagates changes to every other binding of the `hero.name`.
* We declared our use of `NgModel` and other Form directives
by setting the component's `directives` metadata property to the `FORMS_DIRECTIVES` array.
Here's the complete `AppComponent.ts` as it stands now:
code-example(format="linenums").
import {bootstrap, Component, FORM_DIRECTIVES} from 'angular2/angular2';
class Hero {
id: number;
name: string;
}
@Component({
template:`
&lt;h1>{{title}}&lt/h1>
&lth2>{{hero.name}} details!&lt/h2>
&ltdiv>&ltlabel>id: &lt/label>{{hero.id}}&lt/div>
&ltdiv>
&ltlabel>name: &lt/label>
&ltdiv>&ltinput value="{{hero.name}}" placeholder="name">&lt/input>&lt/div>
&lt/div>
`,
directives: [FORM_DIRECTIVES]
})
class AppComponent {
public title = 'Tour of Heroes';
public hero: Hero = {
id: 1,
name: 'Windstorm'
};
}
bootstrap(AppComponent);
<!-- TODO: Add this in when the next chapter exists
# **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.
Well learn more about how to retrieve lists, bind them to the
template, and allow a user to select it in the next chapter.
-->
[Let's get started!](./toh-pt1).

View File

@ -0,0 +1,314 @@
include ../../../../_includes/_util-fns
.l-main-section
:markdown
# Once Upon a Time
Every story starts somewhere. Our story starts where the [QuickStart](../quickstart) ends.
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.
angular2-tour-of-heroes
├── node_modules
├── src
| ├── app
| | └── app.ts
| ├── index.html
| └── tsconfig.json
└── package.json
:markdown
## Keep the App Running
Start the TypeScript compiler and have it watch for changes in one terminal window by typing
pre.prettyprint.lang-bash
code npm run tsc
:markdown
Now open another terminal window and start the server by typing
pre.prettyprint.lang-bash
code npm start
:markdown
This command starts the server, launches the app in a browser,
and keeps the app running while we continue to build the Tour of Heroes.
.alert.is-helpful
:markdown
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
:markdown
# Show our Hero
We want to display Hero data in our app
Let's add two properties to our `AppComponent`, a `title` property for the application name and a `hero` property
for a hero named "Windstorm".
```
class AppComponent {
public title = 'Tour of Heroes';
public hero = 'Windstorm';
}
```
:markdown
Now we update the template in the `@Component` decoration with data bindings to these new properties.
code-example(format="linenums").
template: '&lt;h1>{{title}}&lt/h1>&lth2>{{hero}} details!&lt/h2>'
:markdown
The browser should refresh and display our title and hero.
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;
we can learn more about interpolation in the [Displaying Data chapter](displaying-data).
## 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.
Create a `Hero` class with `id` and `name` properties.
Keep this near the top of hte `app.ts` file for now.
```
class Hero {
id: number;
name: string;
}
```
Now that we have a `Hero` class, lets refactor our components `hero` property to be of type `Hero`.
Then initialize it with an id of `1` and the name, "Windstorm".
```
public hero: Hero = {
id: 1,
name: 'Windstorm'
};
```
Because we changed the hero from a string to an object,
we update the binding in the template to refer to the heros `name` property.
code-example(format="linenums").
template: '&lt;h1>{{title}}&lt/h1>&lth2>{{hero.name}} details!&lt/h2>'
:markdown
The browser refreshes and continues to display our heros name.
## **Adding more HTML**
Displaying a name is good, but we want to see all of our heros properties.
Well add a `<div>` for our heros `id` property and another `<div>` for our heros `name`.
code-example(format="linenums").
template: '&lt;h1>{{title}}&lt/h1>&lth2>{{hero.name}} details!&lt/h2>&ltdiv>&ltlabel>id: &lt/label>{{hero.id}}&lt/div>&ltdiv>&ltlabel>name: &lt/label>{{hero.name}}&lt/div>'
:markdown
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.
### Multi-line Template Strings
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,
lets 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.
code-example(format="linenums").
template:`
&lt;h1>{{title}}&lt/h1>
&lth2>{{hero.name}} details!&lt/h2>
&ltdiv>&ltlabel>id: &lt/label>{{hero.id}}&lt/div>
&ltdiv>&ltlabel>name: &lt/label>{{hero.name}}&lt/div>
`
.callout.is-important
header A back-tick is not a single quote
:markdown
**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.
.l-main-section
:markdown
# Editing Our Hero
We want to be able to edit the hero name in a textbox.
Replace the hero name `<label>` with an `<input>` element as shown below:
code-example(format="linenums").
template:`
&lt;h1>{{title}}&lt/h1>
&lth2>{{hero.name}} details!&lt/h2>
&ltdiv>&ltlabel>id: &lt/label>{{hero.id}}&lt/div>
&ltdiv>
&ltlabel>name: &lt/label>
&ltdiv>&ltinput value="{{hero.name}}" placeholder="name">&lt/input>&lt/div>
&lt/div>
`
:markdown
We see in the browser that the heros name does appear in the `<input>` textbox.
But something doesnt 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 heros name.
In short, we want two-way data binding.
Lets update the template to use the **`ng-model`** built-in directive for two-way binding.
.alert.is-helpful
:markdown
Learn more about `ng-model` in the [Template Syntax](../guide/template-syntax#ng-model)
:markdown
Replace the `<input>` with the following HTML
```
<input [(ng-model)]="hero.name" placeholder="name"></input>
```
Unfortunately, that change broke our application and we're no longer displaying the hero in the browser.
Lets fix that next.
.l-main-section
:markdown
# Declaring Template Directives
We added the `ng-model` directive but we didn't tell Angular about it.
A component must disclose every directive that appears in its template.
Lets first gain access to the `NgModel` directive class by importing it from Angular as shown below:
````
import {bootstrap, Component, NgModel} from 'angular2/angular2';
```
Now tell the component that we will use the `ng-model` directive in the template
by adding the `directives` property to the `@Component` decoration
immediately below the `template` string:
```
directives: [NgModel]
```
The `directives` property is an array holding all directive classes that
are used by the components template.
Unfortunately when we view the app in the browser we still have an error:
```
*EXCEPTION: No value accessor for ' ' in [null]*
```
Apparently declaring the `NgModel` is not quite enough.
## Declare Multiple Form Directives
We learned from our latest error message that we cant the import `NgModel` alone.
We need additional directives to enable two-way data binding with `NgModel`.
We could hunt them down and add each of them to the `directives` array one by one.
That's painful. No one wants to remember all of the necessary directives and
type them correctly. Fortunately, there is a shortcut.
The `ng-model` directive is one of many Forms directives which happen to be
bundled in a convenient array called `FORM_DIRECTIVES`.
<!-- TODO
.alert.is-helpful
:markdown
Learn more about Angular Forms in the [Forms chapter]()
:markdown
-->
Lets forget about importing `NgModel` and import the `FORM_DIRECTIVES` array instead:
```
import {bootstrap, Component, FORM_DIRECTIVES} from 'angular2/angular2';
```
Now we tell the component that our template can use `FORM_DIRECTIVES`
by updating the `directives` property of the `@Component` decorator.
```
directives: [FORM_DIRECTIVES]
```
The browser refreshes. We see our hero again. We can edit the heros name and
see the changes reflected immediately in the `<h2>`.
### Bundled Directives
Angular bundled the Form-related directives together in a convenient `FORM_DIRECTIVES` array.
That's all we need to remember to light up our template.
We may wish to use this trick ourselves someday.
We too can bundle a collection of directives in an array, give it a catchy name,
and plug that array into the `directives` property.
# The Road Weve Travelled
Lets take stock of what weve 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 ES2015s template strings to make our template readable.
* We can both display and change the heros name after adding a two-way data binding to the `<input>` element
using the built-in `ng-model` directive.
* The `ng-model` directive also propagates changes to every other binding of the `hero.name`.
* We declared our use of `NgModel` and other Form directives
by setting the component's `directives` metadata property to the `FORMS_DIRECTIVES` array.
Here's the complete `AppComponent.ts` as it stands now:
code-example(format="linenums").
import {bootstrap, Component, FORM_DIRECTIVES} from 'angular2/angular2';
class Hero {
id: number;
name: string;
}
@Component({
template:`
&lt;h1>{{title}}&lt/h1>
&lth2>{{hero.name}} details!&lt/h2>
&ltdiv>&ltlabel>id: &lt/label>{{hero.id}}&lt/div>
&ltdiv>
&ltlabel>name: &lt/label>
&ltdiv>&ltinput value="{{hero.name}}" placeholder="name">&lt/input>&lt/div>
&lt/div>
`,
directives: [FORM_DIRECTIVES]
})
class AppComponent {
public title = 'Tour of Heroes';
public hero: Hero = {
id: 1,
name: 'Windstorm'
};
}
bootstrap(AppComponent);
:markdown
# 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.
Well 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).

View File

@ -0,0 +1,441 @@
include ../../../../_includes/_util-fns
.l-main-section
:markdown
# It Takes Many Heroes
Our story needs more heroes.
Well expand our Tour of Heroes app to display a list of heroes,
allow the user to select a hero, and display the heros details.
Lets take stock of what well need to display a list of heroes.
First, we need a list of heroes. We want to display those heroes in the views template,
so well need a way to do that.
.l-main-section
:markdown
# Where We Left Off
Before we continue with Part 2 of the Tour of Heroes,
lets verify we have the following structure after [Part 1](./toh-pt1).
If not, well need to go back to Part 1 and figure out what we missed.
code-example.
angular2-tour-of-heroes
├── node_modules
├── src
| ├── app
| | └── app.ts
| ├── index.html
| └── tsconfig.json
└── package.json
:markdown
## Keep the App Running
Start the TypeScript compiler and have it watch for changes in one terminal window by typing
pre.prettyprint.lang-bash
code npm run tsc
:markdown
Now open another terminal window and start the server by typing
pre.prettyprint.lang-bash
code npm start
:markdown
This will keep the application running while we continue to build the Tour of Heroes.
# Displaying Our Heroes
## Creating Heroes
Lets create an array of ten heroes at the bottom of `app.ts`.
```
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" }
];
```
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 lets take small steps
on this road and start by displaying these mock heroes in the browser.
## Exposing Heroes
Lets create a public property in `AppComponent` that exposes the heroes for binding.
```
public heroes = HEROES;
```
We did not have to define the `heroes` type. TypeScript can infer it from the `HEROES` array.
.alert.is-helpful
:markdown
We could have defined the heroes list here in this component class.
But we know that well get the heroes from a data service.
Because know where we are heading, it makes sense to separate the hero data
from the class implementation from the start.
:markdown
## Displaying Heroes in a Template
Our component has`heroes`. Lets create an unordered list in our template to display them.
Well insert the following chunk of HTML below the title and above the hero details.
```
<h2>My Heroes</h2>
<ul class="heroes">
<li>
<!-- each hero goes here -->
</li>
</ul>
```
Now we have a template that we can fill with our heroes.
## Iterating Over Our Heroes with `ng-for`
We want to bind the array of `heroes` in our component to our template, iterate over them,
and display them individually.
Well need some help from Angular to do this. Lets do this step by step.
First modify the `<li>` tag by adding the built-in directive `*ng-for`.
```
<li *ng-for="#hero of heroes">
```
.alert.is-important
:markdown
Do not neglect the leading asterisk (`*`) in front of `ng-for`!
Although it is not part of the `ng-for` directive name,
it is a critical part of the syntax in this usage of `ng-for`.
:markdown
The (`*`) prefix to the `ng-for` indicates that the `<li>` element and its children
constitute a master template.
The `ng-for` directive iterates over the `heroes` array returned by the `AppComponent.heroes` property
and stamps out instances of this template.
The quoted text assigned to `ng-for` means
“*take each hero in the `heroes` array, store it in the local `hero` variable,
and make it available to the corresponding template instance*”.
The `#` prefix before "hero" identifies the `hero` as a local template variable.
We can reference this variable within the template to access a heros properties.
.alert.is-helpful
:markdown
Learn more about `ng-for` and local template variables in the
[Template Syntax](../guide/template-syntax#ng-for) chapter.
:markdown
With this background in mind, we now insert some content between the `<li>` tags
that uses the `hero` template variable to display the heros properties.
code-example.
&lt;li *ng-for="#hero of heroes">
&lt;span class="badge">{{hero.id}}&lt;/span> {{hero.name}}&lt;/a>
&lt;/li>
:markdown
## Declaring NgFor
When we view the running app in the browser we see nothing … no heroes.
We open the developer tools and see an error in the console.
```
EXCEPTION:
Can't bind to 'ngForOf' since it isn't a known property of the '<template>' element and
there are no matching directives with a corresponding property
```
Thankfully we have a clear error message that indicates where we went wrong.
We used `ng-for` in the template but we didnt tell the component about it.
From Angular's perspective, `ng-for` is a meaningless attribute.
When it tries to render the view, it doesnt recognize `ng-for` and gives up.
We need to say “*hey component, Im going to use this NgFor directive. OK?*”
To that end, we first import the `NgFor` symbol
```
import {bootstrap, Component, FORM_DIRECTIVES, NgFor} from 'angular2/angular2';
```
and then declare `NgFor` to be one of the views directives in the `@Component` decorator.
```
directives: [FORM_DIRECTIVES, NgFor]
```
After the browser refreshes, we see a list of heroes!
## Styling Our Heroes
Our list of heroes looks pretty bland.
We want to make it visually obvious to a user which hero we are hovering over and which hero is selected.
Lets add some styles to our component by setting the `styles` property on the `@Component` decorator
to the following CSS classes:
```
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; }
`
```
Notice that we again use the back-tick notation for multi-line strings.
When we assign styles to a component they are scoped to that specific component.
Our styles will only apply to our `AppComponent` and won't "leak" to the outer HTML.
Our template for displaying the heroes should now look like this:
code-example(format="linenums").
&lt;h2>My Heroes&lt;/h2>
&lt;ul class="heroes">
&lt;li *ng-for="#hero of heroes">
&lt;span class="badge">{{hero.id}}&lt;/span> {{hero.name}}&lt;/a>
&lt;/li>
&lt;/ul>
:markdown
Our styled list of heroes should look like this:
figure.image-display
img(src='/resources/images/devguide/toh/heroes-list-2.png' alt="Output of heroes list app (no selection color)")
.l-main-section
:markdown
# Selecting a Hero
We have a list of heroes and we have a single hero displayed in our app.
The list and the single hero are not connected in any way.
We want the user to select a hero from our list, and have the selected hero appear in the details view.
This UI pattern is widely known as “master-detail”.
In our case, the master is the heroes list and the detail is the selected hero.
Lets connect the master to the detail through a `selectedHero` component property bound to a click event.
## Click Event
We modify the `<li>` by inserting an Angular event binding to its click event.
code-example(format="linenums").
&lt;li *ng-for="#hero of heroes" (click)="onSelect(hero)">
&lt;span class="badge">{{hero.id}}&lt;/span> {{hero.name}}&lt;/a>
&lt;/li>
:markdown
Focus on the event binding
pre.prettyprint.lang-bash
code (click)="onSelect(hero)">
:markdown
The parenthesis identify the `<li>` elements `click` event as the target.
The expression to the right of the equal sign calls the `AppComponent` method, `onSelect()`,
passing the local template variable `hero` as an argument.
Thats the same `hero` variable we defined previously in the `ng-for`.
.alert.is-helpful
:markdown
Learn more about Event Binding in the [Templating Syntax](../guide/template-syntax#event-binding) chapter.
:markdown
## Add the click handler
Our event binding refers to an `onSelect` method that doesnt exist yet.
Well add that method to our component now.
What should that method do? It should set the components selected hero to the hero that the user clicked.
Our component doesnt have a “selected hero” yet either. Well start there.
### Expose the Selected Hero
We no longer need the static `hero` property of the `AppComponent`.
**Replace** it with this simple `selectedHero` property:
```
public selectedHero: Hero;
```
Weve decided that none of the heroes should be selected before the user picks a hero so
we wont 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.
```
onSelect(hero: Hero) { this.selectedHero = hero; }
```
We will be showing the selected hero's details in our template.
At the moment, it is still referring to the old `hero` property.
Lets fix the template to bind to the new `selectedHero` property.
code-example(format="linenums").
&lt;h2>{{selectedHero.name}} details!&lt;/h2>
&lt;div>&lt;label>id: &lt;/label>{{selectedHero.id}}&lt;/div>
&lt;div>
&lt;label>name: &lt;/label>
&lt;input [(ng-model)]="selectedHero.name" placeholder="name">&lt;/input>
&lt;/div>
:markdown
### Hide the empty detail with NgIf
When our app loads we see a list of heroes, but a hero is not selected.
The `selectedHero` is `undefined`.
Thats why we'll' see the following error in the browsers console:
```
EXCEPTION: TypeError: Cannot read property 'name' of undefined in [null]
```
Remember that we are displaying `selectedHero.name` in the template.
This name property does not exist because `selectedHero`itself is undefined.
We'll' address this problem by keeping the hero detail out of the DOM until there is a selected hero.
We wrap the HTML hero detail content of our template with a `<div>`.
Then we add the `ng-if` built-in directive and set it to the `selectedHero` property of our component.
code-example(format="linenums").
&lt;div *ng-if="selectedHero">
&lt;h2>{{selectedHero.name}} details!&lt;/h2>
&lt;div>&lt;label>id: &lt;/label>{{selectedHero.id}}&lt;/div>
&lt;div>
&lt;label>name: &lt;/label>
&lt;input [(ng-model)]="selectedHero.name" placeholder="name">&lt;/input>
&lt;/div>
&lt;/div>
.alert.is-important
:markdown
Do not neglect the leading asterisk (`*`) in front of `ng-if`.
It's a critical part of the syntax in this usage of `ng-if`.
:markdown
When there is no `selectedHero`, the `ng-if` directive removes the hero detail HTML from the DOM.
There will no hero detail elements and no bindings to worry about.
When the user picks a hero, `selectedHero` becomes "truthy" and
`ng-if` puts the hero detail content into the DOM and evaluates the nested bindings.
.alert.is-helpful
:markdown
`ng-if` and `ng-for` are called “structural directives” because they can change the
structure of portions of the DOM.
In other words, they give structure to the way Angular displays content in the DOM.
Learn more about `ng-if`, `ng-for` and other structural directives in the
[Template Syntax](../guide/template-syntax#directives) chapter
:markdown
We learned previously with `NgFor` that we must declare every directive we use in the components `@Component` decorator.
Lets do that again for `NgIf`.
Add the `NgIf` symbol to our imports at the top of our `app.ts` file, keeping them sorted
alphabetically to make them easier to find:
```
import {bootstrap, Component, FORM_DIRECTIVES, NgFor, NgIf} from 'angular2/angular2';
```
:markdown
Now add `NgIf` to the directives array in the `@Component` decorator:
```
directives: [FORM_DIRECTIVES, NgFor, NgIf]
```
The browser refreshes and we see the list of heroes but not the selected hero detail.
The `ng-if` keeps it out of the DOM as long as the `selectedHero` is undefined.
When we click on a hero in the list, the selected hero displays in the hero details.
Everything is working as we expect.
### Styling the Selection
We see the selected hero in the details area below but we cant quickly locate that hero in the list above.
We can fix that by applying the `selected` CSS class to the appropriate `<li>` in the master list.
For example, when we select Magneta from the heroes list,
we can make it pop out visually by giving it a subtle background color as shown here.
figure.image-display
img(src='/resources/images/devguide/toh/heroes-list-selected.png' alt="Selected hero")
:markdown
First well add a `getSelectedClass` method to the component that compares the current `selectedHero` to a hero parameter
and returns an object with a single key/value pair.
The key is the name of the CSS class (`selected`). The value is `true` if the two heroes match and `false` otherwise.
Were saying “*apply the `selected` class if the heroes match, remove it if they dont*”.
Here is that method.
```
getSelectedClass(hero: Hero) {
return { 'selected': hero === this.selectedHero };
}
```
What do we do with this method and its peculiar result?
### NgClass
Well add the `ng-class`built-in directive to the `<li>` element in our template and bind it to `getSelectedClass`.
Its no coincidence that the value returned by `getSelectedClass` is exactly what the `ng-class` requires
to add or remove the `selected` class to each heros display.
code-example(format="linenums").
&lt;li *ng-for="#hero of heroes"
[ng-class]="getSelectedClass(hero)"
(click)="onSelect(hero)">
&lt;span class="badge">{{hero.id}}&lt;/span> {{hero.name}}&lt;/a>
&lt;/li>
.alert.is-helpful
:markdown
Learn more about `ng-class` in the
[Template Syntax](../guide/template-syntax#ng-class) chapter
:markdown
Notice in the template that the `ng-class` name is surrounded in square brackets (`[]`).
This is the syntax for a Property Binding, a binding in which data flow one way
from the data source (the `getSelectedClass`) to a property of the `ng-class` directive.
.alert.is-helpful
:markdown
Learn more about about one-way property data binding in the
[Template Syntax](../guide/template-syntax#property-binding) chapter
:markdown
We've added yet another new directive to our template that we have to import and declare
in the components `directives` array as weve done twice before.
```
import {bootstrap, Component,
FORM_DIRECTIVES, NgClass, NgFor, NgIf} from 'angular2/angular2';
```
```
directives: [FORM_DIRECTIVES, NgClass, NgFor, NgIf]
```
The browser reloads our app.
We select a hero and the selection is clearly identified by the background color.
figure.image-display
img(src='/resources/images/devguide/toh/heroes-list-1.png' alt="Output of heroes list app")
:markdown
We select a different hero and the tell-tale color switches to that hero.
## Declaring Built-In Directives
Every time we used a directive, we imported it and declared it in the component.
We only used three directives but we can easily envision a component that uses many more.
The `directives` array grows quickly and the process of importing the directive and adding it to the array is tedious.
We can make this easier.
Remember how we imported the `FORM_DIRECTIVES` array to help us apply `ng-model`to our template in the previous chapter?
The `FORM_DIRECTIVES` array held all the directives we needed for `ng-model` (and a few more).
We didnt have to list them. We simply added the `FORM_DIRECTIVES` array to the components `directives` array.
The `NgClass`, `NgFor`, and `NgIf` are extremely common directives used by many components in many applications.
Fortunately they are all exported from Angular as part of the `CORE_DIRECTIVES` array.
Lets replace all of those separate import variables with `CORE_DIRECTIVES`:
```
import {bootstrap, Component, CORE_DIRECTIVES, FORM_DIRECTIVES} from 'angular2/angular2';
```
Then replace `NgClass`, `NgFor`, and `NgIf` in the `directives` array with `CORE_DIRECTIVES`:
```
directives: [CORE_DIRECTIVES, FORM_DIRECTIVES]
```
Everything still works and we have a convenient way to import and declare the most commonly used directives.
Cleaner code for the win!
.l-main-section
:markdown
# The Road Weve Travelled
Heres 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 heros details
* We learned how to use the built-in directives `ng-if`, `ng-for` and `ng-class` in a components template
## The Road Ahead
Our Tour of Heroes has grown, but its far from complete.
We want to get data from an asynchronous source using promises, use shared services, and create reusable components.
Well learn more about these tasks in the coming tutorial chapters.

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB