angular-cn/aio/content/tutorial/toh-pt2.md

258 lines
9.7 KiB
Markdown
Raw Normal View History

2017-11-06 13:02:18 -05:00
# Display a Heroes List
2017-03-31 19:57:13 -04:00
In this page, you'll expand the Tour of Heroes app to display a list of heroes, and
allow users to select a hero and display the hero's details.
2017-11-06 13:02:18 -05:00
## Create mock heroes
2017-11-06 13:02:18 -05:00
You'll need some heroes to display.
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-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
2017-11-06 13:02:18 -05:00
<code-example path="toh-pt2/src/app/mock-heroes.ts" linenums="false"
title="src/app/mock-heroes.ts">
</code-example>
## Displaying heroes
2017-11-06 13:02:18 -05:00
You're about to display the list of heroes at the top of the `HeroesComponent`.
2017-11-06 13:02:18 -05:00
Open the `HeroesComponent` class file and import the mock `HEROES`.
2017-11-06 13:02:18 -05:00
<code-example path="toh-pt2/src/app/heroes/heroes.component.ts" region="import-heroes" title="src/app/heroes/heroes.component.ts (import HEROES)">
</code-example>
In the same file (`HeroesComponent` class), define a component property called `heroes` to expose `HEROES` array for binding.
2017-03-31 19:57:13 -04:00
<code-example path="toh-pt2/src/app/heroes/heroes.component.ts" region="component">
</code-example>
2017-11-06 13:02:18 -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
2017-11-06 13:02:18 -05:00
* Add an `<h2>` at the top,
* 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-11-06 13:02:18 -05:00
Make it look like this:
2017-11-06 13:02:18 -05:00
<code-example path="toh-pt2/src/app/heroes/heroes.component.1.html" region="list" title="heroes.component.html (heroes template)" linenums="false">
</code-example>
2017-11-06 13:02:18 -05:00
Now change the `<li>` to this:
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">
</code-example>
2017-11-06 13:02:18 -05:00
The [`*ngFor`](guide/template-syntax#ngFor) is Angular's _repeater_ directive.
It repeats the host element for each element in a list.
2017-11-06 13:02:18 -05:00
In this example
2017-11-06 13:02:18 -05:00
* `<li>` is the host element
* `heroes` is the list from the `HeroesComponent` class.
* `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-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-04-10 11:51:13 -04:00
</div>
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}
### Style the heroes
2017-11-06 13:02:18 -05:00
The heroes list should be attractive and should respond visually when users
hover over and select a hero from the list.
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-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-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&mdash; the code, the HTML,
and the CSS &mdash;together in one place.
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-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-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-11-06 13:02:18 -05:00
<code-example path="toh-pt2/src/app/heroes/heroes.component.ts" region="metadata"
title="src/app/heroes/heroes.component.ts (@Component)">
</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-04-10 11:51:13 -04:00
</div>
2017-11-06 13:02:18 -05:00
## Master/Detail
2017-03-31 19:57:13 -04:00
2017-11-06 13:02:18 -05:00
When the user clicks a hero in the **master** 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-11-06 13:02:18 -05:00
### Add a click event binding
2017-11-06 13:02:18 -05:00
Add a click event binding to the `<li>` like this:
2017-11-06 13:02:18 -05:00
<code-example path="toh-pt2/src/app/heroes/heroes.component.1.html" region="selectedHero-click" title="heroes.component.html (template excerpt)" linenums="false">
</code-example>
2017-11-06 13:02:18 -05:00
This is an example of Angular's [event binding](guide/template-syntax#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
2017-11-06 13:02:18 -05:00
`onSelect()` is a `HeroesComponent` method that you're about to write.
Angular calls it with the `hero` object displayed in the clicked `<li>`,
the same `hero` defined previously in the `*ngFor` expression.
2017-11-06 13:02:18 -05:00
### Add the click event handler
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-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-11-06 13:02:18 -05:00
<code-example path="toh-pt2/src/app/heroes/heroes.component.ts" region="on-select" title="src/app/heroes/heroes.component.ts (onSelect)" linenums="false">
</code-example>
2017-11-06 13:02:18 -05:00
### Update the details template
2017-03-31 19:57:13 -04:00
2017-11-06 13:02:18 -05:00
The template still refers to the component's old `hero` property which no longer exists.
Rename `hero` to `selectedHero`.
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.html" region="selectedHero-details" title="heroes.component.html (selected hero details)" linenums="false">
</code-example>
2017-11-06 13:02:18 -05:00
### Hide empty details with _*ngIf_
2017-03-31 19:57:13 -04: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-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
</code-example>
2017-11-06 13:02:18 -05:00
Now click one of the list items.
The app seems to be working again.
The heroes appear in a list and details about the clicked hero appear at the bottom of the page.
2017-11-06 13:02:18 -05:00
#### What happened?
2017-11-06 13:02:18 -05:00
When the app starts, the `selectedHero` is `undefined` _by design_.
2017-11-06 13:02:18 -05:00
Binding expressions in the template that refer to properties of `selectedHero` &mdash; expressions like `{{selectedHero.name}}` &mdash; _must fail_ because there is no selected hero.
2017-11-06 13:02:18 -05:00
#### The fix
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-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-11-06 13:02:18 -05:00
<div class="alert is-important">
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-11-06 13:02:18 -05:00
</div>
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.html" region="ng-if" title="src/app/heroes/heroes.component.html (*ngIf)" linenums="false">
</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.
Click a hero and its details appear.
2017-11-06 13:02:18 -05:00
#### Why it works
2017-11-06 13:02:18 -05:00
When `selectedHero` is undefined, the `ngIf` removes the hero detail from the DOM. There are no `selectedHero` bindings to worry about.
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
### Style the selected hero
2017-11-06 13:02:18 -05:00
It's difficult to identify the _selected hero_ in the list when all `<li>` elements look alike.
2017-11-06 13:02:18 -05:00
If the user clicks "Magneta", that hero should render with a distinctive but subtle background color like this:
docs(aio): image sweep (#16609) * fix(aio): allow code blocks to clear floated images Previously the negative margin on the code headings were causing floated images to overlay the start of a code block. Now all code block successfully clear all floated elements. * feat(aio): add a `.clear` class for clearing floating images * fix(aio): tidy up image styles The css rules for `img.right` and `img.left` allow authors easy access to floating an image on the left or right, respectively. The `.image-display` rule which was always found on a figure has been simplified so that all figures have this styling. It is very unlikely that a figure will be used outside the content area; and at this time it seems like `figure` is as good an indicator that we want this kind of styling as anything. Now that images are all tagged with width and height values, we cannot assume to modify these dimensions via CSS as it can cause the image to lose its correct proportions. Until we find a better solition we must set `height` to `auto` when the screen width is below 1300px to ensure that these images maintain their proportions as they get shrunk to fit. * docs(aio): general tidy up of image HTML in guides Previously, the guides have a lot of inline image styling and unnecessary use of the `image-display` css class. Images over 700px are problematic for guide docs, so those have been given specific widths and associated heights. * docs(aio): use correct anchor for "back to the top" link The `#toc` anchor does not work when the page is wide enough that the TOC is floating to the side. * build(aio): add `#top-of-page` to path variants for link checking Since the `#top-of-page` is outside the rendered docs the `checkAnchorLinks` processor doesn't find them as valid targets for links. Adding them as a `pathVariant` solves this problem but will still catch links to docs that do not actually exist. * fix(aio): ensure that headings clear floated images * fix(aio): do not force live-example embedded image to 100% size This made them look too big, generally. Leaving them with no size means that they will look reasonable in large viewports and switch to 100% width in narrow viewports.
2017-05-09 18:53:32 -04:00
<figure>
2017-11-06 13:02:18 -05:00
docs(aio): image sweep (#16609) * fix(aio): allow code blocks to clear floated images Previously the negative margin on the code headings were causing floated images to overlay the start of a code block. Now all code block successfully clear all floated elements. * feat(aio): add a `.clear` class for clearing floating images * fix(aio): tidy up image styles The css rules for `img.right` and `img.left` allow authors easy access to floating an image on the left or right, respectively. The `.image-display` rule which was always found on a figure has been simplified so that all figures have this styling. It is very unlikely that a figure will be used outside the content area; and at this time it seems like `figure` is as good an indicator that we want this kind of styling as anything. Now that images are all tagged with width and height values, we cannot assume to modify these dimensions via CSS as it can cause the image to lose its correct proportions. Until we find a better solition we must set `height` to `auto` when the screen width is below 1300px to ensure that these images maintain their proportions as they get shrunk to fit. * docs(aio): general tidy up of image HTML in guides Previously, the guides have a lot of inline image styling and unnecessary use of the `image-display` css class. Images over 700px are problematic for guide docs, so those have been given specific widths and associated heights. * docs(aio): use correct anchor for "back to the top" link The `#toc` anchor does not work when the page is wide enough that the TOC is floating to the side. * build(aio): add `#top-of-page` to path variants for link checking Since the `#top-of-page` is outside the rendered docs the `checkAnchorLinks` processor doesn't find them as valid targets for links. Adding them as a `pathVariant` solves this problem but will still catch links to docs that do not actually exist. * fix(aio): ensure that headings clear floated images * fix(aio): do not force live-example embedded image to 100% size This made them look too big, generally. Leaving them with no size means that they will look reasonable in large viewports and switch to 100% width in narrow viewports.
2017-05-09 18:53:32 -04:00
<img src='generated/images/guide/toh/heroes-list-selected.png' alt="Selected hero">
2017-11-06 13:02:18 -05:00
</figure>
2017-03-31 19:57:13 -04:00
2017-11-06 13:02:18 -05:00
That _selected hero_ coloring is the work of the `.selected` CSS class in the [styles you added earlier](#styles).
You just have to apply the `.selected` class to the `<li>` when the user clicks it.
2017-03-31 19:57:13 -04:00
2017-11-06 13:02:18 -05:00
The Angular [class binding](guide/template-syntax#class-binding) makes it easy to add and remove a CSS class conditionally.
Just add `[class.some-css-class]="some-condition"` to the element you want to style.
2017-11-06 13:02:18 -05:00
Add the following `[class.selected]` binding to the `<li>` in the `HeroesComponent` template:
2017-11-06 13:02:18 -05:00
<code-example path="toh-pt2/src/app/heroes/heroes.component.1.html" region="class-selected" title="heroes.component.html (toggle the 'selected' CSS class)" linenums="false">
</code-example>
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
2017-11-06 13:02:18 -05:00
<code-example path="toh-pt2/src/app/heroes/heroes.component.html" region="li" title="heroes.component.html (list item hero)" linenums="false">
</code-example>
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
Your app should look like this <live-example></live-example>.
2017-11-06 13:02:18 -05:00
Here are the code files discussed on this page, including the `HeroesComponent` styles.
2017-11-06 13:02:18 -05:00
<code-tabs>
<code-pane title="src/app/heroes/heroes.component.ts" path="toh-pt2/src/app/heroes/heroes.component.ts">
</code-pane>
2017-11-06 13:02:18 -05:00
<code-pane title="src/app/heroes/heroes.component.html" path="toh-pt2/src/app/heroes/heroes.component.html">
</code-pane>
2017-03-31 19:57:13 -04:00
2017-11-06 13:02:18 -05:00
<code-pane title="src/app/heroes/heroes.component.css" path="toh-pt2/src/app/heroes/heroes.component.css">
</code-pane>
2017-11-06 13:02:18 -05:00
</code-tabs>
2017-03-31 19:57:13 -04:00
## Summary
2017-03-31 19:57:13 -04:00
2017-11-06 13:02:18 -05:00
* The Tour of Heroes app displays a list of heroes in a Master/Detail view.
* 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.