2020-01-13 13:37:43 -05:00
# Display a selection list
2017-03-31 19:57:13 -04:00
2021-01-25 16:39:31 -05:00
In this page, you'll expand the Tour of Heroes application to display a list of heroes, and
2017-03-27 11:08:53 -04:00
allow users to select a hero and display the hero's details.
2017-02-22 13:09:39 -05:00
2020-04-10 13:10:59 -04:00
< div class = "alert is-helpful" >
2021-01-25 16:39:31 -05:00
For the sample application that this page describes, see the < live-example > < / live-example > .
2020-04-10 13:10:59 -04:00
< / div >
2017-02-22 13:09:39 -05:00
2017-11-06 13:02:18 -05:00
## Create mock heroes
2017-02-22 13:09:39 -05:00
2017-11-06 13:02:18 -05:00
You'll need some heroes to display.
2017-02-22 13:09:39 -05:00
2017-11-06 13:02:18 -05:00
Eventually you'll get them from a remote data server.
For now, you'll create some _mock heroes_ and pretend they came from the server.
2017-02-22 13:09:39 -05:00
2017-11-06 13:02:18 -05:00
Create a file called `mock-heroes.ts` in the `src/app/` folder.
Define a `HEROES` constant as an array of ten heroes and export it.
The file should look like this.
2017-03-31 19:57:13 -04:00
2019-07-20 13:40:17 -04:00
< code-example path = "toh-pt2/src/app/mock-heroes.ts" header = "src/app/mock-heroes.ts" > < / code-example >
2017-02-22 13:09:39 -05:00
2017-03-27 11:08:53 -04:00
## Displaying heroes
2017-02-22 13:09:39 -05:00
2017-11-06 13:02:18 -05:00
Open the `HeroesComponent` class file and import the mock `HEROES` .
2017-02-22 13:09:39 -05:00
2018-10-11 07:29:59 -04:00
< code-example path = "toh-pt2/src/app/heroes/heroes.component.ts" region = "import-heroes" header = "src/app/heroes/heroes.component.ts (import HEROES)" >
2017-03-27 11:08:53 -04:00
< / code-example >
2017-02-22 13:09:39 -05:00
2019-01-31 14:25:30 -05:00
In the same file (`HeroesComponent` class), define a component property called `heroes` to expose the `HEROES` array for binding.
2017-03-31 19:57:13 -04:00
2019-01-31 14:25:30 -05:00
< code-example path = "toh-pt2/src/app/heroes/heroes.component.ts" header = "src/app/heroes/heroes.component.ts" region = "component" >
2017-03-27 11:08:53 -04:00
< / code-example >
2017-02-22 13:09:39 -05:00
2019-01-31 14:25:30 -05:00
### List heroes with `*ngFor`
2017-03-31 19:57:13 -04:00
2017-11-06 13:02:18 -05:00
Open the `HeroesComponent` template file and make the following changes:
2017-03-31 19:57:13 -04:00
2018-11-28 13:03:21 -05:00
* Add an `<h2>` at the top,
2017-11-06 13:02:18 -05:00
* Below it add an HTML unordered list (`< ul > `)
* Insert an `<li>` within the `<ul>` that displays properties of a `hero` .
* Sprinkle some CSS classes for styling (you'll add the CSS styles shortly).
2017-02-22 13:09:39 -05:00
2017-11-06 13:02:18 -05:00
Make it look like this:
2017-02-22 13:09:39 -05:00
2019-07-20 13:40:17 -04:00
< code-example path = "toh-pt2/src/app/heroes/heroes.component.1.html" region = "list" header = "heroes.component.html (heroes template)" > < / code-example >
2017-03-27 11:08:53 -04:00
2019-01-31 14:25:30 -05:00
That shows one hero. To list them all, add an `*ngFor` to the `<li>` to iterate through the list of heroes:
2017-03-31 19:57:13 -04:00
2017-11-06 13:02:18 -05:00
< code-example path = "toh-pt2/src/app/heroes/heroes.component.1.html" region = "li" >
2017-03-27 11:08:53 -04:00
< / code-example >
2020-04-28 16:26:58 -04:00
The [`*ngFor` ](guide/built-in-directives#ngFor ) is Angular's _repeater_ directive.
2017-11-06 13:02:18 -05:00
It repeats the host element for each element in a list.
2017-03-27 11:08:53 -04:00
2019-01-31 14:25:30 -05:00
The syntax in this example is as follows:
2017-03-27 11:08:53 -04:00
2019-01-31 14:25:30 -05:00
* `<li>` is the host element.
* `heroes` holds the mock heroes list from the `HeroesComponent` class, the mock heroes list.
2018-11-28 13:03:21 -05:00
* `hero` holds the current hero object for each iteration through the list.
2017-03-31 19:57:13 -04:00
2017-11-06 13:02:18 -05:00
< div class = "alert is-important" >
2017-02-22 13:09:39 -05:00
2017-11-06 13:02:18 -05:00
Don't forget the asterisk (*) in front of `ngFor` . It's a critical part of the syntax.
2017-02-22 13:09:39 -05:00
2017-04-10 11:51:13 -04:00
< / div >
2017-03-27 11:08:53 -04:00
2017-11-06 13:02:18 -05:00
After the browser refreshes, the list of heroes appears.
2017-03-31 19:57:13 -04:00
2017-11-06 13:02:18 -05:00
{@a styles}
2017-03-27 11:08:53 -04:00
### Style the heroes
2018-11-28 13:03:21 -05:00
The heroes list should be attractive and should respond visually when users
2017-11-06 13:02:18 -05:00
hover over and select a hero from the list.
2017-03-27 11:08:53 -04:00
2017-11-06 13:02:18 -05:00
In the [first tutorial ](tutorial/toh-pt0#app-wide-styles ), you set the basic styles for the entire application in `styles.css` .
That stylesheet didn't include styles for this list of heroes.
2017-02-22 13:09:39 -05:00
2017-11-06 13:02:18 -05:00
You could add more styles to `styles.css` and keep growing that stylesheet as you add components.
2017-02-22 13:09:39 -05:00
2017-11-06 13:02:18 -05:00
You may prefer instead to define private styles for a specific component and keep everything a component needs— the code, the HTML,
and the CSS — together in one place.
2017-02-22 13:09:39 -05:00
2017-11-06 13:02:18 -05:00
This approach makes it easier to re-use the component somewhere else
and deliver the component's intended appearance even if the global styles are different.
2017-02-22 13:09:39 -05:00
2017-11-06 13:02:18 -05:00
You define private styles either inline in the `@Component.styles` array or
as stylesheet file(s) identified in the `@Component.styleUrls` array.
2017-02-22 13:09:39 -05:00
2017-11-06 13:02:18 -05:00
When the CLI generated the `HeroesComponent` , it created an empty `heroes.component.css` stylesheet for the `HeroesComponent`
and pointed to it in `@Component.styleUrls` like this.
2017-03-27 11:08:53 -04:00
2017-11-06 13:02:18 -05:00
< code-example path = "toh-pt2/src/app/heroes/heroes.component.ts" region = "metadata"
2018-10-11 07:29:59 -04:00
header="src/app/heroes/heroes.component.ts (@Component)">
2017-02-22 13:09:39 -05:00
< / code-example >
2017-11-06 13:02:18 -05:00
Open the `heroes.component.css` file and paste in the private CSS styles for the `HeroesComponent` .
You'll find them in the [final code review ](#final-code-review ) at the bottom of this guide.
2017-03-31 19:57:13 -04:00
2017-11-06 13:02:18 -05:00
< div class = "alert is-important" >
2017-03-31 19:57:13 -04:00
2017-11-06 13:02:18 -05:00
Styles and stylesheets identified in `@Component` metadata are scoped to that specific component.
The `heroes.component.css` styles apply only to the `HeroesComponent` and don't affect the outer HTML or the HTML in any other component.
2017-02-22 13:09:39 -05:00
2017-04-10 11:51:13 -04:00
< / div >
2017-03-27 11:08:53 -04:00
2021-01-25 16:39:31 -05:00
## Viewing details
2017-03-31 19:57:13 -04:00
2021-01-25 16:39:31 -05:00
When the user clicks a hero in the list, the component should display the selected hero's details at the bottom of the page.
2017-03-31 19:57:13 -04:00
2017-11-06 13:02:18 -05:00
In this section, you'll listen for the hero item click event
and update the hero detail.
2017-02-22 13:09:39 -05:00
2017-11-06 13:02:18 -05:00
### Add a click event binding
2017-02-22 13:09:39 -05:00
2017-11-06 13:02:18 -05:00
Add a click event binding to the `<li>` like this:
2017-02-22 13:09:39 -05:00
2019-07-20 13:40:17 -04:00
< code-example path = "toh-pt2/src/app/heroes/heroes.component.1.html" region = "selectedHero-click" header = "heroes.component.html (template excerpt)" > < / code-example >
2017-02-22 13:09:39 -05:00
2020-04-28 16:26:58 -04:00
This is an example of Angular's [event binding ](guide/event-binding ) syntax.
2017-03-31 19:57:13 -04:00
2017-11-06 13:02:18 -05:00
The parentheses around `click` tell Angular to listen for the `<li>` element's `click` event.
When the user clicks in the `<li>` , Angular executes the `onSelect(hero)` expression.
2017-03-31 19:57:13 -04:00
2019-01-31 14:25:30 -05:00
In the next section, define an `onSelect()` method in `HeroesComponent` to
display the hero that was defined in the `*ngFor` expression.
2017-02-22 13:09:39 -05:00
2017-11-06 13:02:18 -05:00
### Add the click event handler
2017-02-22 13:09:39 -05:00
2017-11-06 13:02:18 -05:00
Rename the component's `hero` property to `selectedHero` but don't assign it.
There is no _selected hero_ when the application starts.
2017-02-22 13:09:39 -05:00
2017-11-06 13:02:18 -05:00
Add the following `onSelect()` method, which assigns the clicked hero from the template
to the component's `selectedHero` .
2017-03-27 11:08:53 -04:00
2019-07-20 13:40:17 -04:00
< code-example path = "toh-pt2/src/app/heroes/heroes.component.ts" region = "on-select" header = "src/app/heroes/heroes.component.ts (onSelect)" > < / code-example >
2017-02-22 13:09:39 -05:00
2019-01-31 14:25:30 -05:00
### Add a details section
2017-03-31 19:57:13 -04:00
2019-01-31 14:25:30 -05:00
Currently, you have a list in the component template. To click on a hero on the list
and reveal details about that hero, you need a section for the details to render in the
template. Add the following to `heroes.component.html` beneath the list section:
2017-03-31 19:57:13 -04:00
2019-07-20 13:40:17 -04:00
< code-example path = "toh-pt2/src/app/heroes/heroes.component.html" region = "selectedHero-details" header = "heroes.component.html (selected hero details)" > < / code-example >
2017-02-22 13:09:39 -05:00
2017-11-06 13:02:18 -05:00
After the browser refreshes, the application is broken.
2017-03-31 19:57:13 -04:00
2017-11-06 13:02:18 -05:00
Open the browser developer tools and look in the console for an error message like this:
2017-02-22 13:09:39 -05:00
2017-11-06 13:02:18 -05:00
< code-example language = "sh" class = "code-shell" >
HeroesComponent.html:3 ERROR TypeError: Cannot read property 'name' of undefined
2017-03-27 11:08:53 -04:00
< / code-example >
2017-02-22 13:09:39 -05:00
2017-11-06 13:02:18 -05:00
#### What happened?
2017-02-22 13:09:39 -05:00
2021-01-25 16:39:31 -05:00
When the application starts, the `selectedHero` is `undefined` _by design_ .
2017-03-27 11:08:53 -04:00
2019-01-31 14:25:30 -05:00
Binding expressions in the template that refer to properties of `selectedHero` — expressions like `{{selectedHero.name}}` — _must fail_ because there is no selected hero.
2017-02-22 13:09:39 -05:00
2018-11-28 13:03:21 -05:00
#### The fix - hide empty details with _*ngIf_
2017-03-31 19:57:13 -04:00
2017-11-06 13:02:18 -05:00
The component should only display the selected hero details if the `selectedHero` exists.
2017-02-22 13:09:39 -05:00
2017-11-06 13:02:18 -05:00
Wrap the hero detail HTML in a `<div>` .
Add Angular's `*ngIf` directive to the `<div>` and set it to `selectedHero` .
2017-02-22 13:09:39 -05:00
2018-11-28 13:03:21 -05:00
2017-11-06 13:02:18 -05:00
< div class = "alert is-important" >
2017-02-22 13:09:39 -05:00
2017-11-06 13:02:18 -05:00
Don't forget the asterisk (*) in front of `ngIf` . It's a critical part of the syntax.
2017-03-27 11:08:53 -04:00
2017-11-06 13:02:18 -05:00
< / div >
2017-03-31 19:57:13 -04:00
2019-07-20 13:40:17 -04:00
< code-example path = "toh-pt2/src/app/heroes/heroes.component.html" region = "ng-if" header = "src/app/heroes/heroes.component.html (*ngIf)" > < / code-example >
2017-03-31 19:57:13 -04:00
2017-11-06 13:02:18 -05:00
After the browser refreshes, the list of names reappears.
The details area is blank.
2019-05-17 01:42:46 -04:00
Click a hero in the list of heroes and its details appear.
2021-01-25 16:39:31 -05:00
The application seems to be working again.
2019-05-17 01:42:46 -04:00
The heroes appear in a list and details about the clicked hero appear at the bottom of the page.
2017-02-22 13:09:39 -05:00
2017-11-06 13:02:18 -05:00
#### Why it works
2017-03-27 11:08:53 -04:00
2019-01-31 14:25:30 -05:00
When `selectedHero` is undefined, the `ngIf` removes the hero detail from the DOM. There are no `selectedHero` bindings to consider.
2017-03-31 19:57:13 -04:00
2017-11-06 13:02:18 -05:00
When the user picks a hero, `selectedHero` has a value and
`ngIf` puts the hero detail into the DOM.
2017-03-31 19:57:13 -04:00
2017-03-27 11:08:53 -04:00
### Style the selected hero
2021-01-25 16:39:31 -05:00
To help identify the selected hero, you can use the `.selected` CSS class in the [styles you added earlier ](#styles ).
To apply the `.selected` class to the `<li>` when the user clicks it, use class binding.
2017-03-30 15:04:18 -04:00
2019-11-11 17:47:51 -05:00
< div class = "lightbox" >
2021-01-25 16:39:31 -05:00
< img src = 'generated/images/guide/toh/heroes-list-selected.png' alt = "Selected hero with dark background and light text that differentiates it from unselected list items" >
2019-11-11 17:47:51 -05:00
< / div >
2017-03-31 19:57:13 -04:00
2021-01-25 16:39:31 -05:00
Angular's [class binding ](guide/attribute-binding#class-binding ) can add and remove a CSS class conditionally.
2017-11-06 13:02:18 -05:00
Just add `[class.some-css-class]="some-condition"` to the element you want to style.
2017-02-22 13:09:39 -05:00
2019-10-26 10:35:03 -04:00
Add the following `[class.selected]` binding to the `<li>` in the `HeroesComponent` template:
2017-02-22 13:09:39 -05:00
2019-07-20 13:40:17 -04:00
< code-example path = "toh-pt2/src/app/heroes/heroes.component.1.html" region = "class-selected" header = "heroes.component.html (toggle the 'selected' CSS class)" > < / code-example >
2017-03-27 11:08:53 -04:00
2017-11-06 13:02:18 -05:00
When the current row hero is the same as the `selectedHero` , Angular adds the `selected` CSS class. When the two heroes are different, Angular removes the class.
2017-03-31 19:57:13 -04:00
2017-11-06 13:02:18 -05:00
The finished `<li>` looks like this:
2017-03-31 19:57:13 -04:00
2019-07-20 13:40:17 -04:00
< code-example path = "toh-pt2/src/app/heroes/heroes.component.html" region = "li" header = "heroes.component.html (list item hero)" > < / code-example >
2017-03-27 11:08:53 -04:00
2017-11-06 13:02:18 -05:00
{@a final-code-review}
2017-03-31 19:57:13 -04:00
2017-11-06 13:02:18 -05:00
## Final code review
2017-03-31 19:57:13 -04:00
2017-11-06 13:02:18 -05:00
Here are the code files discussed on this page, including the `HeroesComponent` styles.
2017-02-22 13:09:39 -05:00
2017-11-06 13:02:18 -05:00
< code-tabs >
2019-06-16 13:36:58 -04:00
< code-pane header = "src/app/mock-heroes.ts" path = "toh-pt2/src/app/mock-heroes.ts" >
< / code-pane >
2019-01-31 14:25:30 -05:00
2018-10-11 07:29:59 -04:00
< code-pane header = "src/app/heroes/heroes.component.ts" path = "toh-pt2/src/app/heroes/heroes.component.ts" >
2017-11-06 13:02:18 -05:00
< / code-pane >
2017-02-22 13:09:39 -05:00
2018-10-11 07:29:59 -04:00
< code-pane header = "src/app/heroes/heroes.component.html" path = "toh-pt2/src/app/heroes/heroes.component.html" >
2017-11-06 13:02:18 -05:00
< / code-pane >
2017-03-31 19:57:13 -04:00
2018-10-11 07:29:59 -04:00
< code-pane header = "src/app/heroes/heroes.component.css" path = "toh-pt2/src/app/heroes/heroes.component.css" >
2017-11-06 13:02:18 -05:00
< / code-pane >
2018-06-11 22:12:17 -04:00
2017-11-06 13:02:18 -05:00
< / code-tabs >
2017-03-31 19:57:13 -04:00
2017-09-27 16:45:47 -04:00
## Summary
2017-03-31 19:57:13 -04:00
2021-01-25 16:39:31 -05:00
* The Tour of Heroes application displays a list of heroes with a detail view.
2017-11-06 13:02:18 -05:00
* The user can select a hero and see that hero's details.
* You used `*ngFor` to display a list.
* You used `*ngIf` to conditionally include or exclude a block of HTML.
* You can toggle a CSS style class with a `class` binding.