5. 컴포넌트 만들기
지금까지 작성한 앱은 HeroesComponent
가 히어로의 목록과 선택된 히어로의 상세정보를 동시에 표시한다.
하지만 모든 기능을 컴포넌트 하나가 담당하면 애플리케이션이 커질수록 이 컴포넌트를 관리하기 점점 힘들어진다. 그래서 컴포넌트가 복잡해지면 이 컴포넌트의 역할을 나눠서 일부 역할만 담당하도록 작은 컴포넌트로 나누는 것이 좋다.
이 문서에서는 히어로의 상세정보를 표시하는 부분을 분리해서 HeroDetailComponent
로 만들어 본다.
그러면 HeroesComponent
는 히어로의 목록만 표시하고 이 목록에서 선택된 히어로는 HeroDetailComponent
가 표시할 것이다.
1. HeroDetailComponent
생성하기
Angular CLI로 hero-detail
컴포넌트를 생성한다.
이 명령은 다음 순서로 실행된다:
src/app/hero-detail
폴더를 생성한다.
그리고 이 폴더에 4개의 파일을 생성한다:
- 컴포넌트 스타일을 지정하는 CSS 파일
- 컴포넌트 템플릿을 정의하는 HTML 파일
- 컴포넌트 클래스
HeroDetailComponent
가 정의된 TypeScript 파일 HeroDetailComponent
클래스 파일을 테스트하는 파일
이 명령을 실행하면 HeroDetailComponent
가 자동으로 src/app/app.module.ts
파일에 있는 @NgModule
에 등록된다.
1) 템플릿 작성하기
HeroesComponent
아래쪽에 히어로의 상세정보를 표시하는 HTML 템플릿을 잘라내서 HeroDetailComponent
템플릿에 붙여넣는다.
이 때 붙여넣은 HTML에는 selectedHero
를 참조하는 부분이 있다. 그런데 새로 만든 HeroDetailComponent
는 선택된 히어로가 아니라 히어로 한 명의 상세정보를 표시한다. 템플릿에 있는 selectedHero
는 모두 hero
로 변경한다.
그러면 HeroDetailComponent
의 템플릿이 다음과 같이 작성될 것이다:
<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>
2) @Input()
히어로 프로퍼티 추가하기
HeroDetailComponent
템플릿에 바인딩된 hero
는 컴포넌트의 hero
프로퍼티를 참조해야 한다.
HeroDetailComponent
클래스 파일을 열어서 Hero
심볼을 로드한다.
이 때 hero
프로퍼티의 값은 외부 컴포넌트인 HeroesComponent
에서 바인딩되어 전달된다. 따라서 hero
프로퍼티는 @Input()
데코레이터를 사용해서 입력 프로퍼티로 선언해야 한다.
@angular/core
패키지에서 Input
심볼을 로드한다.
import { Component, OnInit, Input } from "@angular/core";
그리고 @Input()
데코레이터와 함께 hero
프로퍼티를 선언한다.
HeroDetailComponent
클래스는 여기까지 수정하면 된다. 더 추가할 프로퍼티는 없으며 클래스에 추가할 로직도 없다. 이 컴포넌트는 단순하게 히어로 객체를 받아서 hero
프로퍼티에 할당하고, 템플릿에 이 히어로의 상세정보를 표시할 뿐이다.
2. HeroDetailComponent
표시하기
HeroesComponent
는 템플릿의 해당 부분을 제거하기 전에 히어로 세부정보를 자체적으로 표시하는 데 사용되었다. 이 섹션에서는 HeroDetailComponent
에 로직을 위임하는 방법을 안내한다.
이제 HeroesComponent
와 HeroDetailComponent
는 부모/자식 관계가 되었다. 부모 컴포넌트인 HeroesComponent
는 자식 컴포넌트인 HeroDetailComponent
를 관리한다. 부모 컴포넌트의 히어로 목록에서 히어로를 선택하면 이 히어로의 정보를 HeroDetailComponent
로 보내서 히어로의 정보를 표시하게 할 것이다.
1) HeroesComponent
템플릿 수정하기
HeroDetailComponent
의 셀렉터는 'app-hero-detail'
이다. 원래 히어로의 상세정보를 표시하던 HeroesComponent
템플릿 아래쪽에 <app-hero-detail>
엘리먼트를 추가한다.
그리고 HeroesComponent.selectedHero
프로퍼티를 이 엘리먼트의 hero
프로퍼티에 다음과 같이 바인딩한다.
[hero]="selectedHero"
는 Angular가 제공하는 프로퍼티 바인딩 문법이다.
이렇게 작성하면 HeroesComponent
의 selectedHero
프로퍼티가 HeroDetailComponent
의 hero
프로퍼티로 단방향 데이터 바인딩된다.
이제 사용자 목록에서 선택하면 selectedHero
의 값이 변경된다. 그리고 selectedHero
값이 변경되면 프로퍼티 바인딩된 HeroDetailComponent
의 hero
프로퍼티도 변경되면서 선택된 히어로의 상세정보가 화면에 표시된다.
이렇게 수정하고 나면 HeroesComponent
템플릿 코드는 다음과 같이 변경된다:
<h2>My Heroes</h2>
<ul class="heroes">
<li
*ngFor="let hero of heroes"
[class.selected]="hero === selectedHero"
(click)="onSelect(hero)"
>
<span class="badge">{{hero.id}}</span> {{hero.name}}
</li>
</ul>
<app-hero-detail [hero]="selectedHero"></app-hero-detail>
브라우저가 갱신되고 나면 애플리케이션이 실행되면서 이전과 동일하게 동작한다.
3. 어떤 것이 변경되었을까?
이 앱은 이전과 동일하게 사용자가 히어로의 이름을 클릭하면 히어로 목록 아래에 히어로의 상세정보를 표시한다. 하지만 이제는 히어로의 상세정보를 HeroesComponent
대신 HeroDetailComponent
가 표시한다.
이번 가이드에서는 HeroesComponent
를 좀 더 효율적으로 관리하기 위해 컴포넌트 두 개로 분리했다:
1] HeroesComponent
의 코드가 좀 더 간단해졌다.
2] HeroDetailComponent
는 좀 더 다양한 기능으로 확장할 수 있지만, 이 때 부모 컴포넌트이 HeroesComponent
는 신경쓰지 않아도 된다.
3] HeroesComponent
를 수정할 때도 상세정보 화면은 신경쓰지 않아도 된다.
4] HeroDetailComponent
는 다른 컴포넌트에서도 재사용할 수 있다.
4. 최종코드 리뷰
이 문서에서 다룬 코드는 다음과 같다.
import { Component, OnInit, 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 implements OnInit {
@Input() hero?: Hero;
constructor() {}
ngOnInit() {}
}
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 {}
5. 정리
이번 튜토리얼을 진행하면서 다음과 같은 내용에 대해 알아봤다.
- 기존 컴포넌트의 일부를 분리해서
HeroDetailComponent
를 만들었다. 이 컴포넌트는 다른 곳에 재사용할 수 있다. - 부모 컴포넌트
HeroesComponent
에서 자식 컴포넌트HeroDetailComponent
로 데이터를 전달하기 위해 프로퍼티 바인딩을 사용했다. HeroDetailComponent
의hero
프로퍼티 값을 컴포넌트 외부인HeroesComponent
에서 가져오기 위해@Input
데코레이터를 사용했다.