At the moment, the HeroesComponent
displays both the list of heroes and the selected hero's details.
Keeping all features in one component as the application grows won't be maintainable. This tutorial splits up large components into smaller subcomponents, each focused on a specific task or workflow.
The first step is to move the hero details into a separate, reusable HeroDetailComponent
and end up with:
HeroesComponent
that presents the list of heroes.HeroDetailComponent
that presents the details of a selected hero.For the sample application that this page describes, see the live example.
HeroDetailComponent
Use this ng generate
command to create a new component named hero-detail
.
ng generate component hero-detail
The command scaffolds the following:
src/app/hero-detail
.Inside that directory, four files are created:
HeroDetailComponent
.HeroDetailComponent
class.The command also adds the HeroDetailComponent
as a declaration in the @NgModule
decorator of the src/app/app.module.ts
file.
Cut the HTML for the hero detail from the bottom of the HeroesComponent
template and paste it over the boilerplate content in the HeroDetailComponent
template.
The pasted HTML refers to a selectedHero
. The new HeroDetailComponent
can present any hero, not just a selected hero. Replace selectedHero
with hero
everywhere in the template.
When you're done, the HeroDetailComponent
template should look like this:
<div *ngIf="hero"> <h2>{{hero.name | uppercase}} Details</h2> <div><span>id: </span>{{hero.id}}</div> <div> <label for="hero-name">Hero name: </label> <input id="hero-name" [(ngModel)]="hero.name" placeholder="name"> </div> </div>
@Input()
hero propertyThe HeroDetailComponent
template binds to the component's hero
property which is of type Hero
.
Open the HeroDetailComponent
class file and import the Hero
symbol.
import { Hero } from '../hero';
The hero
property must be an Input
property, annotated with the @Input()
decorator, because the external HeroesComponent
binds to it like this.
<app-hero-detail [hero]="selectedHero"></app-hero-detail>
Amend the @angular/core
import statement to include the Input
symbol.
import { Component, Input } from '@angular/core';
Add a hero
property, preceded by the @Input()
decorator.
@Input() hero?: Hero;
That's the only change you should make to the HeroDetailComponent
class. There are no more properties. There's no presentation logic. This component only receives a hero object through its hero
property and displays it.
HeroDetailComponent
The HeroesComponent
used to display the hero details on its own, before you removed that part of the template. This section guides you through delegating logic to the HeroDetailComponent
.
The two components have a parent/child relationship. The parent, HeroesComponent
, controls the child, HeroDetailComponent
by sending it a new hero to display whenever the user selects a hero from the list.
You don't need to change the HeroesComponent
class, instead change its template.
HeroesComponent
templateThe HeroDetailComponent
selector is 'app-hero-detail'
. Add an <app-hero-detail>
element near the bottom of the HeroesComponent
template, where the hero detail view used to be.
Bind the HeroesComponent.selectedHero
to the element's hero
property like this.
<app-hero-detail [hero]="selectedHero"></app-hero-detail>
[hero]="selectedHero"
is an Angular property binding.
It's a one-way data binding from the selectedHero
property of the HeroesComponent
to the hero
property of the target element, which maps to the hero
property of the HeroDetailComponent
.
Now when the user clicks a hero in the list, the selectedHero
changes. When the selectedHero
changes, the property binding updates hero
and the HeroDetailComponent
displays the new hero.
The revised HeroesComponent
template should look like this:
<h2>My Heroes</h2> <ul class="heroes"> <li *ngFor="let hero of heroes"> <button [class.selected]="hero === selectedHero" type="button" (click)="onSelect(hero)"> <span class="badge">{{hero.id}}</span> <span class="name">{{hero.name}}</span> </button> </li> </ul> <app-hero-detail [hero]="selectedHero"></app-hero-detail>
The browser refreshes and the application starts working again as it did before.
As before, whenever a user clicks on a hero name, the hero detail appears below the hero list. Now the HeroDetailComponent
is presenting those details instead of the HeroesComponent
.
Refactoring the original HeroesComponent
into two components yields benefits, both now and in the future:
You reduced the HeroesComponent
responsibilities.
You can evolve the HeroDetailComponent
into a rich hero editor without touching the parent HeroesComponent
.
You can evolve the HeroesComponent
without touching the hero detail view.
You can re-use the HeroDetailComponent
in the template of some future component.
Here are the code files discussed on this page.
import { Component, Input } from '@angular/core'; import { Hero } from '../hero'; @Component({ selector: 'app-hero-detail', templateUrl: './hero-detail.component.html', styleUrls: ['./hero-detail.component.css'] }) export class HeroDetailComponent { @Input() hero?: Hero; }
<div *ngIf="hero"> <h2>{{hero.name | uppercase}} Details</h2> <div><span>id: </span>{{hero.id}}</div> <div> <label for="hero-name">Hero name: </label> <input id="hero-name" [(ngModel)]="hero.name" placeholder="name"> </div> </div>
<h2>My Heroes</h2> <ul class="heroes"> <li *ngFor="let hero of heroes"> <button [class.selected]="hero === selectedHero" type="button" (click)="onSelect(hero)"> <span class="badge">{{hero.id}}</span> <span class="name">{{hero.name}}</span> </button> </li> </ul> <app-hero-detail [hero]="selectedHero"></app-hero-detail>
import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { AppComponent } from './app.component'; import { HeroesComponent } from './heroes/heroes.component'; import { HeroDetailComponent } from './hero-detail/hero-detail.component'; @NgModule({ declarations: [ AppComponent, HeroesComponent, HeroDetailComponent ], imports: [ BrowserModule, FormsModule ], providers: [], bootstrap: [AppComponent] }) export class AppModule { }
You created a separate, reusable HeroDetailComponent
.
You used a property binding to give the parent HeroesComponent
control over the child HeroDetailComponent
.
You used the @Input
decorator to make the hero
property available for binding by the external HeroesComponent
.
© 2010–2023 Google, Inc.
Licensed under the Creative Commons Attribution License 4.0.
https://angular.io/tutorial/tour-of-heroes/toh-pt3