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.
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
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.
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.
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>
.
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 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.
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 theselector
property of theHeroDetailComponent
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]
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!
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.
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; }
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" } ];
export class Hero { id: number; name: string; }
Let's summarize what we have created.
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