docs(tutorial): Part 3 improvements + input explanation

Updated Attribute Directives chapter with appendix on input properties
closes #576
This commit is contained in:
Ward Bell 2015-12-16 19:38:07 -08:00
parent 597e20a374
commit f4acc0ccbc
13 changed files with 277 additions and 197 deletions

View File

@ -9,13 +9,14 @@
</div> </div>
<!-- #docregion span --> <!-- #docregion span -->
<p><span [myHighlight]="color">Highlight me!</span></p> <p [myHighlight]="color">Highlight me!</p>
<!-- #enddocregion span --> <!-- #enddocregion span -->
<!-- #enddocregion v2 --> <!-- #enddocregion v2 -->
<!-- #docregion defaultColor --> <!-- #docregion defaultColor -->
<p><span [myHighlight]="color" [defaultColor]="'violet'"> <p [myHighlight]="color" [defaultColor]="'violet'">
Highlight me too! Highlight me too!
</span></p> </p>
<!-- #enddocregion defaultColor --> <!-- #enddocregion defaultColor -->
<!-- #enddocregion --> <!-- #enddocregion -->

View File

@ -1,11 +1,11 @@
// #docregion // #docregion
import {Component} from 'angular2/core'; import {Component} from 'angular2/core';
import {Highlight} from './highlight.directive' import {HighlightDirective} from './highlight.directive'
@Component({ @Component({
selector: 'my-app', selector: 'my-app',
templateUrl: 'app/app.component.html', templateUrl: 'app/app.component.html',
directives: [Highlight] directives: [HighlightDirective]
}) })
export class AppComponent { } export class AppComponent { }

View File

