{ "id": "tutorial/toh-pt2", "title": "Display a selection list", "contents": "\n\n\n
\n mode_edit\n
\n\n\n
\n

Display a selection listlink

\n

In this page, you'll expand the Tour of Heroes application to display a list of heroes, and\nallow users to select a hero and display the hero's details.

\n
\n

For the sample application that this page describes, see the .

\n
\n

Create mock heroeslink

\n

You'll need some heroes to display.

\n

Eventually you'll get them from a remote data server.\nFor now, you'll create some mock heroes and pretend they came from the server.

\n

Create a file called mock-heroes.ts in the src/app/ folder.\nDefine a HEROES constant as an array of ten heroes and export it.\nThe file should look like this.

\n\nimport { Hero } from './hero';\n\nexport const HEROES: Hero[] = [\n { id: 11, name: 'Dr Nice' },\n { id: 12, name: 'Narco' },\n { id: 13, name: 'Bombasto' },\n { id: 14, name: 'Celeritas' },\n { id: 15, name: 'Magneta' },\n { id: 16, name: 'RubberMan' },\n { id: 17, name: 'Dynama' },\n { id: 18, name: 'Dr IQ' },\n { id: 19, name: 'Magma' },\n { id: 20, name: 'Tornado' }\n];\n\n\n\n

Displaying heroeslink

\n

Open the HeroesComponent class file and import the mock HEROES.

\n\nimport { HEROES } from '../mock-heroes';\n\n\n

In the same file (HeroesComponent class), define a component property called heroes to expose the HEROES array for binding.

\n\nexport class HeroesComponent implements OnInit {\n\n heroes = HEROES;\n}\n\n\n

List heroes with *ngForlink

\n

Open the HeroesComponent template file and make the following changes:

\n\n

Make it look like this:

\n\n<h2>My Heroes</h2>\n<ul class=\"heroes\">\n <li>\n <span class=\"badge\">{{hero.id}}</span> {{hero.name}}\n </li>\n</ul>\n\n\n

That shows one hero. To list them all, add an *ngFor to the <li> to iterate through the list of heroes:

\n\n<li *ngFor=\"let hero of heroes\">\n\n\n

The *ngFor is Angular's repeater directive.\nIt repeats the host element for each element in a list.

\n

The syntax in this example is as follows:

\n\n
\n

Don't forget the asterisk (*) in front of ngFor. It's a critical part of the syntax.

\n
\n

After the browser refreshes, the list of heroes appears.

\n\n

Style the heroeslink

\n

The heroes list should be attractive and should respond visually when users\nhover over and select a hero from the list.

\n

In the first tutorial, you set the basic styles for the entire application in styles.css.\nThat stylesheet didn't include styles for this list of heroes.

\n

You could add more styles to styles.css and keep growing that stylesheet as you add components.

\n

You may prefer instead to define private styles for a specific component and keep everything a component needs— the code, the HTML,\nand the CSS —together in one place.

\n

This approach makes it easier to re-use the component somewhere else\nand deliver the component's intended appearance even if the global styles are different.

\n

You define private styles either inline in the @Component.styles array or\nas stylesheet file(s) identified in the @Component.styleUrls array.

\n

When the CLI generated the HeroesComponent, it created an empty heroes.component.css stylesheet for the HeroesComponent\nand pointed to it in @Component.styleUrls like this.

\n\n@Component({\n selector: 'app-heroes',\n templateUrl: './heroes.component.html',\n styleUrls: ['./heroes.component.css']\n})\n\n\n

Open the heroes.component.css file and paste in the private CSS styles for the HeroesComponent.\nYou'll find them in the final code review at the bottom of this guide.

\n
\n

Styles and stylesheets identified in @Component metadata are scoped to that specific component.\nThe heroes.component.css styles apply only to the HeroesComponent and don't affect the outer HTML or the HTML in any other component.

\n
\n

Viewing detailslink

\n

When the user clicks a hero in the list, the component should display the selected hero's details at the bottom of the page.

\n

In this section, you'll listen for the hero item click event\nand update the hero detail.

\n

Add a click event bindinglink

\n

Add a click event binding to the <li> like this:

\n\n<li *ngFor=\"let hero of heroes\" (click)=\"onSelect(hero)\">\n\n\n

This is an example of Angular's event binding syntax.

\n

The parentheses around click tell Angular to listen for the <li> element's click event.\nWhen the user clicks in the <li>, Angular executes the onSelect(hero) expression.

\n

In the next section, define an onSelect() method in HeroesComponent to\ndisplay the hero that was defined in the *ngFor expression.

\n

Add the click event handlerlink

\n

Rename the component's hero property to selectedHero but don't assign it.\nThere is no selected hero when the application starts.

\n

Add the following onSelect() method, which assigns the clicked hero from the template\nto the component's selectedHero.

\n\nselectedHero?: Hero;\nonSelect(hero: Hero): void {\n this.selectedHero = hero;\n}\n\n\n

Add a details sectionlink

\n

Currently, you have a list in the component template. To click on a hero on the list\nand reveal details about that hero, you need a section for the details to render in the\ntemplate. Add the following to heroes.component.html beneath the list section:

\n\n<h2>{{selectedHero.name | uppercase}} Details</h2>\n<div><span>id: </span>{{selectedHero.id}}</div>\n<div>\n <label for=\"hero-name\">Hero name: </label>\n <input id=\"hero-name\" [(ngModel)]=\"selectedHero.name\" placeholder=\"name\">\n</div>\n\n\n

After the browser refreshes, the application is broken.

\n

Open the browser developer tools and look in the console for an error message like this:

\n\n HeroesComponent.html:3 ERROR TypeError: Cannot read property 'name' of undefined\n\n

What happened?link

\n

When the application starts, the selectedHero is undefined by design.

\n

Binding expressions in the template that refer to properties of selectedHero—expressions like {{selectedHero.name}}must fail because there is no selected hero.

\n

The fix - hide empty details with *ngIflink

\n

The component should only display the selected hero details if the selectedHero exists.

\n

Wrap the hero detail HTML in a <div>.\nAdd Angular's *ngIf directive to the <div> and set it to selectedHero.

\n
\n

Don't forget the asterisk (*) in front of ngIf. It's a critical part of the syntax.

\n
\n\n<div *ngIf=\"selectedHero\">\n\n <h2>{{selectedHero.name | uppercase}} Details</h2>\n <div><span>id: </span>{{selectedHero.id}}</div>\n <div>\n <label for=\"hero-name\">Hero name: </label>\n <input id=\"hero-name\" [(ngModel)]=\"selectedHero.name\" placeholder=\"name\">\n </div>\n\n</div>\n\n\n

After the browser refreshes, the list of names reappears.\nThe details area is blank.\nClick a hero in the list of heroes and its details appear.\nThe application seems to be working again.\nThe heroes appear in a list and details about the clicked hero appear at the bottom of the page.

\n

Why it workslink

\n

When selectedHero is undefined, the ngIf removes the hero detail from the DOM. There are no selectedHero bindings to consider.

\n

When the user picks a hero, selectedHero has a value and\nngIf puts the hero detail into the DOM.

\n

Style the selected herolink

\n

To help identify the selected hero, you can use the .selected CSS class in the styles you added earlier.\nTo apply the .selected class to the <li> when the user clicks it, use class binding.

\n
\n \"Selected\n
\n

Angular's class binding can add and remove a CSS class conditionally.\nJust add [class.some-css-class]=\"some-condition\" to the element you want to style.

\n

Add the following [class.selected] binding to the <li> in the HeroesComponent template:

\n\n[class.selected]=\"hero === selectedHero\"\n\n\n

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.

\n

The finished <li> looks like this:

\n\n<li *ngFor=\"let hero of heroes\"\n [class.selected]=\"hero === selectedHero\"\n (click)=\"onSelect(hero)\">\n <span class=\"badge\">{{hero.id}}</span> {{hero.name}}\n</li>\n\n\n\n

Final code reviewlink

\n

Here are the code files discussed on this page, including the HeroesComponent styles.

\n\n\n \nimport { Hero } from './hero';\n\nexport const HEROES: Hero[] = [\n { id: 11, name: 'Dr Nice' },\n { id: 12, name: 'Narco' },\n { id: 13, name: 'Bombasto' },\n { id: 14, name: 'Celeritas' },\n { id: 15, name: 'Magneta' },\n { id: 16, name: 'RubberMan' },\n { id: 17, name: 'Dynama' },\n { id: 18, name: 'Dr IQ' },\n { id: 19, name: 'Magma' },\n { id: 20, name: 'Tornado' }\n];\n\n\n\n\n \nimport { Component, OnInit } from '@angular/core';\nimport { Hero } from '../hero';\nimport { HEROES } from '../mock-heroes';\n\n@Component({\n selector: 'app-heroes',\n templateUrl: './heroes.component.html',\n styleUrls: ['./heroes.component.css']\n})\n\nexport class HeroesComponent implements OnInit {\n\n heroes = HEROES;\n selectedHero?: Hero;\n\n constructor() { }\n\n ngOnInit() {\n }\n\n onSelect(hero: Hero): void {\n this.selectedHero = hero;\n }\n}\n\n\n\n\n \n<h2>My Heroes</h2>\n<ul class=\"heroes\">\n <li *ngFor=\"let hero of heroes\"\n [class.selected]=\"hero === selectedHero\"\n (click)=\"onSelect(hero)\">\n <span class=\"badge\">{{hero.id}}</span> {{hero.name}}\n </li>\n</ul>\n\n<div *ngIf=\"selectedHero\">\n\n <h2>{{selectedHero.name | uppercase}} Details</h2>\n <div><span>id: </span>{{selectedHero.id}}</div>\n <div>\n <label for=\"hero-name\">Hero name: </label>\n <input id=\"hero-name\" [(ngModel)]=\"selectedHero.name\" placeholder=\"name\">\n </div>\n\n</div>\n\n\n\n\n \n/* HeroesComponent's private CSS styles */\n.heroes {\n margin: 0 0 2em 0;\n list-style-type: none;\n padding: 0;\n width: 15em;\n}\n.heroes li {\n cursor: pointer;\n position: relative;\n left: 0;\n background-color: #EEE;\n margin: .5em;\n padding: .3em 0;\n height: 1.6em;\n border-radius: 4px;\n}\n.heroes li:hover {\n color: #2c3a41;\n background-color: #e6e6e6;\n left: .1em;\n}\n.heroes li.selected {\n background-color: black;\n color: white;\n}\n.heroes li.selected:hover {\n background-color: #505050;\n color: white;\n}\n.heroes li.selected:active {\n background-color: black;\n color: white;\n}\n.heroes .badge {\n display: inline-block;\n font-size: small;\n color: white;\n padding: 0.8em 0.7em 0 0.7em;\n background-color:#405061;\n line-height: 1em;\n position: relative;\n left: -1px;\n top: -4px;\n height: 1.8em;\n margin-right: .8em;\n border-radius: 4px 0 0 4px;\n}\n\ninput {\n padding: .5rem;\n}\n\n\n\n\n\n

Summarylink

\n\n\n \n
\n\n\n" }