📜 ⬆️ ⬇️

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

Part 1 Part 2 Part 3 Part 4


Our application is growing. In this part, focus on reusable components as well as data transfer to components. Let's separate the list of heroes into a separate component and make this component reusable.


Run the application, part 3


Where we stayed


Before continuing our tour of heroes, let's check that our project has the following structure. If this is not the case, you will need to return to the previous chapters.


angular2-tour-of-heroes app app.component.ts main.ts node_modules ... typings ... index.html package.json tsconfig.json typings.json 

Support code conversion and application execution


We need to run a TypeScript compiler so that it can track changes in files and compile it 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.


Creating a Hero Details Component


The list of heroes and detailed information about the hero are in the same component, in the same file. So far, they are small, but each of them can grow. We can get new requirements for one of them, which will require changing only one, but not the other. However, each change carries the risk of errors for the two components and doubles the testing. If there was a need to reuse the detailed information about the hero in another place of our application, we would have to grab a list of heroes.


Our current component violates the single principle of responsibility . This material is just a lesson, but we can do everything right - especially since it is not so difficult. In addition, in the process we will learn more about how to build applications in Angular.


Let's extract the detailed information about the hero into our own component.


Separate detailed information about the hero


Add a new file named hero-detail.component.ts to the app folder and create the HeroDetailComponent , as shown below.


hero-detail.component.ts (original version)


  import {Component, Input} from 'angular2/core'; @Component({ selector: 'my-hero-detail', }) export class HeroDetailComponent { } 

Naming conventions

We would like to understand at a glance which classes are components (by class name) and which files contain components (by file name).


Notice that we have an AppComponent in the file named app.component.ts and our new HeroDetailComponent is in the file named hero-detail.component.ts .


All our compound class names end with "Component". All our compound filenames end in ".component".


We translate file names into "lower case with a dash" (kebab-case), so we do not worry about case sensitivity on the server or in the version control system.


Consider the above code.


We started by importing Angular - Component and Input decorators, because we will need them soon.
We then create metadata with the @Component decorator, where we specify the name of the selector that identifies the component element. We then export the class to make it available to other components.


After finishing here, we import it into the AppComponent and create the appropriate element.
<my-hero-detail> .


Hero Details Template


At the moment, Heroes and Hero Detail views are combined into one template in AppComponent . Let's cut the Hero Detail content from the AppComponent and paste it into the new property of the HeroDetailComponent template.


Earlier we bound the selectedHero.name property in the AppComponent . Our HeroDetailComponent will have a hero property, not a selectedHero property. Thus, we will replace selectedHero with hero throughout our new template. This is our only change. The result will look like this:


hero-detail.component.ts (template)


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

Now our markup of detailed information about the hero exists only in the HeroDetailComponent .


Add hero property


Add the hero property, which we talked about above, to the component class.


  hero: Hero; 

Oh oh. We declared the hero property as a Hero type, but our hero class is in the app.component.ts file. We have two components, each in its own file, which should refer to the Hero class.


We will solve this problem by moving the Hero class from app.component.ts to our own hero.ts file.


hero.ts (Exported Hero class)


  export class Hero { id: number; name: string; } 

We export the Hero class from hero.ts , because we need to refer to it in both component files. Add the following import statement at the top of app.component.ts and hero-detail.component.ts .


hero-detail.component.ts and app.component.ts (Import Hero class)


  import {Hero} from './hero'; 

The hero property is inbound .


You need to tell the HeroDetailComponent component which hero to display to it. Who will tell him this? Parent AppComponent !


AppComponent knows which hero to show: the hero that the user selected from the list. The user's choice is in the selectedHero property.


We will update the AppComponent template so that it associates its selectedHero property with the hero property of our HeroDetailComponent . Binding may look like this:


  <my-hero-detail [hero]="selectedHero"></my-hero-detail> 

Notice that the hero property is a target property — it is in square brackets to the left of (=).


Angular requires the declared target property to be an incoming property. If we do not do this, Angular will refuse to bind and display an error message.


We will explain the input properties in more detail here , we will also explain why the target properties require this special approach, and the source properties do not.

There are several ways to indicate that hero is inbound . We will do this in the preferred way by annotating the hero property with the @Input decorator, which we imported earlier.


  @Input() hero: Hero; 

Learn more about the decorator @Input() in the Attributes Directive chapter.

AppComponent update


Let's return to the AppComponent and teach it to use the HeroDetailComponent .


Let's start by importing the HeroDetailComponent so that you can refer to it.


  import {HeroDetailComponent} from './hero-detail.component'; 

Find a place in the template where we delete the contents of Hero Detail and add an element tag that represents the HeroDetailComponent .


  <my-hero-detail></my-hero-detail> 

my-hero-detail is the name we set in the selector property of the HeroDetailComponent metadata.

These two components will not be coordinated until we associate the selectedHero property of the AppComponent component with the hero property of the HeroDetailComponent component, like this:


  <my-hero-detail [hero]="selectedHero"></my-hero-detail> 

The AppComponent template should look like this:
app.component.ts (Template)


  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> <my-hero-detail [hero]="selectedHero"></my-hero-detail> `, 

Thanks to the linking, HeroDetailComponent should get the hero from AppComponent and display the detailed information of this hero under the list. This information should be updated each time the user selects a new hero.


This is not happening yet!


Click in the list of heroes. No information. We are looking for bugs in the Browser Developer Tools console. No errors.


Looks like Angular ignores a new tag. All this because it really ignores the new tag.


Directive array


The browser ignores HTML tags and attributes unknown to it. So does Angular.


We imported the HeroDetailComponent and used it in the template, but we didn't tell Angular about it.


We talk about this Angular by listing this component in metadata, in an array of directives . Add this array of properties at the bottom of the @Component configuration, immediately after the template and styles .


  directives: [HeroDetailComponent] 

Earned!


When we view our application in a browser, we see a list of heroes. When we choose a hero, we see detailed information about him.


The fundamental change is that we can use this HeroDetailComponent component to display detailed information about a hero somewhere else in the application.


We created our first reusable component!


Application structure overview


Let's check that after refactoring in this chapter, we have the following project structure:


  angular2-tour-of-heroes app app.component.ts hero.ts hero-detail.component.ts main.ts node_modules ... typings ... index.html package.json tsconfig.json typings.json 

The code files that we discussed in this chapter.


app / hero-detail.component.ts
  import {Component, Input} from 'angular2/core'; import {Hero} from './hero'; @Component({ selector: 'my-hero-detail', template: ` <div *ngIf="hero"> <h2>{{hero.name}} details!</h2> <div><label>id: </label>{{hero.id}}</div> <div> <label>name: </label> <input [(ngModel)]="hero.name" placeholder="name"/> </div> </div> ` }) export class HeroDetailComponent { @Input() hero: Hero; } 

app / app.component.ts
  import {Component} from 'angular2/core'; import {Hero} from './hero'; import {HeroDetailComponent} from './hero-detail.component'; @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> <my-hero-detail [hero]="selectedHero"></my-hero-detail> `, 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; } `], directives: [HeroDetailComponent] }) 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" } ]; 

app / hero.ts
  export class Hero { id: number; name: string; } 

The path that we have passed


Let's summarize what we have created.



Run the application, part 3


The way ahead


Our tour of heroes has become more suitable for reuse with shared components.


We still get our hero data (using a stub to get it) in the AppComponent . This is not the best option. We must refactor data access by transferring the data to a separate service, and share this service to the components that need this data.


We will learn to create services in the next chapter .


')

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


All Articles