@ -5,7 +5,7 @@ import {Directive, ElementRef, Renderer, Input} from 'angular2/core';
selector: '[myHighlight]' selector: '[myHighlight]'
}) })
export class Highlight { export class HighlightDirective {
constructor(el: ElementRef, renderer: Renderer) { constructor(el: ElementRef, renderer: Renderer) {
//el.nativeElement.style.backgroundColor = 'yellow'; //el.nativeElement.style.backgroundColor = 'yellow';
renderer.setElementStyle(el, 'backgroundColor', 'yellow'); renderer.setElementStyle(el, 'backgroundColor', 'yellow');

View File

@ -11,7 +11,7 @@ import {Directive, ElementRef, Renderer, Input} from 'angular2/core';
// #enddocregion host // #enddocregion host
}) })
export class Highlight { export class HighlightDirective {
// #docregion ctor // #docregion ctor
constructor(private el: ElementRef, private renderer: Renderer) { constructor(private el: ElementRef, private renderer: Renderer) {
} }

View File

@ -11,7 +11,7 @@ import {Directive, ElementRef, Renderer, Input} from 'angular2/core';
}) })
// #docregion class-1 // #docregion class-1
export class Highlight { export class HighlightDirective {
// #enddocregion class-1 // #enddocregion class-1
// #enddocregion full // #enddocregion full
/* /*

View File

@ -1,8 +0,0 @@
<!-- #docregion component-1 -->
<my-hero-detail></my-hero-detail>
<!-- #enddocregion component-1 -->
<!-- #docregion component-2 -->
<my-hero-detail [hero]="selectedHero"></my-hero-detail>
<!-- #enddocregion component-2 -->

View File

@ -1,41 +0,0 @@
// #docregion hero-detail-component-1
@Component({
selector: 'my-hero-detail'
})
export class HeroDetailComponent { }
// #enddocregion hero-detail-component-1
// #docregion hero-detail-component-2
@Component({
selector: 'my-hero-detail',
template: ``
})
export class HeroDetailComponent { }
// #enddocregion hero-detail-component-2
// #docregion hero-detail-component-3
@Component({
selector: 'my-hero-detail',
template: `
<div *ngIf="hero">
<h2>{{selected.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 { }
// #enddocregion hero-detail-component-3
// #docregion imports-1
import {Component} from 'angular2/core';
// #enddocregion imports-1
// #docregion hero-property
export class HeroDetailComponent {
public hero: Hero;
}
// #enddocregion hero-property

View File

@ -1,3 +1,4 @@
//#docregion
import {Component} from 'angular2/core'; import {Component} from 'angular2/core';
// #docregion hero-import // #docregion hero-import
import {Hero} from './hero'; import {Hero} from './hero';
@ -38,9 +39,9 @@ import {HeroDetailComponent} from './hero-detail.component';
} }
.selected { background-color: #EEE; color: #369; } .selected { background-color: #EEE; color: #369; }
`], `],
// #docregion declaring // #docregion directives
directives: [HeroDetailComponent] directives: [HeroDetailComponent]
// #enddocregion declaring // #enddocregion directives
}) })
export class AppComponent { export class AppComponent {
public title = 'Tour of Heroes'; public title = 'Tour of Heroes';

View File

@ -1,9 +1,17 @@
// #docregion hero-detail-component // #docplaster
// #docregion
// #docregion v1
import {Component} from 'angular2/core'; import {Component} from 'angular2/core';
// #enddocregion v1
// #docregion hero-import
import {Hero} from './hero';
// #enddocregion hero-import
// #docregion inputs // #docregion v1
@Component({ @Component({
selector: 'my-hero-detail', selector: 'my-hero-detail',
// #enddocregion v1
// #docregion template
template: ` template: `
<div *ngIf="hero"> <div *ngIf="hero">
<h2>{{hero.name}} details!</h2> <h2>{{hero.name}} details!</h2>
@ -14,10 +22,17 @@ import {Component} from 'angular2/core';
</div> </div>
</div> </div>
`, `,
// #enddocregion template
// #docregion inputs
inputs: ['hero'] inputs: ['hero']
// #enddocregion inputs
// #docregion v1
}) })
// #enddocregion inputs
export class HeroDetailComponent { export class HeroDetailComponent {
// #enddocregion v1
// #docregion hero
public hero: Hero; public hero: Hero;
// #enddocregion hero
// #docregion v1
} }
// #enddocregion hero-detail-component // #enddocregion v1

View File

@ -1,6 +1,6 @@
// #docregion hero-interface // #docregion
export interface Hero { export interface Hero {
id: number; id: number;
name: string; name: string;
} }
// #enddocregion hero-interface // #enddocregion

View File

@ -54,21 +54,10 @@ include ../../../../_includes/_util-fns
Let's build a small illustrative example together. Let's build a small illustrative example together.
### Setup :marked
### Our first draft
Create a new project folder (`attribute-directives`) and follow the steps in the [QuickStart](../quickstart.html). Create a new project folder (`attribute-directives`) and follow the steps in the [QuickStart](../quickstart.html).
As in the [tutorial](/docs/ts/latest/tutorial/), we'll rename `app.ts` to `app.component.ts`
and relocate the call to `bootstrap` to a separate `boot.ts` file.
+makeExample('attribute-directives/ts/app/boot.ts', null, 'app/boot.ts')
:marked
A clean `app.component.ts` without bootstrapping is much easier to test.
Finally, we remember to update `index.html` to load `boot.ts`
code-example.
System.import('app/boot');
:marked
### Write the directive
Add a new file to the `app` folder called `highlight.directive.ts` and add the following code: Add a new file to the `app` folder called `highlight.directive.ts` and add the following code:
+makeExample('attribute-directives/ts/app/highlight.directive.1.ts', null, 'app/highlight.directive.ts') +makeExample('attribute-directives/ts/app/highlight.directive.1.ts', null, 'app/highlight.directive.ts')
@ -98,28 +87,30 @@ code-example.
The `ng` prefix belongs to Angular. The `ng` prefix belongs to Angular.
We need a prefix of our own, preferably short, and `my` will do for now. We need a prefix of our own, preferably short, and `my` will do for now.
:marked :marked
After the `@Component` metadata comes the directive's controller class which we are exporting After the `@Directive` metadata comes the directive's controller class which we are exporting
to make it accessible to other components. to make it accessible to other components.
The directive's controller class contains the logic for our directive. The directive's controller class contains the logic for the directive.
Angular creates a new instance of the directive's controller class for Angular creates a new instance of the directive's controller class for
each matching element, injecting an *Element Reference* and each matching element, injecting an *Element Reference* and
the *Renderer* service as arguments to our constructor. the *Renderer* service as arguments to the constructor.
We'll need them to set the element's background color. We'll need those *services* to set the element's background color.
Our code shows two ways to do that. Our code shows two ways to set the color.
We could access the `nativeElement` property of the element reference We could access the `nativeElement` property of the element reference
and set the element's background using the browser DOM API. We don't need and set the element's background color using the browser DOM API. We don't need
the `Renderer` for this technique. But we commented it out. the `Renderer` for this approach.
We chose the second way, the preferred way, that relies on the `Renderer` service We commented this technique out. It works. But we don't like it.
We prefer the second way that relies on the `Renderer` service
to set the element properties. to set the element properties.
.l-sub-section .l-sub-section
:marked :marked
### Why prefer the Renderer? ### Why prefer the Renderer?
Manipulating the DOM directly is a practice we should *avoid* because it chains us Manipulating the DOM directly is a practice we'd rather *avoid* because it chains us
to the browser DOM API. to the browser DOM API.
The `Renderer` insulates our code from the browser's API. The `Renderer` insulates our code from the browser's API.
@ -134,13 +125,13 @@ code-example.
.l-main-section .l-main-section
:marked :marked
## Apply the attribute directive ## Apply the attribute directive
The `AppComponent` will be the test harness for our `highlight` directive. The `AppComponent` will be the test harness for our `HighlightDirective`.
Let's give it a new template that Let's give it a new template that
applies the directive as an attribute to a `span` element. applies the directive as an attribute to a `span` element.
In Angular terms, the `<span>` element will be the attribute **host**. In Angular terms, the `<span>` element will be the attribute **host**.
We'll put the template in its own `app.component.html` file that looks like this: We'll put the template in its own `app.component.html` file that looks like this:
+makeExample('attribute-directives/ts/app/app.component.1.html',null,'app/app.component.html') +makeExample('attribute-directives/ts/app/app.component.1.html',null,'app/app.component.html')(format=".")
:marked :marked
A separate template file is clearly overkill for a 2-line template. A separate template file is clearly overkill for a 2-line template.
Hang in there; we're going to expand it later. Hang in there; we're going to expand it later.
@ -160,7 +151,7 @@ figure.image-display
Let's recap what happened. Let's recap what happened.
Angular found the `myHighlight` attribute on the `<span>` element. It created Angular found the `myHighlight` attribute on the `<span>` element. It created
an instance of the `Highlight` directive class, an instance of the `HighlightDirective` class,
injecting both a reference to the element and the `Renderer` service into the constructor. injecting both a reference to the element and the `Renderer` service into the constructor.
The constructor told the `Renderer` to set the `<span>` element's background style to yellow. The constructor told the `Renderer` to set the `<span>` element's background style to yellow.
@ -179,7 +170,7 @@ figure.image-display
Start with event detection. Start with event detection.
We add a `host` property to the directive metadata and give it a configuration object We add a `host` property to the directive metadata and give it a configuration object
that specifies two mouse events and the directive methods to call when they are raised. that specifies two mouse events and the directive methods to call when they are raised.
+makeExample('attribute-directives/ts/app/highlight.directive.2.ts','host') +makeExample('attribute-directives/ts/app/highlight.directive.2.ts','host')(format=".")
:marked :marked
.l-sub-section .l-sub-section
:marked :marked
@ -196,7 +187,7 @@ figure.image-display
Let's roll with the `host` property. Let's roll with the `host` property.
:marked :marked
Now we implement those two mouse event handlers: Now we implement those two mouse event handlers:
+makeExample('attribute-directives/ts/app/highlight.directive.2.ts','mouse-methods') +makeExample('attribute-directives/ts/app/highlight.directive.2.ts','mouse-methods')(format=".")
:marked :marked
Notice that they delegate to a helper method that calls the `Renderer` service Notice that they delegate to a helper method that calls the `Renderer` service
as we used to do in the constructor. as we used to do in the constructor.
@ -205,7 +196,7 @@ figure.image-display
we still want the injected `ElementRef` and `Renderer` service. we still want the injected `ElementRef` and `Renderer` service.
We revise the constructor signature to capture the injectables in private variables We revise the constructor signature to capture the injectables in private variables
and clear the body. and clear the body.
+makeExample('attribute-directives/ts/app/highlight.directive.2.ts','ctor') +makeExample('attribute-directives/ts/app/highlight.directive.2.ts','ctor')(format=".")
:marked :marked
Here's the updated directive: Here's the updated directive:
+makeExample('attribute-directives/ts/app/highlight.directive.2.ts',null, 'app/highlight.directive.ts') +makeExample('attribute-directives/ts/app/highlight.directive.2.ts',null, 'app/highlight.directive.ts')
@ -228,6 +219,7 @@ figure.image-display
Here is the final version of the class: Here is the final version of the class:
+makeExample('attribute-directives/ts/app/highlight.directive.ts', 'class-1', 'app/highlight.directive.ts (class only)') +makeExample('attribute-directives/ts/app/highlight.directive.ts', 'class-1', 'app/highlight.directive.ts (class only)')
<a id="input"></a>
:marked :marked
The new `highlightColor` property is called an "input" property because data flows from the binding expression into our directive. The new `highlightColor` property is called an "input" property because data flows from the binding expression into our directive.
Notice that we call the `@Input()` decorator function while defining the property. Notice that we call the `@Input()` decorator function while defining the property.
@ -235,8 +227,10 @@ figure.image-display
:marked :marked
This `@Input` decorator adds metadata to the class that makes the `highlightColor` property available for property binding This `@Input` decorator adds metadata to the class that makes the `highlightColor` property available for property binding
under the `myHighlight` alias. under the `myHighlight` alias.
We must add this input metadata. Angular will give us an error if we try to bind We must add this input metadata.
to a property without declaring it as an input. Angular will reject a binding to this property if we don't declare it as an input.
See the [appendix](#why-input) below to learn why.
.l-sub-section .l-sub-section
:marked :marked
The developer who uses our directive expects to bind to the attribute name, `myHighlight`. The developer who uses our directive expects to bind to the attribute name, `myHighlight`.
@ -295,7 +289,7 @@ figure.image-display
Let's let the template developer set the default color, the color that prevails until the user picks a highlight color. Let's let the template developer set the default color, the color that prevails until the user picks a highlight color.
We'll add a second **input** property to `HighlightDirective` called `defaultColor`: We'll add a second **input** property to `HighlightDirective` called `defaultColor`:
+makeExample('attribute-directives/ts/app/highlight.directive.ts', 'defaultColor') +makeExample('attribute-directives/ts/app/highlight.directive.ts', 'defaultColor')(format=".")
:marked :marked
The `defaultColor` property has a setter that overrides the hard-coded default color, "red". The `defaultColor` property has a setter that overrides the hard-coded default color, "red".
We don't need a getter. We don't need a getter.
@ -305,11 +299,11 @@ figure.image-display
Remember that a *component is a directive too*. Remember that a *component is a directive too*.
We can add as many component property bindings as we need by stringing them along in the template We can add as many component property bindings as we need by stringing them along in the template
as in this example that sets the `a`, `b`, `c` properties to the string literals 'a', 'b', and 'c'. as in this example that sets the `a`, `b`, `c` properties to the string literals 'a', 'b', and 'c'.
``` code-example(format="." ).
<my-component [a]="'a'" [b]="'b'" [c]="'c'"><my-component> &lt;my-component [a]="'a'" [b]="'b'" [c]="'c'">&lt;my-component>
``` :marked
We do the same thing with an attribute directive. We do the same thing with an attribute directive.
+makeExample('attribute-directives/ts/app/app.component.html', 'defaultColor') +makeExample('attribute-directives/ts/app/app.component.html', 'defaultColor')(format=".")
:marked :marked
Here we're binding the user's color choice to the `myHighlight` attribute as we did before. Here we're binding the user's color choice to the `myHighlight` attribute as we did before.
We're *also* binding the literal string, 'violet', to the `defaultColor`. We're *also* binding the literal string, 'violet', to the `defaultColor`.
@ -343,3 +337,45 @@ figure.image-display
boot.ts, boot.ts,
index.html index.html
`) `)
<a id="why-input"></a>
.l-main-section
:marked
### Appendix: Input properties
Earlier we declared the `highlightColor` property to be an ***input*** property of our
`HighlightDirective`
We've seen properties in bindings before.
We never had to declare them as anything. Why now?
Angular makes a subtle but important distinction between binding **sources** and **targets**.
In all previous bindings, the directive or component property was a binding ***source***.
A property is a *source* if it appears in the template expression to the ***right*** of the (=).
A property is a *target* when it appears to the ***left** of the (=) ...
as it is does when we bind to the `myHighlight` property of the `HighlightDirective`,
+makeExample('attribute-directives/ts/app/app.component.html','span')(format=".")
:marked
The 'color' in `[myHighlight]="color"` is a binding ***source***.
A source property doesn't require a declaration.
The 'myHighlight' in `[myHighlight]="color"` *is* a binding ***target***.
We must declare it as an *input* property.
Angular rejects the binding with a clear error if we don't.
Angular treats a *target* property differently for a good reason.
A component or directive in target position needs protection.
Imagine that our `HighlightDirective` did truly wonderous things.
We graciously made a gift of it to the world.
To our surprise, some people &mdash; perhaps naively &mdash;
started binding to *every* property of our directive.
Not just the one or two properties we expected them to target. *Every* property.
That could really mess up our directive in ways we didn't anticipate and have no desire to support.
The *input* declaration ensures that consumers of our directive can only bind to
the properties of our public API ... nothing else.

View File

@ -27,117 +27,176 @@ include ../../../../_includes/_util-fns
We want to start the TypeScript compiler, have it watch for changes, and start our server. We'll do this by typing We want to start the TypeScript compiler, have it watch for changes, and start our server. We'll do this by typing
code-example(format="." language="bash"). code-example(format="." language="bash").
npm run go npm start
:marked :marked
This will keep the application running while we continue to build the Tour of Heroes. This will keep the application running while we continue to build the Tour of Heroes.
## Making a Hero Detail Component ## Making a Hero Detail Component
Our heroes list and our hero details are all in the same component. What if we want to reuse the hero details somewhere else in our app? This would be difficult since it is intermixed with the heroes list. Lets make this easier and separate the hero details into its own component to make this more reusable. Our heroes list and our hero details are in the same component in the same file.
They're small now but each could grow.
We are sure to receive new requirements for one and not the other.
Yet every change puts both components at risk and doubles the testing burden without benefit.
If we had to reuse the hero details elsewhere in our app,
the heroes list would tag along for the ride.
Our current component violates the
[Single Responsibility Principle](https://blog.8thlight.com/uncle-bob/2014/05/08/SingleReponsibilityPrinciple.html).
It's only a tutorial but we can still do things right &mdash;
especially if doing them right is easy and we learn how to build Angular apps in the process.
Lets break the hero details out into its own component.
### Separating the Hero Detail Component ### Separating the Hero Detail Component
Well need a new file and a new component to host our hero details. Lets create a new file named `hero-detail.component.ts` in the `app` folder, and create a component named `HeroDetailComponent`. Add a new file named `hero-detail.component.ts` to the `app` folder and create `HeroDetailComponent` as follows.
+makeExample('toh-3/ts-snippets/hero-detail.component.pt3.ts', 'hero-detail-component-1', 'hero-detail.component.ts (HeroDetailComponent)')
:marked
We want to use `HeroDetailComponent` from our original `AppComponent`. Well need the selector name when we refer to it in `AppComponent`s template. We export the `HeroDetailComponent` here so we can later import it into our `AppComponent`, which well do after we finish creating our `HeroDetailComponent`.
We anticipate our template will contain multiple lines. So lets initialize the `template` property to an empty string between back-ticks.
+makeExample('toh-3/ts-snippets/hero-detail.component.pt3.ts', 'hero-detail-component-2', 'hero-detail.component.ts (Empty template)')
:marked
Remember, we want to refer to our `HeroDetailComponent` in the `AppComponent`. This is why we export the `HeroDetailComponent` class.
#### Hero Detail Template
Our heroes and hero details are combined in one template in `AppComponent` so we need to separate them. Lets move the appropriate template content from `AppComponent` and paste it in the template property of `HeroDetailComponent`.
Lets also change the name of the property in the template from `selectedHero` to `hero`, as it is more appropriate for a reusable component.
+makeExample('toh-3/ts-snippets/hero-detail.component.pt3.ts', 'hero-detail-component-3', 'hero-detail.component.ts (Detail template)')
:marked
Now our hero detail template exists only in our `HeroDetailComponent`.
#### Importing
Now that we have the foundation for the component, we need to make sure that we import everything we are using in the component. Lets add the following import statement to the top of our `hero-detail.component.ts` file to get the exports from Angular that we are using.
+makeExample('toh-3/ts-snippets/hero-detail.component.pt3.ts', 'imports-1', 'hero-detail.component.ts (Importing Component)')
:marked
#### Declaring our Hero
Our `HeroDetailComponent`s template refers to a hero, so lets add a property on the component to hold the hero.
+makeExample('toh-3/ts-snippets/hero-detail.component.pt3.ts', 'hero-property', 'hero-detail.component.ts (Hero property)')
:marked
Uh oh. We declare the `hero` property as being of type `Hero` but our `Hero` interface is over in the `app.component.ts` file. We now have two components, each in their own file, that need to reference the `Hero` interface. Lets solve this problem by removing the `Hero` interface from `app.component.ts` and moving it to its own file named `hero.ts`.
+makeExample('toh-3/ts/app/hero.ts', 'hero-interface', 'hero.ts (Exported Hero interface)')
:marked
We export the `Hero` interface from `hero.ts` because we will need to import it in both of our component files. Lets add the following import statement to the top of both `app.component.ts` and `hero.-detail.component.ts`.
+makeExample('toh-3/ts/app/app.component.ts', 'hero-import', 'hero-detail.component.ts and app.component.ts (Import the Hero interface)')
:marked
Now we can also use the `Hero` interface from other files by importing it.
#### Defining the Input for HeroDetailComponent
Our `HeroDetailComponent` needs to be told what hero to use. We have a `hero` property, but we need a way for the `AppComponent` to tell the `HeroDetailComponent` the hero it should use.
Lets declare the inputs for our component in the `@Component` decorators `inputs` property. We will set the input to the `hero` property so it matches the `hero` property on the `HeroDetailComponent`.
+makeExample('toh-3/ts/app/hero-detail.component.ts', 'inputs', 'hero-detail.component.ts (Component input)')
:marked
Now our `AppComponent`, or any component that refers to `HeroDetailComponent`, can tell the `HeroDetailComponent` which hero to use.
### Making AppComponent Refer to the HeroDetailComponent
Our `HeroDetailComponent` is ready, but we need to go back to the `AppComponent` and clean up some loose ends.
First we need to tell our `AppComponent` about our new component. Lets add an import statement so we can refer to the `HeroDetailComponent`.
+makeExample('toh-3/ts/app/app.component.ts', 'hero-detail-import', 'app.component.ts (Importing HeroDetailComponent)')
:marked
Lets find the location of the template content we removed from `AppComponent` and refer to our new component.
+makeExample('toh-3/ts-snippets/app.component.pt3.html', 'component-1', 'app.component.ts (Using HeroDetailComponent)')
:marked
This would be good enough if the component did not have any inputs. But we do, so we want to pass the selected hero to the `hero` input of the `HeroDetailComponent`, as shown below:
+makeExample('toh-3/ts-snippets/app.component.pt3.html', 'component-2', 'app.component.ts (Passing the hero to HeroDetailComponent)')
:marked
### Declaring HeroDetailComponent
We've imported `HeroDetailComponent`, we've used it in the template, but we haven't told our `AppComponent` that we intend to use it in the template. We must declare our intention to use `HeroDetailComponent` in `AppComponent`.
Let's add the `directives` property to our `@Component` decorator and set it to an array which contains the `HeroDetailComponent`. We'll do this after our `template` and `styles` properties in the `@Component` decorator.
+makeExample('toh-3/ts/app/app.component.ts', 'declaring', 'app.component.ts (Declaring HeroDetailComponent)')
:marked
Our `AppComponent`s template should now look like this
+makeExample('toh-3/ts/app/app.component.ts', 'hero-detail-template', 'app.component.ts (Template)')
+makeExample('toh-3/ts/app/hero-detail.component.ts', 'v1', 'hero-detail.component.ts (initial version)')(format=".")
.l-sub-section .l-sub-section
:marked :marked
Naming conventions can be useful. ### Naming conventions
We want to identify which files are components. Our `AppComponent` is named `app.component.ts` and our `HeroDetailComponent` is `hero-detail.component.ts`. We follow this consistent pattern which makes it easy to know what is in each file, by following a naming convention where we identify which files contain a component. We like to identify at a glance which classes are components and which files contain components.
Notice that we have an `AppComponent` in a file named `app.component.ts` and our new
`HeroDetailComponent` is in a file named `hero-detail.component.ts`.
All of our component names end in "Component". All of our component file names end in ".component".
We spell our file names in lower dash case (AKA "kebab case") so we don't worry about
case sensitivity on the server or in source control.
:marked
<!-- TODO <!-- TODO
.l-sub-section .l-sub-section
:marked :marked
Learn more about naming conventions in the chapter [Naming Conventions] Learn more about naming conventions in the chapter [Naming Conventions]
:marked :marked
--> -->
:marked
We begin by importing the `Component` function from Angular so that we have it handy when we create
the metadata for our component.
We create metadata with the `@Component` decorator where we
specify the selector name that identifies this component's element.
Then we export the class to make it available to other components.
When we finish here, we'll import it into `AppComponent` and refer to its `<my-hero-detail>` element.
:marked
#### Hero Detail Template
At the moment, the *Heroes* and *Hero Detail* views are combined in one template in `AppComponent`.
Lets **cut** the *Hero Detail* content from `AppComponent` and **paste** it into the new template property of `HeroDetailComponent`.
We previously bound to the `selectedHero.name` property of the `AppComponent`.
Our `HeroDetailComponent` will have a `hero` property, not a `selectedHero` property.
So we replace `selectedHero` with `hero` everywhere in our new template. That's our only change.
The result looks like this:
+makeExample('toh-3/ts/app/hero-detail.component.ts', 'template', 'hero-detail.component.ts (template)')(format=".")
:marked :marked
### Checking Our Work Now our hero detail layout exists only in the `HeroDetailComponent`.
When we view our app in the browser we see the list of heroes. When we select a hero we can see the selected heros details. If we want to show hero details somewhere else in our app we can use the `HeroDetailComponent` and pass in a hero.
#### Add the *hero* property
Lets add that `hero` property we were talking about to the component class.
+makeExample('toh-3/ts/app/hero-detail.component.ts', 'hero')
:marked
Uh oh. We declared the `hero` property as type `Hero` but our `Hero` interface is over in the `app.component.ts` file.
We have two components, each in their own file, that need to reference the `Hero` interface.
We solve the problem by relocating the `Hero` interface from `app.component.ts` to its own `hero.ts` file.
+makeExample('toh-3/ts/app/hero.ts', null, 'hero.ts (Exported Hero interface)')(format=".")
:marked
We export the `Hero` interface from `hero.ts` because we'll need to reference it in both component files.
Add the following import statement near the top of both `app.component.ts` and `hero.-detail.component.ts`.
+makeExample('toh-3/ts/app/hero-detail.component.ts', 'hero-import', 'hero-detail.component.ts and app.component.ts (Import the Hero interface)')
:marked
#### The *hero* property is an ***input***
The `HeroDetailComponent` must be told what hero to display. Who will tell it? The parent `AppComponent`!
The `AppComponent` knows which hero to show: the hero that the user selected from the list.
The user's selection is in its `selectedHero` property.
We will soon update the `AppComponent` template so that it binds its `selectedHero` property
to the `hero` property of our `HeroDetailComponent`. The binding *might* look like this:
code-example(format=".").
&lt;my-hero-detail [hero]="selectedHero">&lt;/my-hero-detail>
:marked
Notice that the `hero` property is the ***target*** of a property binding &mdash; it's in square brackets to the left of the (=).
Angular insists that we declare a ***target*** property to be an ***input*** property.
If we don't, Angular rejects the binding and throws an error.
.l-sub-section
:marked
We explain input properties in more detail [here](../guide/attribute-directives.html#why-input)
where we also explain why *target* properties require this special treament and
*source* properties do not.
:marked
There are a couple of ways we can declare that `hero` is an *input*.
We'll do it by adding an `inputs` array to the `@Component` metadata.
+makeExample('toh-3/ts/app/hero-detail.component.ts', 'inputs')
.l-sub-section
:marked
Learn about the `@Input()` decorator way in the
[Attribute Directives](../guide/attribute-directives.html#input) chapter.
:marked
.l-main-section
:marked
## Refresh the AppComponent
We return to the `AppComponent` and teach it to use the `HeroDetailComponent`.
We begin by importing the `HeroDetailComponent` so we can refer to it.
+makeExample('toh-3/ts/app/app.component.ts', 'hero-detail-import')
:marked
Find the location in the template where we removed the *Hero Detail* content
and add an element tag that represents the `HeroDetailComponent`.
code-example(format=".").
&lt;my-hero-detail>&lt;/my-hero-detail>
.l-sub-section
:marked
*my-hero-detail* is the name we set as the `selector` in the `HeroDetailComponent` metadata.
:marked
The two components won't coordinate until we bind the `selectedHero` property of the `AppComponent`
to the `HeroDetailComponent` element's `hero` property like this:
code-example(format=".")
&lt;my-hero-detail [hero]="selectedHero">&lt;/my-hero-detail>
:marked
The `AppComponent`s template should now look like this
+makeExample('toh-3/ts/app/app.component.ts', 'hero-detail-template', 'app.component.ts (Template)')
:marked
Thanks to the binding, the `HeroDetailComponent` should receive the hero from the `AppComponent` and display that hero's detail beneath the list.
The detail should update every time the user picks a new hero.
It's not happening yet!
We click among the heroes. No details. We look for an error in the console of the browser development tools. No error.
It is as if Angular were ignoring the new tag. That's because *it is ignoring the new tag*.
### The *directives* array
A browser ignores HTML tags and attributes that it doesn't recognize. So does Angular.
We've imported `HeroDetailComponent`, we've used it in the template, but we haven't told Angular about it.
We tell Angular about it by listing it in the metadata `directives` array. Let's add that array property to the bottom of the
`@Component` configuration object, immediately after the `template` and `styles` properties.
+makeExample('toh-3/ts/app/app.component.ts', 'directives')
:marked
### It works!
When we view our app in the browser we see the list of heroes.
When we select a hero we can see the selected heros details.
What's fundamentally new is that we can use this `HeroDetailComponent`
to show hero details anywhere in the app.
Weve created our first reusable component! Weve created our first reusable component!
@ -157,18 +216,35 @@ code-example(format="." language="bash").
.file index.html .file index.html
.file package.json .file package.json
.file tsconfig.json .file tsconfig.json
:marked
Here are the code files we discussed in this chapter.
+makeTabs(`
toh-3/ts/app/hero-detail.component.ts,
toh-3/ts/app/app.component.ts,
toh-3/ts/app/hero.ts
`,'',`
app/hero-detail.component.ts,
app/app.component.ts,
app/hero.ts
`)
.l-main-section .l-main-section
:marked :marked
## The Road Weve Travelled ## The Road Weve Travelled
Lets take stock in what weve built. Lets take stock of what weve built.
* We created a reusable component * We created a reusable component
* We learned how to make a component accept input * We learned how to make a component accept input
* We learned to bind a parent component to a child component.
* We learned to declare the application directives we need in a `directives` array.
[Run the live example for part 3](/resources/live-examples/toh-3/ts/plnkr.html) [Run the live example for part 3](/resources/live-examples/toh-3/ts/plnkr.html).
### The Road Ahead
.l-main-section
:marked
## The Road Ahead
Our Tour of Heroes has become more reusable using shared components. Our Tour of Heroes has become more reusable using shared components.
We want to create reusable services that retrieve our data. As our app evolves, We want to create reusable services that retrieve our data. As our app evolves,
well learn how to design it to make it easier to grow and maintain. well learn how to design it to make it easier to grow and maintain.