Angular 2 Beta, Heroes Tour Tutorial Part 3

Original author: Google
  • Transfer
  • Tutorial

Part 1 Part 2 Part 3 Part 4


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.tsto the folder appand create HeroDetailComponentit 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 AppComponentin the file with the name app.component.tsand our new one HeroDetailComponentis 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 - Componentand Inputbecause 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 AppComponentand 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 AppComponentand paste it into a new template property HeroDetailComponent.


Previously, we bound property selectedHero.nameto AppComponent. Ours HeroDetailComponentwill have a property hero, not a property selectedHero. So we will replace selectedHerowith heroeverywhere 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 herowe talked about above to the component class.


    hero: Hero;

Oh oh. We declared the property heroas 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 Herofrom app.component.tsto our own file hero.ts.


hero.ts (Exported Hero Class)


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

We export the class Herofrom hero.tsbecause we need to reference it in both component files. Add the following import statement at the top app.component.tsand 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 HeroDetailComponentwhich hero to display. Who will tell him that? Parent AppComponent!


AppComponentknows 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 AppComponentso that it connects its property selectedHerowith the property of heroours HeroDetailComponent. The binding may look like this:



Note that the property herois 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 herois incoming . We will make this the preferred way by annotating the property with the herodecorator @Inputthat 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 AppComponentand teach how to use it HeroDetailComponent.


Let's start with the import HeroDetailComponentso 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 the selectormetadata property HeroDetailComponent.

These two components will not be coordinated until we associate the property of the selectedHerocomponent AppComponentwith the property of the herocomponent HeroDetailComponent, for example like this:



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


    template:`
          

{{title}}

My Heroes

  • {{hero.id}} {{hero.name}}
`,

Thanks to the binding, I HeroDetailComponentmust get the hero from AppComponentand 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 HeroDetailComponentand 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 templateand 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 HeroDetailComponentto 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.


app / hero-detail.component.ts
    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; }

app / app.component.ts
    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" } ];

app / hero.ts
    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 .


Also popular now: