Our app is growing. Use cases are flowing in for reusing components, passing data to components, and creating more reusable assets. Let's separate the heroes list from the hero details and make the details component reusable.
Run the
Where We Left Off
Before we continue with our Tour of Heroes, let’s verify we have the following structure. If not, we’ll need to go back and follow the previous chapters.
Keep the app compiling and running
We want to start the Dart compiler, have it watch for changes, and start our server. We'll do this by typing
This will keep the application running while we continue to build the Tour of Heroes.
Making a Hero Detail Component
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. It's only a tutorial but we can still do things right — especially if doing them right is easy and we learn how to build Angular apps in the process.
Let’s break the hero details out into its own component.
Separating the Hero Detail Component
Add a new file named hero_detail_component.dart
to the lib
folder and create HeroDetailComponent
as follows.
lib/hero_detail_component.dart (initial version)
Naming conventions
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.dart
and our new
HeroDetailComponent
is in a file named hero_detail_component.dart
.
All of our component names end in "Component". All of our component file names end in "_component".
We spell our filenames in lower underscore case (AKA snake_case) so we don't worry about case sensitivity on the server or in source control.
We begin by importing the Angular core.dart
file,
so that we can use common types like @Component
when we create
our component.
We create metadata with the @Component
annotation where we
specify the selector name that identifies this component's element.
When we finish here, we'll import it into AppComponent
and create a corresponding <my-hero-detail>
element.
Hero Detail Template
At the moment, the Heroes and Hero Detail views are combined in one template in AppComponent
.
Let’s 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:
lib/hero_detail_component.dart (template)
Now our hero detail layout exists only in the HeroDetailComponent
.
Add the hero property
Let’s add that hero
property we were talking about to the component class.
Uh oh. We declared the hero
property as type Hero
but our Hero
class is over in the app_component.dart
file.
We have two components, each in their own file, that need to reference the Hero
class.
We solve the problem by relocating the Hero
class from app_component.dart
to its own hero.dart
file.
lib/hero.dart
Add the following import statement near the top of both app_component.dart
and hero_detail_component.dart
.
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:
Notice that the hero
property is the target of a property binding — 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.
We explain input properties in more detail here where we also explain why target properties require this special treatment and source properties do not.
There are a couple of ways we can declare that hero
is an input.
We'll do it the way we prefer, by annotating the hero
property with @Input()
.
Learn more about @Input()
in the
Attribute Directives chapter.
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.
Find the location in the template where we removed the Hero Detail content
and add an element tag that represents the HeroDetailComponent
.
my-hero-detail is the name we set as the selector
in the HeroDetailComponent
metadata.
The two components won't coordinate until we bind the selectedHero
property of the AppComponent
to the HeroDetailComponent
element's hero
property like this:
The AppComponent
’s template should now look like this
app_component.dart (template)
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 list
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
list. Let's add that list property to the bottom of the
@Component
configuration object, immediately after the template
and styles
properties.
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 hero’s details.
What's fundamentally new is that we can use this HeroDetailComponent
to show hero details anywhere in the app.
We’ve created our first reusable component!
Reviewing the App Structure
Let’s verify that we have the following structure after all of our good refactoring in this chapter:
Here are the code files we discussed in this chapter.
The Road We’ve Travelled
Let’s take stock of what we’ve built.
- We created a reusable component
- 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
list.
Run the
The Road Ahead
Our Tour of Heroes has become more reusable with shared components.
We're still getting our (mock) data within the AppComponent
.
That's not sustainable.
We should refactor data access to a separate service
and share it among the components that need data.
We’ll learn to create services in the next tutorial chapter.