2015-10-15 03:51:24 -04:00
|
|
|
|
include ../../../../_includes/_util-fns
|
|
|
|
|
|
|
|
|
|
.l-main-section
|
2015-11-10 13:31:46 -05:00
|
|
|
|
:marked
|
|
|
|
|
# It Takes Many Heroes
|
|
|
|
|
Our story needs more heroes.
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
|
|
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,
|
2015-10-15 03:51:24 -04:00
|
|
|
|
so we’ll need a way to do that.
|
2015-11-10 13:31:46 -05:00
|
|
|
|
|
2015-10-15 03:51:24 -04:00
|
|
|
|
.l-main-section
|
2015-11-10 13:31:46 -05:00
|
|
|
|
:marked
|
2015-10-16 04:06:56 -04:00
|
|
|
|
## Where We Left Off
|
2015-11-10 13:31:46 -05:00
|
|
|
|
Before we continue with Part 2 of the Tour of Heroes,
|
2015-10-15 14:33:43 -04:00
|
|
|
|
let’s verify we have the following structure after [Part 1](./toh-pt1.html).
|
2015-10-15 03:51:24 -04:00
|
|
|
|
If not, we’ll need to go back to Part 1 and figure out what we missed.
|
2015-11-10 13:31:46 -05:00
|
|
|
|
|
2015-10-15 03:51:24 -04:00
|
|
|
|
code-example.
|
|
|
|
|
angular2-tour-of-heroes
|
|
|
|
|
├── node_modules
|
|
|
|
|
├── src
|
|
|
|
|
| ├── app
|
|
|
|
|
| | └── app.ts
|
|
|
|
|
| ├── index.html
|
|
|
|
|
| └── tsconfig.json
|
2015-11-10 13:31:46 -05:00
|
|
|
|
└── package.json
|
|
|
|
|
:marked
|
2015-10-16 04:06:56 -04:00
|
|
|
|
### Keep the app running
|
2015-10-15 03:51:24 -04:00
|
|
|
|
Start the TypeScript compiler and have it watch for changes in one terminal window by typing
|
|
|
|
|
|
|
|
|
|
pre.prettyprint.lang-bash
|
|
|
|
|
code npm run tsc
|
|
|
|
|
|
2015-11-10 13:31:46 -05:00
|
|
|
|
:marked
|
2015-10-15 03:51:24 -04:00
|
|
|
|
Now open another terminal window and start the server by typing
|
|
|
|
|
|
|
|
|
|
pre.prettyprint.lang-bash
|
|
|
|
|
code npm start
|
|
|
|
|
|
2015-11-10 13:31:46 -05:00
|
|
|
|
:marked
|
2015-10-15 03:51:24 -04:00
|
|
|
|
This will keep the application running while we continue to build the Tour of Heroes.
|
2015-10-16 04:06:56 -04:00
|
|
|
|
|
|
|
|
|
.l-main-section
|
2015-11-10 13:31:46 -05:00
|
|
|
|
:marked
|
2015-10-16 04:06:56 -04:00
|
|
|
|
## Displaying Our Heroes
|
|
|
|
|
### Creating heroes
|
2015-10-15 03:51:24 -04:00
|
|
|
|
Let’s create an array of ten heroes at the bottom of `app.ts`.
|
|
|
|
|
```
|
|
|
|
|
var HEROES: Hero[] = [
|
2015-10-15 22:08:55 -04:00
|
|
|
|
{ "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" }
|
2015-10-15 03:51:24 -04:00
|
|
|
|
];
|
|
|
|
|
```
|
2015-11-10 13:31:46 -05:00
|
|
|
|
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
|
2015-10-15 03:51:24 -04:00
|
|
|
|
on this road and start by displaying these mock heroes in the browser.
|
2015-11-10 13:31:46 -05:00
|
|
|
|
|
2015-10-16 04:06:56 -04:00
|
|
|
|
### Exposing heroes
|
2015-10-15 03:51:24 -04:00
|
|
|
|
Let’s 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.
|
2015-10-16 04:06:56 -04:00
|
|
|
|
.l-sub-section
|
2015-11-10 13:31:46 -05:00
|
|
|
|
: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.
|
|
|
|
|
Because we know where we are heading, it makes sense to separate the hero data
|
2015-10-15 03:51:24 -04:00
|
|
|
|
from the class implementation from the start.
|
2015-11-10 13:31:46 -05:00
|
|
|
|
:marked
|
2015-10-16 04:06:56 -04:00
|
|
|
|
### Displaying heroes in a template
|
2015-11-10 13:31:46 -05:00
|
|
|
|
Our component has`heroes`. Let’s create an unordered list in our template to display them.
|
2015-10-15 03:51:24 -04:00
|
|
|
|
We’ll 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.
|
|
|
|
|
|
2015-10-16 04:06:56 -04:00
|
|
|
|
### Listing heroes with ng-for
|
2015-11-10 13:31:46 -05:00
|
|
|
|
|
|
|
|
|
We want to bind the array of `heroes` in our component to our template, iterate over them,
|
|
|
|
|
and display them individually.
|
2015-10-15 03:51:24 -04:00
|
|
|
|
We’ll need some help from Angular to do this. Let’s do this step by step.
|
2015-11-10 13:31:46 -05:00
|
|
|
|
|
|
|
|
|
First modify the `<li>` tag by adding the built-in directive `*ng-for`.
|
2015-10-15 03:51:24 -04:00
|
|
|
|
```
|
|
|
|
|
<li *ng-for="#hero of heroes">
|
|
|
|
|
```
|
2015-10-16 04:06:56 -04:00
|
|
|
|
.alert.is-critical
|
2015-11-10 13:31:46 -05:00
|
|
|
|
:marked
|
2015-10-16 04:06:56 -04:00
|
|
|
|
The leading asterisk (`*`) in front of `ng-for` is a critical part of this syntax.
|
2015-11-10 13:31:46 -05:00
|
|
|
|
|
2015-10-16 04:06:56 -04:00
|
|
|
|
.l-sub-section
|
2015-11-10 13:31:46 -05:00
|
|
|
|
:marked
|
2015-10-16 04:06:56 -04:00
|
|
|
|
The (`*`) prefix to `ng-for` indicates that the `<li>` element and its children
|
|
|
|
|
constitute a master template.
|
2015-11-10 13:31:46 -05:00
|
|
|
|
|
2015-10-16 04:06:56 -04:00
|
|
|
|
The `ng-for` directive iterates over the `heroes` array returned by the `AppComponent.heroes` property
|
|
|
|
|
and stamps out instances of this template.
|
2015-11-10 13:31:46 -05:00
|
|
|
|
|
|
|
|
|
The quoted text assigned to `ng-for` means
|
2015-10-16 04:06:56 -04:00
|
|
|
|
“*take each hero in the `heroes` array, store it in the local `hero` variable,
|
|
|
|
|
and make it available to the corresponding template instance*”.
|
2015-11-10 13:31:46 -05:00
|
|
|
|
|
|
|
|
|
The `#` prefix before "hero" identifies the `hero` as a local template variable.
|
2015-10-16 04:06:56 -04:00
|
|
|
|
We can reference this variable within the template to access a hero’s properties.
|
|
|
|
|
|
2015-11-10 13:31:46 -05:00
|
|
|
|
Learn more about `ng-for` and local template variables in the
|
2015-11-14 03:22:26 -05:00
|
|
|
|
[Template Syntax chapter](../guide/template-syntax.html#ng-for).
|
2015-10-15 03:51:24 -04:00
|
|
|
|
|
2015-11-10 13:31:46 -05:00
|
|
|
|
:marked
|
|
|
|
|
With this background in mind, we now insert some content between the `<li>` tags
|
2015-10-15 03:51:24 -04:00
|
|
|
|
that uses the `hero` template variable to display the hero’s properties.
|
2015-10-16 04:06:56 -04:00
|
|
|
|
|
|
|
|
|
code-example(format="linenums" language="html").
|
2015-10-15 03:51:24 -04:00
|
|
|
|
<li *ng-for="#hero of heroes">
|
2015-10-19 16:51:41 -04:00
|
|
|
|
<span class="badge">{{hero.id}}</span> {{hero.name}}
|
2015-10-15 03:51:24 -04:00
|
|
|
|
</li>
|
2015-11-10 13:31:46 -05:00
|
|
|
|
|
|
|
|
|
:marked
|
2015-10-16 04:06:56 -04:00
|
|
|
|
### Declaring ng-for
|
2015-11-10 13:31:46 -05:00
|
|
|
|
When we view the running app in the browser we see nothing … no heroes.
|
2015-10-15 03:51:24 -04:00
|
|
|
|
We open the developer tools and see an error in the console.
|
2015-11-10 13:31:46 -05:00
|
|
|
|
|
2015-10-16 04:06:56 -04:00
|
|
|
|
code-example(language="html" ).
|
2015-11-10 13:31:46 -05:00
|
|
|
|
EXCEPTION:
|
|
|
|
|
Can't bind to 'ngForOf' since it isn't a known property of the '<template>' element and
|
2015-10-15 03:51:24 -04:00
|
|
|
|
there are no matching directives with a corresponding property
|
2015-10-16 04:06:56 -04:00
|
|
|
|
|
2015-11-10 13:31:46 -05:00
|
|
|
|
:marked
|
|
|
|
|
Thankfully we have a clear error message that indicates where we went wrong.
|
|
|
|
|
We used `ng-for` in the template but we didn’t tell the component about it.
|
|
|
|
|
From Angular's perspective, `ng-for` is a meaningless attribute.
|
2015-10-15 03:51:24 -04:00
|
|
|
|
When it tries to render the view, it doesn’t recognize `ng-for` and gives up.
|
2015-11-10 13:31:46 -05:00
|
|
|
|
|
2015-10-15 03:51:24 -04:00
|
|
|
|
We need to say “*hey component, I’m going to use this NgFor directive. OK?*”
|
2015-11-10 13:31:46 -05:00
|
|
|
|
|
2015-10-15 03:51:24 -04:00
|
|
|
|
To that end, we first import the `NgFor` symbol
|
|
|
|
|
```
|
|
|
|
|
import {bootstrap, Component, FORM_DIRECTIVES, NgFor} from 'angular2/angular2';
|
|
|
|
|
```
|
2015-11-10 13:31:46 -05:00
|
|
|
|
and then declare `NgFor` to be one of the view’s directives in the `@Component` decorator.
|
2015-10-15 03:51:24 -04:00
|
|
|
|
```
|
|
|
|
|
directives: [FORM_DIRECTIVES, NgFor]
|
|
|
|
|
```
|
|
|
|
|
After the browser refreshes, we see a list of heroes!
|
2015-11-10 13:31:46 -05:00
|
|
|
|
|
2015-10-16 04:06:56 -04:00
|
|
|
|
### Styling our heroes
|
2015-10-15 03:51:24 -04:00
|
|
|
|
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.
|
2015-11-10 13:31:46 -05:00
|
|
|
|
|
|
|
|
|
Let’s add some styles to our component by setting the `styles` property on the `@Component` decorator
|
2015-10-15 03:51:24 -04:00
|
|
|
|
to the following CSS classes:
|
|
|
|
|
```
|
2015-10-16 04:06:56 -04:00
|
|
|
|
styles:[`
|
2015-10-15 03:51:24 -04:00
|
|
|
|
.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;
|
2015-10-16 04:06:56 -04:00
|
|
|
|
}
|
|
|
|
|
.selected { background-color: #EEE; color: #369; }
|
|
|
|
|
`],
|
2015-10-15 03:51:24 -04:00
|
|
|
|
```
|
|
|
|
|
Notice that we again use the back-tick notation for multi-line strings.
|
2015-11-10 13:31:46 -05:00
|
|
|
|
|
|
|
|
|
When we assign styles to a component they are scoped to that specific component.
|
2015-10-15 03:51:24 -04:00
|
|
|
|
Our styles will only apply to our `AppComponent` and won't "leak" to the outer HTML.
|
2015-11-10 13:31:46 -05:00
|
|
|
|
|
2015-10-15 03:51:24 -04:00
|
|
|
|
Our template for displaying the heroes should now look like this:
|
2015-11-10 13:31:46 -05:00
|
|
|
|
|
|
|
|
|
code-example(format="linenums").
|
2015-10-15 03:51:24 -04:00
|
|
|
|
<h2>My Heroes</h2>
|
|
|
|
|
<ul class="heroes">
|
|
|
|
|
<li *ng-for="#hero of heroes">
|
2015-10-29 18:37:09 -04:00
|
|
|
|
<span class="badge">{{hero.id}}</span> {{hero.name}}
|
2015-10-15 03:51:24 -04:00
|
|
|
|
</li>
|
|
|
|
|
</ul>
|
2015-11-10 13:31:46 -05:00
|
|
|
|
:marked
|
2015-10-15 03:51:24 -04:00
|
|
|
|
Our styled list of heroes should look like this:
|
2015-11-10 13:31:46 -05:00
|
|
|
|
|
2015-10-15 03:51:24 -04:00
|
|
|
|
figure.image-display
|
2015-11-10 13:31:46 -05:00
|
|
|
|
img(src='/resources/images/devguide/toh/heroes-list-2.png' alt="Output of heroes list app (no selection color)")
|
2015-10-15 03:51:24 -04:00
|
|
|
|
|
|
|
|
|
.l-main-section
|
2015-11-10 13:31:46 -05:00
|
|
|
|
:marked
|
2015-10-16 04:06:56 -04:00
|
|
|
|
## Selecting a Hero
|
2015-11-10 13:31:46 -05:00
|
|
|
|
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.
|
|
|
|
|
|
2015-10-15 03:51:24 -04:00
|
|
|
|
Let’s connect the master to the detail through a `selectedHero` component property bound to a click event.
|
2015-11-10 13:31:46 -05:00
|
|
|
|
|
2015-10-16 04:06:56 -04:00
|
|
|
|
### Click event
|
2015-10-15 03:51:24 -04:00
|
|
|
|
We modify the `<li>` by inserting an Angular event binding to its click event.
|
2015-11-10 13:31:46 -05:00
|
|
|
|
|
|
|
|
|
code-example(format="linenums").
|
2015-10-15 03:51:24 -04:00
|
|
|
|
<li *ng-for="#hero of heroes" (click)="onSelect(hero)">
|
2015-10-19 16:51:41 -04:00
|
|
|
|
<span class="badge">{{hero.id}}</span> {{hero.name}}
|
2015-10-15 03:51:24 -04:00
|
|
|
|
</li>
|
2015-11-10 13:31:46 -05:00
|
|
|
|
:marked
|
2015-10-15 03:51:24 -04:00
|
|
|
|
Focus on the event binding
|
|
|
|
|
pre.prettyprint.lang-bash
|
|
|
|
|
code (click)="onSelect(hero)">
|
2015-11-10 13:31:46 -05:00
|
|
|
|
:marked
|
|
|
|
|
The parenthesis identify the `<li>` element’s `click` event as the target.
|
|
|
|
|
The expression to the right of the equal sign calls the `AppComponent` method, `onSelect()`,
|
2015-10-15 03:51:24 -04:00
|
|
|
|
passing the local template variable `hero` as an argument.
|
|
|
|
|
That’s the same `hero` variable we defined previously in the `ng-for`.
|
2015-10-16 04:06:56 -04:00
|
|
|
|
.l-sub-section
|
2015-11-10 13:31:46 -05:00
|
|
|
|
:marked
|
2015-11-14 03:22:26 -05:00
|
|
|
|
Learn more about Event Binding in the [Templating Syntax chapter](../guide/template-syntax.html#event-binding).
|
2015-11-10 13:31:46 -05:00
|
|
|
|
:marked
|
2015-10-16 04:06:56 -04:00
|
|
|
|
### Add the click handler
|
2015-11-10 13:31:46 -05:00
|
|
|
|
Our event binding refers to an `onSelect` method that doesn’t exist yet.
|
|
|
|
|
We’ll add that method to our component now.
|
|
|
|
|
|
2015-10-15 03:51:24 -04:00
|
|
|
|
What should that method do? It should set the component’s selected hero to the hero that the user clicked.
|
2015-11-10 13:31:46 -05:00
|
|
|
|
|
2015-10-15 03:51:24 -04:00
|
|
|
|
Our component doesn’t have a “selected hero” yet either. We’ll start there.
|
2015-11-10 13:31:46 -05:00
|
|
|
|
|
|
|
|
|
### Expose the selected hero
|
2015-10-15 03:51:24 -04:00
|
|
|
|
We no longer need the static `hero` property of the `AppComponent`.
|
|
|
|
|
**Replace** it with this simple `selectedHero` property:
|
|
|
|
|
```
|
|
|
|
|
public selectedHero: Hero;
|
|
|
|
|
```
|
2015-11-10 13:31:46 -05:00
|
|
|
|
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`.
|
|
|
|
|
|
2015-10-15 03:51:24 -04:00
|
|
|
|
Now **add an `onSelect` method** that sets the `selectedHero` property to the `hero` the user clicked.
|
|
|
|
|
```
|
|
|
|
|
onSelect(hero: Hero) { this.selectedHero = hero; }
|
|
|
|
|
```
|
2015-11-10 13:31:46 -05:00
|
|
|
|
|
|
|
|
|
We will be showing the selected hero's details in our template.
|
|
|
|
|
At the moment, it is still referring to the old `hero` property.
|
2015-10-15 03:51:24 -04:00
|
|
|
|
Let’s fix the template to bind to the new `selectedHero` property.
|
2015-11-10 13:31:46 -05:00
|
|
|
|
|
|
|
|
|
code-example(format="linenums").
|
2015-10-15 03:51:24 -04:00
|
|
|
|
<h2>{{selectedHero.name}} details!</h2>
|
|
|
|
|
<div><label>id: </label>{{selectedHero.id}}</div>
|
|
|
|
|
<div>
|
|
|
|
|
<label>name: </label>
|
|
|
|
|
<input [(ng-model)]="selectedHero.name" placeholder="name"></input>
|
|
|
|
|
</div>
|
2015-11-10 13:31:46 -05:00
|
|
|
|
|
|
|
|
|
:marked
|
2015-10-16 04:06:56 -04:00
|
|
|
|
### Hide the empty detail with ng-if
|
2015-11-10 13:31:46 -05:00
|
|
|
|
|
|
|
|
|
When our app loads we see a list of heroes, but a hero is not selected.
|
|
|
|
|
The `selectedHero` is `undefined`.
|
2015-10-15 22:32:47 -04:00
|
|
|
|
That’s why we'll see the following error in the browser’s console:
|
2015-11-10 13:31:46 -05:00
|
|
|
|
|
2015-10-16 04:06:56 -04:00
|
|
|
|
code-example(language="html").
|
2015-10-15 03:51:24 -04:00
|
|
|
|
EXCEPTION: TypeError: Cannot read property 'name' of undefined in [null]
|
2015-11-10 13:31:46 -05:00
|
|
|
|
|
|
|
|
|
:marked
|
|
|
|
|
Remember that we are displaying `selectedHero.name` in the template.
|
|
|
|
|
This name property does not exist because `selectedHero`itself is undefined.
|
|
|
|
|
|
2015-10-15 22:32:47 -04:00
|
|
|
|
We'll address this problem by keeping the hero detail out of the DOM until there is a selected hero.
|
2015-11-10 13:31:46 -05:00
|
|
|
|
|
|
|
|
|
We wrap the HTML hero detail content of our template with a `<div>`.
|
2015-10-15 03:51:24 -04:00
|
|
|
|
Then we add the `ng-if` built-in directive and set it to the `selectedHero` property of our component.
|
2015-11-10 13:31:46 -05:00
|
|
|
|
|
2015-10-16 04:06:56 -04:00
|
|
|
|
code-example(format="linenums").
|
2015-10-15 03:51:24 -04:00
|
|
|
|
<div *ng-if="selectedHero">
|
|
|
|
|
<h2>{{selectedHero.name}} details!</h2>
|
|
|
|
|
<div><label>id: </label>{{selectedHero.id}}</div>
|
|
|
|
|
<div>
|
|
|
|
|
<label>name: </label>
|
|
|
|
|
<input [(ng-model)]="selectedHero.name" placeholder="name"></input>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2015-10-16 04:06:56 -04:00
|
|
|
|
.alert.is-critical
|
2015-11-10 13:31:46 -05:00
|
|
|
|
:marked
|
|
|
|
|
Remember that the leading asterisk (`*`) in front of `ng-if` is
|
2015-10-16 04:06:56 -04:00
|
|
|
|
a critical part of this syntax.
|
2015-11-10 13:31:46 -05:00
|
|
|
|
:marked
|
|
|
|
|
When there is no `selectedHero`, the `ng-if` directive removes the hero detail HTML from the DOM.
|
|
|
|
|
There will be no hero detail elements and no bindings to worry about.
|
|
|
|
|
|
2015-10-15 03:51:24 -04:00
|
|
|
|
When the user picks a hero, `selectedHero` becomes "truthy" and
|
2015-11-10 13:31:46 -05:00
|
|
|
|
`ng-if` puts the hero detail content into the DOM and evaluates the nested bindings.
|
2015-10-16 04:06:56 -04:00
|
|
|
|
.l-sub-section
|
2015-11-10 13:31:46 -05:00
|
|
|
|
:marked
|
|
|
|
|
`ng-if` and `ng-for` are called “structural directives” because they can change the
|
|
|
|
|
structure of portions of the DOM.
|
2015-10-15 03:51:24 -04:00
|
|
|
|
In other words, they give structure to the way Angular displays content in the DOM.
|
2015-11-10 13:31:46 -05:00
|
|
|
|
|
|
|
|
|
Learn more about `ng-if`, `ng-for` and other structural directives in the
|
2015-11-14 03:22:26 -05:00
|
|
|
|
[Template Syntax chapter](../guide/template-syntax.html#directives).
|
2015-11-10 13:31:46 -05:00
|
|
|
|
|
|
|
|
|
:marked
|
|
|
|
|
We learned previously with `NgFor` that we must declare every directive we use in the component’s `@Component` decorator.
|
2015-10-15 03:51:24 -04:00
|
|
|
|
Let’s do that again for `NgIf`.
|
2015-11-10 13:31:46 -05:00
|
|
|
|
|
2015-10-15 03:51:24 -04:00
|
|
|
|
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';
|
|
|
|
|
```
|
2015-11-10 13:31:46 -05:00
|
|
|
|
:marked
|
2015-10-15 03:51:24 -04:00
|
|
|
|
Now add `NgIf` to the directives array in the `@Component` decorator:
|
|
|
|
|
```
|
|
|
|
|
directives: [FORM_DIRECTIVES, NgFor, NgIf]
|
|
|
|
|
```
|
2015-11-10 13:31:46 -05:00
|
|
|
|
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.
|
2015-10-15 03:51:24 -04:00
|
|
|
|
Everything is working as we expect.
|
2015-11-10 13:31:46 -05:00
|
|
|
|
|
2015-10-16 04:06:56 -04:00
|
|
|
|
### Styling the selection
|
2015-11-10 13:31:46 -05:00
|
|
|
|
|
|
|
|
|
We see the selected hero in the details area below but we can’t 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,
|
2015-10-15 03:51:24 -04:00
|
|
|
|
we can make it pop out visually by giving it a subtle background color as shown here.
|
2015-11-10 13:31:46 -05:00
|
|
|
|
|
2015-10-15 03:51:24 -04:00
|
|
|
|
figure.image-display
|
|
|
|
|
img(src='/resources/images/devguide/toh/heroes-list-selected.png' alt="Selected hero")
|
2015-11-10 13:31:46 -05:00
|
|
|
|
:marked
|
|
|
|
|
First we’ll 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.
|
|
|
|
|
We’re saying “*apply the `selected` class if the heroes match, remove it if they don’t*”.
|
2015-10-15 03:51:24 -04:00
|
|
|
|
Here is that method.
|
|
|
|
|
```
|
|
|
|
|
getSelectedClass(hero: Hero) {
|
|
|
|
|
return { 'selected': hero === this.selectedHero };
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
What do we do with this method and its peculiar result?
|
2015-11-10 13:31:46 -05:00
|
|
|
|
|
2015-10-16 04:06:56 -04:00
|
|
|
|
### ng-class
|
2015-10-15 03:51:24 -04:00
|
|
|
|
We’ll add the `ng-class`built-in directive to the `<li>` element in our template and bind it to `getSelectedClass`.
|
2015-11-10 13:31:46 -05:00
|
|
|
|
It’s no coincidence that the value returned by `getSelectedClass` is exactly what the `ng-class` requires
|
2015-10-15 03:51:24 -04:00
|
|
|
|
to add or remove the `selected` class to each hero’s display.
|
2015-11-10 13:31:46 -05:00
|
|
|
|
code-example(format="linenums").
|
2015-10-15 03:51:24 -04:00
|
|
|
|
<li *ng-for="#hero of heroes"
|
|
|
|
|
[ng-class]="getSelectedClass(hero)"
|
|
|
|
|
(click)="onSelect(hero)">
|
2015-10-19 16:51:41 -04:00
|
|
|
|
<span class="badge">{{hero.id}}</span> {{hero.name}}
|
2015-10-15 03:51:24 -04:00
|
|
|
|
</li>
|
|
|
|
|
|
2015-11-10 13:31:46 -05:00
|
|
|
|
:marked
|
|
|
|
|
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 flows one way
|
|
|
|
|
from the data source (the `getSelectedClass`) to a property of the `ng-class` directive.
|
|
|
|
|
|
2015-10-16 04:06:56 -04:00
|
|
|
|
.l-sub-section
|
2015-11-10 13:31:46 -05:00
|
|
|
|
:marked
|
2015-10-16 04:06:56 -04:00
|
|
|
|
Learn more about [ng-class](../guide/template-syntax.html#ng-class)
|
2015-11-10 13:31:46 -05:00
|
|
|
|
and [Property Binding](../guide/template-syntax.html#property-binding)
|
2015-11-14 03:22:26 -05:00
|
|
|
|
in the Template Syntax chapter.
|
2015-11-10 13:31:46 -05:00
|
|
|
|
:marked
|
|
|
|
|
We've added yet another new directive to our template that we have to import and declare
|
2015-10-15 03:51:24 -04:00
|
|
|
|
in the component’s `directives` array as we’ve done twice before.
|
|
|
|
|
```
|
2015-11-10 13:31:46 -05:00
|
|
|
|
import {bootstrap, Component,
|
2015-10-15 03:51:24 -04:00
|
|
|
|
FORM_DIRECTIVES, NgClass, NgFor, NgIf} from 'angular2/angular2';
|
|
|
|
|
```
|
2015-11-10 13:31:46 -05:00
|
|
|
|
|
|
|
|
|
```
|
2015-10-15 03:51:24 -04:00
|
|
|
|
directives: [FORM_DIRECTIVES, NgClass, NgFor, NgIf]
|
|
|
|
|
```
|
2015-11-10 13:31:46 -05:00
|
|
|
|
The browser reloads our app.
|
2015-10-15 03:51:24 -04:00
|
|
|
|
We select a hero and the selection is clearly identified by the background color.
|
2015-11-10 13:31:46 -05:00
|
|
|
|
|
2015-10-15 03:51:24 -04:00
|
|
|
|
figure.image-display
|
|
|
|
|
img(src='/resources/images/devguide/toh/heroes-list-1.png' alt="Output of heroes list app")
|
2015-11-10 13:31:46 -05:00
|
|
|
|
|
|
|
|
|
:marked
|
2015-10-15 03:51:24 -04:00
|
|
|
|
We select a different hero and the tell-tale color switches to that hero.
|
2015-11-10 13:31:46 -05:00
|
|
|
|
|
2015-10-15 03:51:24 -04:00
|
|
|
|
## Declaring Built-In Directives
|
2015-11-10 13:31:46 -05:00
|
|
|
|
|
|
|
|
|
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.
|
2015-10-15 03:51:24 -04:00
|
|
|
|
We can make this easier.
|
2015-11-10 13:31:46 -05:00
|
|
|
|
|
2015-10-15 03:51:24 -04:00
|
|
|
|
Remember how we imported the `FORM_DIRECTIVES` array to help us apply `ng-model`to our template in the previous chapter?
|
2015-11-10 13:31:46 -05:00
|
|
|
|
The `FORM_DIRECTIVES` array held all the directives we needed for `ng-model` (and a few more).
|
2015-10-15 03:51:24 -04:00
|
|
|
|
We didn’t have to list them. We simply added the `FORM_DIRECTIVES` array to the component’s `directives` array.
|
2015-11-10 13:31:46 -05:00
|
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
2015-10-15 03:51:24 -04:00
|
|
|
|
Let’s 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]
|
|
|
|
|
```
|
2015-11-10 13:31:46 -05:00
|
|
|
|
Everything still works and we have a convenient way to import and declare the most commonly used directives.
|
|
|
|
|
Cleaner code for the win!
|
|
|
|
|
|
2015-10-15 03:51:24 -04:00
|
|
|
|
.l-main-section
|
2015-11-10 13:31:46 -05:00
|
|
|
|
:marked
|
2015-10-16 04:06:56 -04:00
|
|
|
|
## The Road We’ve Travelled
|
2015-10-15 03:51:24 -04:00
|
|
|
|
Here’s what we achieved in this chapter:
|
2015-11-10 13:31:46 -05:00
|
|
|
|
|
|
|
|
|
* Our Tour of Heroes now displays a list of selectable heroes
|
2015-10-15 03:51:24 -04:00
|
|
|
|
* We added the ability to select a hero and show the hero’s details
|
|
|
|
|
* We learned how to use the built-in directives `ng-if`, `ng-for` and `ng-class` in a component’s template
|
2015-11-10 13:31:46 -05:00
|
|
|
|
|
2015-10-16 04:06:56 -04:00
|
|
|
|
### The Road Ahead
|
2015-11-10 13:31:46 -05:00
|
|
|
|
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.
|
2015-10-16 04:06:56 -04:00
|
|
|
|
We’ll learn more about these tasks in the coming tutorial chapters.
|