Angular 2 Beta, Heroes Tour Tutorial Part 3
- Transfer
- Tutorial
Our application is growing. In this part, we will focus on reusable components, as well as transferring data to components. Let's separate the list of heroes into a separate component and make this component suitable for reuse.
Launch the application, part 3
Where we stayed
Before continuing our Hero Tour, let's verify 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 the TypeScript compiler so that it monitors changes in files and immediately compiles, and also starts our web server. We will do it by typing
npm start
This will keep the application running while we continue to create the Hero Tour.
Creating a Hero Detailed Information Component
The list of heroes and detailed information about the hero are in the same component, in one file. So far, they are small, but each of them can grow. We can get new requirements for one of them, which will require a change of only one, but not the other. However, each change carries a risk of error for the two components and doubles the testing. If there was a need to reuse detailed information about the hero elsewhere in our application, we would have to grab a list of heroes.
Our current component violates the single principle of liability . This material is just a lesson, but we can do it 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 with the name hero-detail.component.ts
to the folder app
and create HeroDetailComponent
it as shown below.
hero-detail.component.ts (initial 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).
Pay attention to what we have AppComponent
in the file with the name app.component.ts
and our new one HeroDetailComponent
is in the file with the name hero-detail.component.ts
.
All our compound class names end in "Component". All of our compound file names end in ".component".
We translate the file names to "lower case with 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 decorators - Component
and Input
because we will need them soon.
Then we create metadata with a decorator @Component
, where we specify the name of the selector that identifies the component element. Then we export the class to make it available to other components.
Having finished here, we import it into AppComponent
and create the corresponding element
.
Hero Details Template
At the moment, Heroes and Hero Detail views are combined into one template in AppComponent
. Let's clip the contents of Hero Detail from AppComponent
and paste it into a new template property HeroDetailComponent
.
Previously, we bound property selectedHero.name
to AppComponent
. Ours HeroDetailComponent
will have a property hero
, not a property selectedHero
. So we will replace selectedHero
with hero
everywhere in our new template. This is our only change. The result will look like this:
hero-detail.component.ts (template)
template: `
{{hero.name}} details!
{{hero.id}}
`
Now our markup of detailed information about the hero exists only in HeroDetailComponent
.
Adding a hero property
Add the property hero
we talked about above to the component class.
hero: Hero;
Oh oh. We declared the property hero
as a type Hero
, but our hero class is in the file app.component.ts
. We have two components, each of which in its own file, which must refer to the class Hero
.
We will solve this problem by moving the class Hero
from app.component.ts
to our own file hero.ts
.
hero.ts (Exported Hero Class)
export class Hero {
id: number;
name: string;
}
We export the class Hero
from hero.ts
because we need to reference it in both component files. Add the following import statement at the top 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 component HeroDetailComponent
which hero to display. Who will tell him that? Parent AppComponent
!
AppComponent
knows which hero to show: the hero that the user selected from the list. User selection is in the property selectedHero
.
We will update the template AppComponent
so that it connects its property selectedHero
with the property of hero
ours HeroDetailComponent
. The binding may look like this:
Note that the property hero
is the 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, Angular will refuse to bind and give 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, but the source properties do not.
There are several ways to indicate what hero
is incoming . We will make this the preferred way by annotating the property with the hero
decorator @Input
that we imported earlier.
@Input()
hero: Hero;
You can learn more about the decorator @Input()
in the chapter of the Attributes Directive .
AppComponent Update
Let's go back to AppComponent
and teach how to use it HeroDetailComponent
.
Let's start with the import HeroDetailComponent
so that you can refer to it.
import {HeroDetailComponent} from './hero-detail.component';
Find the place in the template where we deleted the contents of Hero Detail and add the tag for the element that represents HeroDetailComponent
.
my-hero-detail is the name we set in theselector
metadata propertyHeroDetailComponent
.
These two components will not be coordinated until we associate the property of the selectedHero
component AppComponent
with the property of the hero
component HeroDetailComponent
, for example like this:
The template AppComponent
should look like this:
app.component.ts (Template)
template:`
{{title}}
My Heroes
- {{hero.id}} {{hero.name}}
`,
Thanks to the binding, I HeroDetailComponent
must get the hero from AppComponent
and display the detailed information of this hero under the list. This information should be updated every time the user selects a new hero.
This is not happening yet!
We click in the list of heroes. No information. We are looking for errors in the "Browser Developer Tools" console. There are no errors.
It looks like Angular is ignoring the new tag. All this because he really ignores the new tag.
Array of directives
The browser ignores HTML tags and attributes unknown to it. Angular does the same.
We imported HeroDetailComponent
and used it in the template, but we did not tell Angular about it.
We talk about this Angular by listing this component in metadata, in an array directives
. Add this array of properties at the bottom of the configuration @Component
, immediately after template
and styles
.
directives: [HeroDetailComponent]
Earned!
When we view our application in a browser, we see a list of heroes. When we select a hero, we see detailed information about him.
The fundamental change is that we can use this component HeroDetailComponent
to display detailed information about the hero somewhere else in the application.
We created our first reusable component!
Application Structure Overview
Let's verify 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 we discussed in this chapter.
import {Component, Input} from 'angular2/core';
import {Hero} from './hero';
@Component({
selector: 'my-hero-detail',
template: `
{{hero.name}} details!
{{hero.id}}
`
})
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:`
{{title}}
My Heroes
- {{hero.id}} {{hero.name}}
`,
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;
}
The path we walked
Let's take stock of what we have created.
- We have created a component that can be reused.
- We learned how to make the component accept input.
- We learned how to bind a parent component to a child component.
- We learned how to declare the necessary application directives in an array
directives
.
Launch the application, part 3
Upcoming path
Our heroes tour has become more reusable with shared components.
We still get our hero data (using a stub to retrieve them) c AppComponent
. This is not the best option. We must refactor access to the data, taking out the receipt of data in a separate service, and share this service with the components that need this data.
We will learn how to create services in the next chapter .