📜 ⬆️ ⬇️

Angular 2 Beta, training course "Tour of Heroes" part 2

Part 1 Part 2 Part 3 Part 4


Need more heroes


Our story needs more heroes. We will expand our Heroes Tour app to display a list of heroes in which the user can select a hero and view detailed information about him.


Run the application, part 2


Let's think about what we need to display a list of heroes. First, we need an array that will contain a list of characters to display. Then we need a way to transfer the data from the array to the template.


Where we stayed


Before moving on to the second tour of heroes, you must complete the first part . If you have not done so, go back.


Support code conversion and application execution


We want to run a TypeScript compiler so that it also tracks changes in files and compiles right away, as well as run our web server. We will do this by typing


npm start 

This will keep the application running while we continue to create a Tour of Heroes.


Display of our heroes


Creation of heroes


Let's create an array of ten characters at the bottom of app.component.ts .


app.component.ts (Array of Heroes)


  let 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" } ]; 

HEROES is an array of elements like Hero , a class that we created in the first part. Of course, we want to get this list of heroes from the web service, but let's take small steps, and first we will display the heroes from the mock object (a stub that returns the previously specified data).


Representation of heroes


Let's create a property in AppComponent that will be the source of the heroes to be linked.


app.component.ts (Property - array of heroes)


  public heroes = HEROES; 

We do not need to explicitly define the type for heroes . TypeScript can get it from the HEROES variable.


We could define a list of heroes here in this component class. But we know that in the end we will receive heroes from the web-service. Therefore, it makes sense to immediately remove this data from the class implementation.

Display of characters in the template


Our component contains heroes . Let's create an unordered list in our template to display them. We insert the following piece of HTML under the heading, above the detailed information about the hero.


app.component.ts (Heroes Template)


  <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.


Displaying a list of heroes with ngFor


We want to associate an array of heroes in our component with our template, iterate over it, and display each of the heroes separately. We need some Angular help to do this. Let's start step by step:
First, change the <li> by adding the built-in *ngFor directive.


app.component.ts (ngFor)


  <li *ngFor="#hero of heroes"> 

