3. 히어로 에디터
이전 튜토리얼에서는 애플리케이션의 제목을 수정해 봤다. 이번 튜토리얼에서는 히어로의 정보를 표시하는 컴포넌트를 생성하고 이 컴포넌트를 애플리케이션 셸에 추가해 보자.
1. 히어로 컴포넌트 생성하기
다음 명령을 실행해서 Angular CLI로 heroes
컴포넌트를 생성한다.
그러면 CLI가 src/app/heroes/
폴더를 생성하고 HeroesComponent
를 구성하는 파일 3개와 테스트 파일을 생성한다.
자동으로 생성된 HeroesComponent
클래스 파일의 내용은 다음과 같다.
import { Component, OnInit } from "@angular/core";
@Component({
selector: "app-heroes",
templateUrl: "./heroes.component.html",
styleUrls: ["./heroes.component.css"],
})
export class HeroesComponent implements OnInit {
constructor() {}
ngOnInit() {}
}
Angular 컴포넌트를 선언하려면 반드시 Angular 코어 라이브러리에서 Component
심볼을 로드하고 컴포넌트 클래스에 @Component
와 같이 지정해야 한다.
이 때 @Component
는 클래스에 메타데이터를 지정해서 Angular 컴포넌트로 선언하는 데코레이터 함수이다.
Angular CLI는 기본적으로 3개의 메타데이터 프로퍼티를 생성한다.
1] selector
: 컴포넌트의 CSS 엘리먼트 셀렉터
2] templateUrl
: 컴포넌트 템플릿 파일의 위치
3] styleUrls
: 컴포넌트 CSS 스타일 파일의 위치
'app-heroes'
는 CSS 엘리먼트 셀렉터이다. 엘리먼트 셀렉터는 DOM 트리에서 이 컴포넌트를 표현하는 이름이며, 부모 컴포넌트의 템플릿에 사용한다.
ngOnInit
은 라이프싸이클 후킹 함수이다. Angular는 컴포넌트를 생성한 직후에 ngOnInit
를 호출한다. 그래서 컴포넌트를 초기화하는 로직은 이 메서드에 작성하는 것이 좋다.
컴포넌트는 반드시 export
해야 AppModule
와 같은 다른 모듈에서 import
할 수 있다.
1) hero
프로퍼티 추가하기
HeroesComponent
에 hero
프로퍼티를 추가하고 이 값을 히어로의 이름 "Windstorm"로 할당한다.
2) 히어로 표시하기
heroes.component.html
템플릿 파일을 연다. 이 파일에서 Angular CLI가 만든 코드를 삭제하고 hero
프로퍼티를 데이터 바인딩하는 코드로 수정한다.
2. HeroesComponent
뷰 표시하기
HeroesComponent
를 표시하려면 이 컴포넌트를 AppComponent
셸의 템플릿에 추가해야 한다.
이전 단계에서 HeroesComponent
의 엘리먼트 셀렉터는 app-heroes
로 선언했다. <app-heroes>
엘리먼트를 AppComponent
템플릿 파일에서 타이틀 바로 밑에 추가한다.
Angular CLI 명령 ng serve
가 실행되고 있는 상태라면 브라우저는 자동으로 화면을 갱신한다. 변경된 화면에서 애플리케이션 이름과 히어로 이름이 표시되는 것을 확인해 보자.
3. 히어로 인터페이스 생성하기
실제 데이터를 생각해보면 히어로를 표현하는 객체는 이름 외에 다른 정보도 있을 수 있다.
src/app
폴더에 Hero
인터페이스를 생성하고 이 클래스에 id
와 name
프로퍼티를 추가한다.
그리고 HeroesComponent
클래스로 돌아가서 Hero
인터페이스를 로드한다.
컴포넌트의 hero
프로퍼티를 Hero
타입으로 리팩토링한다. 이 때 id
를 1
로, name
을 Windstorm
으로 초기화한다.
수정된 HeroesComponent
클래스 파일은 아래와 같다.
import { Component, OnInit } from "@angular/core";
import { Hero } from "../hero";
@Component({
selector: "app-heroes",
templateUrl: "./heroes.component.html",
styleUrls: ["./heroes.component.css"],
})
export class HeroesComponent implements OnInit {
hero: Hero = {
id: 1,
name: "Windstorm",
};
constructor() {}
ngOnInit() {}
}
이제 화면을 확인해보면 히어로가 제대로 표시되지 않는 것을 확인할 수 있다. 왜냐하면 히어로 프로퍼티 값을 문자열 타입에서 객체 타입으로 변경했기 때문이다.
4. 히어로 객체 표시하기
히어로의 이름이 제대로 표시되도록 템플릿을 수정하자. 상세정보를 표시하는 레이아웃에 다음과 같이 id
와 name
을 바인딩한다.
<h2>{{hero.name}} Details</h2>
<div><span>id: </span>{{hero.id}}</div>
<div><span>name: </span>{{hero.name}}</div>
이제 브라우저가 갱신되면 히어로의 정보가 제대로 표시된다.
5. UppercasePipe
로 표시형식 지정하기
다음과 같이 hero.name
바인딩 문법을 수정한다.
이제 브라우저가 갱신되면 히어로의 이름이 대문자로 표시된다.
문자열 바인딩(interpolation binding)에서 파이프 연산자(|
) 바로 뒤에 있는 uppercase
는 Angular의 기본 파이프인 UppercasePipe
를 가리킨다.
파이프는 문자열의 형식을 지정하거나, 통화 단위를 변경하고, 날짜나 데이터가 표시되는 형식을 변경할 때 사용한다.
6. 히어로 정보 수정하기
<input>
텍스트박스를 사용해서 사용자가 히어로의 이름을 수정하게 하려고 한다.
텍스트박스는 히어로의 name
프로퍼티를 화면에 표시하면서, 동시에 유저가 입력한 값으로 프로퍼티를 업데이트해야 한다. 이 말은 데이터가 컴포넌트 클래스에서 화면으로, 그리고 반대 방향인 화면에서 클래스로 전달되어야 한다는 것을 의미한다.
이 데이터 흐름을 자동화하려면 <input>
엘리먼트와 hero.name
프로퍼티를 양방향 바인딩으로 연결하면 된다.
1) 양방향 바인딩
HeroesComponent
템플릿에서 상세 화면 영역을 아래 코드와 같이 리팩토링한다.
<div>
<label for="name">Hero name: </label>
<input id="name" [(ngModel)]="hero.name" placeholder="name" />
</div>
[(ngModel)]
는 Angular의 양방향 바인딩 문법이다.
이렇게 작성하면 hero.name
프로퍼티가 HTML 텍스트박스와 양방향 바인딩되기 때문에, hero.name
프로퍼티 값이 텍스트박스로, 텍스트박스의 값이 다시 hero.name
프로퍼티로 전달될 수 있다.
2) FormsModule
을 빠뜨렸다
하지만 [(ngModel)]
를 추가했기 때문에 이 앱은 이제 동작하지 않는다.
이 때 브라우저에서 개발자도구를 열어 콘솔을 확인하면 다음과 같은 에러가 표시되는 것을 확인할 수 있다.
ngModel
은 Angular에서 제공하는 디렉티브지만, 아무것도 설정하지 않은 상태에서 이 디렉티브를 바로 사용할 수는 없다.
이 디렉티브는 FormsModule
에서 제공하는 디렉티브이기 때문에 이 디렉티브를 사용하려면 명시적으로 FormsModule
을 로드해야 한다.
7. AppModule
개발자가 만든 Angular 구성요소나 서드파티 파일, 라이브러리를 Angular가 조합할 때는 이 구성요소들에 대한 정보가 필요하다. 이런 정보를 메타데이터(metadata)라고 한다.
컴포넌트 클래스에 지정해야 하는 메타데이터는 @Component
데코레이터에 지정한다. 그리고 애플리케이션 동작에 필요한 메타데이터는 보통 @NgModule
데코레이터에 지정한다.
이 중에서 가장 중요한 데코레이터는 애플리케이션 최상위 모듈인 AppModule
클래스에 지정하는 @NgModule
데코레이터이다.
Angular CLI로 프로젝트를 생성하면 src/app/app.module.ts
파일에 AppModule
클래스가 생성된다. FormsModule
은 이 모듈에 등록한다.
1) FormsModule
등록하기
AppModule
(app.module.ts
)를 열고 @angular/forms
라이브러리에서 제공하는 FormsModule
심볼을 로드한다.
import { FormsModule } from "@angular/forms"; // <-- NgModel은 이 패키지가 제공합니다.
그리고나서 이 FormsModule
을 @NgModule
메타데이터의 imports
배열에 추가한다. 이 배열에는 애플리케이션이 동작할 때 필요한 외부 모듈을 등록한다.
이제 브라우저를 새로고침하면 앱이 제대로 동작한다. 화면에서 히어로의 이름을 변경할 수 있으며 이렇게 변경된 이름이 텍스트박스 위에 있는 <h2>
태그에 바로 반영되는 것도 확인할 수 있다.
2) HeroesComponent
선언하기
컴포넌트는 반드시 NgModule
중 하나에 등록되어야 한다.
하지만 HeroesComponent
는 어디에도 등록하지 않았다. 그런데 이 애플리케이션이 동작하는 이유는 Angular CLI로 HeroesComponent
를 생성할 때 Angular CLI가 이 컴포넌트를 AppModule
에 자동으로 등록했기 때문이다.
src/app/app.module.ts
파일을 열어서 HeroesComponent
가 로드되는 것을 확인해 보자.
이렇게 로드한 컴포넌트 HeroesComponent
는 @NgModule.declarations
배열에 등록되어 있다.
이 코드에서 AppModule
에는 AppComponent
와 HeroesComponent
가 등록되어 있다.
8. 최종 코드 리뷰
이번 튜토리얼에서 다룬 코드의 내용을 확인해 보자.
import { Component, OnInit } from "@angular/core";
import { Hero } from "../hero";
@Component({
selector: "app-heroes",
templateUrl: "./heroes.component.html",
styleUrls: ["./heroes.component.css"],
})
export class HeroesComponent implements OnInit {
hero: Hero = {
id: 1,
name: "Windstorm",
};
constructor() {}
ngOnInit() {}
}
import { BrowserModule } from "@angular/platform-browser";
import { NgModule } from "@angular/core";
import { FormsModule } from "@angular/forms"; // <-- NgModel은 이 패키지가 제공합니다.
import { AppComponent } from "./app.component";
import { HeroesComponent } from "./heroes/heroes.component";
@NgModule({
declarations: [AppComponent, HeroesComponent],
imports: [BrowserModule, FormsModule],
providers: [],
bootstrap: [AppComponent],
})
export class AppModule {}
9. 정리
이번 튜토리얼을 진행하면서 다음과 같은 내용에 대해 알아봤다.
- Angular CLI를 사용해서 두 번째 컴포넌트인
HeroesComponent
를 생성했다. HeroesComponent
를AppComponent
셸에 추가해서 화면에 표시했다.- 화면에 표시되는 이름의 형식을 지정하기 위해
UppercasePipe
를 사용했다. ngModel
디렉티브를 사용해서 양방향 데이터 바인딩을 연결했다.AppModule
에 대해 알아봤다.- Angular가
ngModel
디렉티브를 제대로 인식하고 동작할 수 있도록AppModule
에FormsModule
을 로드했다. - 컴포넌트는 반드시
@NgModule
중 하나에 등록되어야 한다. 이 때 Angular CLI를 사용하면 더 편하다.