* The leading asterisk (` ) ngFor` is an important part of the syntax. **

The prefix * for ngFor indicates that the <li> element and its child elements make up the master template.


The ngFor directive goes through the heroes array, which AppComponent.heroes property, and displays them in a pattern.


The text in quotes assigned by ngFor means: “take each character in the heroes array, save it in the local variable hero , and make it available for the corresponding template instance”.


The prefix # before the hero identifies the hero as a local variable of the template. Then in the template we can refer to this variable in order to gain access to the properties of the hero.


You can learn more about ngFor and local variables of a template in the chapters Displaying Data and Template Syntax .


Now we will insert some code between the <li> tags, which uses the hero template variable to display the properties of the hero.


app.component.ts (ngFor template)


  <li *ngFor="#hero of heroes"> <span class="badge">{{hero.id}}</span> {{hero.name}} </li> 

When the browser refreshes the page, we will see a list of heroes!


Adding styles to our heroes


Our list of heroes looks pretty boring. We want to make it visually obvious for the user, so that he understands which hero is selected and which hero is currently over the cursor.


Let's add styles to our component. Let us @Component following CSS classes to the styles property in the @Component decorator:


app.component.ts (Adding Styles)


  styles:[` .selected { background-color: #CFD8DC !important; color: white; } .heroes { margin: 0 0 2em 0; list-style-type: none; padding: 0; width: 15em; } .heroes li { cursor: pointer; position: relative; left: 0; background-color: #EEE; margin: .5em; padding: .3em 0; height: 1.6em; border-radius: 4px; } .heroes li.selected:hover { background-color: #BBD8DC !important; color: white; } .heroes li:hover { color: #607D8B; background-color: #DDD; left: .1em; } .heroes .text { position: relative; top: -3px; } .heroes .badge { display: inline-block; font-size: small; color: white; padding: 0.8em 0.7em 0 0.7em; background-color: #607D8B; line-height: 1em; position: relative; left: -1px; top: -4px; height: 1.8em; margin-right: .8em; border-radius: 4px 0 0 4px; } `] 

Notice that we again use the `-nation for a multi-line representation of a long string.


When we assign styles to a component, they are only in the scope of that particular component. Therefore, our styles will only apply to AppComponent and will not "leak" into external HTML.


Our template for displaying heroes should now look like this:


app.component.ts (Styles for Heroes)


  <h2>My Heroes</h2> <ul class="heroes"> <li *ngFor="#hero of heroes"> <span class="badge">{{hero.id}}</span> {{hero.name}} </li> </ul> 

How many styles! We can include them in the description of the component, as shown here, or move them to a separate file, which will make the code of our component easier. We will do this in the next chapter. For now, leave everything as is.


Hero selection


In our application there is a list of heroes and information about one hero. The list of heroes and one hero are not related to each other. We want the user to select a hero in our list, and information about the selected hero appears in a detailed view. This UI pattern is commonly known as "Master-Detail" (translated as "master-slave", but will later be used as "Master / Detail"). In our case, Master is a list of heroes, and Detail is a detailed view of the selected hero.
Let's connect the Master to Detail through the property of the 'selectedHero' component associated with the click event on the hero in the list.


Click event (mouse click)


Modify <li> by inserting a click event handling using the Angular event binding.


app.component.ts (Capture click event)


  <li *ngFor="#hero of heroes" (click)="onSelect(hero)"> <span class="badge">{{hero.id}}</span> {{hero.name}} </li> 

Focus on event binding:


  (click)="onSelect(hero)" 

The brackets assign the <li> element as the target for the click event. The expression to the right of the equal sign calls the onSelect() method, which is in the AppComponent component, passing the local template variable hero as an argument. This is the same hero variable that we defined earlier in ngFor .


For more information on event binding, see the chapters User Input and Template Syntax .

Adding a click handler


Our event is tied to the onSelect method, which does not yet exist. Add this method to our component. What should this method do? It must write to the variable "selected hero" of our component of the hero that the user clicked on.


So far, our component does not have such a variable, so let's start by adding it.


Appointment of the selected hero


We no longer need the hero property in AppComponent . Replace it with the selectedHero property:


app.component.ts (selectedHero)


  selectedHero: Hero; 

We decided that the hero should not matter until the user chooses the hero, so we will not initialize selectedHero , as we did with the hero .


Now let's add the onSelect method, which writes the selectedHero hero value to the selectedHero property.


app.component.ts (onSelect)


  onSelect(hero: Hero) { this.selectedHero = hero; } 

We need to display the detailed information of the selected hero in our template. At the moment, the template still refers to the old hero property. Let's fix the template to bind to the selectedHero new property.


app.component.ts (Binding to selectedHero's)


  <h2>{{selectedHero.name}} details!</h2> <div><label>id: </label>{{selectedHero.id}}</div> <div> <label>name: </label> <input [(ngModel)]="selectedHero.name" placeholder="name"/> </div> 

Hiding empty data with ngIf


After downloading our application, we see a list of heroes, but the hero is not selected. selectedHero is undefined, that is, it is undefined . That is why we will see the following error message in the browser console:


  EXCEPTION: TypeError: Cannot read property 'name' of undefined in [null] 

As you remember, we display selectedHero.name in the template. The name property does not exist, because the selectedHero variable that contains the property is not defined.


We will solve this problem by removing the detailed information about the hero from the DOM until the hero is selected.


We wrapped HTML, which contains detailed information about the hero, in a <div> . Add the built-in directive ngIf and set its selectedHero property to our component.


app.component.ts (ngIf)


  <div *ngIf="selectedHero"> <h2>{{selectedHero.name}} details!</h2> <div><label>id: </label>{{selectedHero.id}}</div> <div> <label>name: </label> <input [(ngModel)]="selectedHero.name" placeholder="name"/> </div> </div> 

* Remember that the leading asterisk ( ) before 'ngIf' is an important part of the syntax. **

While the variable selectedHero not defined, the ngIf directive removes HTML with detailed information about the hero from the DOM. Thus, we will have neither elements with detailed information about the hero, nor bindings to worry about.


When the user selects a hero in the list, the selectedHero variable gets the value and becomes defined, and ngIf places the data with detailed information about the hero in the DOM and implements nested bindings.


ngIf and `ngFor 'are called" structural directives "because they can change the structure of parts of the DOM. In other words, they define the structure of how Angular maps content to the DOM.

You can learn more about ngIf, ngFor and other structural directives in the chapters Structural Directives and Pattern Syntax .


The browser is updated, and we see a list of characters, but without detailed information about the selected character. NgIf stores it outside the DOM until the selectedHero variable is defined. When we click on a hero in the list, detailed information about the selected hero is displayed. Everything works as we expected.


Styling the selected item


We see information about the selected hero under the list, but we cannot quickly find this hero in the list above. We can fix this by applying the CSS selected class to the corresponding <li> element in the main list. For example, when we select Magenta from the list of heroes, we can make it highlight it visually by changing the background color, as shown here.


heroes-list-selected


We will add a binding property to the class element to set the selected class in the template. We do this through an expression that compares the current selectedHero and hero .
The key is the name of the CSS class ( selected ). The value is true ( true ) if both heroes match and false ( false ) otherwise. We say "apply the selected class, if the characters are the same, delete it, if it is not . "


app.component.ts (Setting CSS class)


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

Notice that the class.selected pattern class.selected enclosed in square brackets ( [] ). This is the syntax for binding properties, binding, in which data flows go in the same direction from the data source (expression hero === selectedHero ) to the class property.


app.component.ts (Styling each character)


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

Learn more about Associating properties in the Template Syntax chapter.

The browser reloads our application. We choose a Magenta hero, and the choice is clearly identified by the background color.


heroes-list


We choose another hero and the background color switches to that hero.


Here are the full contents of app.component.ts for the moment:


app.component.ts
 import {Component} from 'angular2/core'; export class Hero { id: number; name: string; } @Component({ selector: 'my-app', template:` <h1>{{title}}</h1> <h2>My Heroes</h2> <ul class="heroes"> <li *ngFor="#hero of heroes" [class.selected]="hero === selectedHero" (click)="onSelect(hero)"> <span class="badge">{{hero.id}}</span> {{hero.name}} </li> </ul> <div *ngIf="selectedHero"> <h2>{{selectedHero.name}} details!</h2> <div><label>id: </label>{{selectedHero.id}}</div> <div> <label>name: </label> <input [(ngModel)]="selectedHero.name" placeholder="name"/> </div> </div> `, styles:[` .selected { background-color: #CFD8DC !important; color: white; } .heroes { margin: 0 0 2em 0; list-style-type: none; padding: 0; width: 15em; } .heroes li { cursor: pointer; position: relative; left: 0; background-color: #EEE; margin: .5em; padding: .3em 0; height: 1.6em; border-radius: 4px; } .heroes li.selected:hover { background-color: #BBD8DC !important; color: white; } .heroes li:hover { color: #607D8B; background-color: #DDD; left: .1em; } .heroes .text { position: relative; top: -3px; } .heroes .badge { display: inline-block; font-size: small; color: white; padding: 0.8em 0.7em 0 0.7em; background-color: #607D8B; line-height: 1em; position: relative; left: -1px; top: -4px; height: 1.8em; margin-right: .8em; border-radius: 4px 0 0 4px; } `] }) export class AppComponent { title = 'Tour of Heroes'; heroes = HEROES; selectedHero: Hero; onSelect(hero: Hero) { this.selectedHero = hero; } } 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 path that we have passed


Here is what we achieved in this chapter:



Run the application, part 2


The way ahead


Our tour of heroes has grown, but it's far from completion. We cannot put the entire application in one component. We need to break it down into subcomponents and teach them to work together. We will learn how to do this in the next chapter .


')

Source: https://habr.com/ru/post/281727/


All Articles