새 컴포넌트 만들기
2.4. 새 컴포넌트 만들기
먼저 할 일은 우리 가게에서 내어놓을 피자들을 보여주는 것입니다. 고객들은 이 목록을 보고 피자를 주문할 것입니다. 우리는 앞서 컴포넌트를 하나 보고 왔죠. 그리고 피자 목록을 보여줄 때 편한 컴포넌트를 고민해보면 좋을 것 같습니다. 컴포넌트의 기초적인 조건은 "구성 가능한" 단위로 만들어지는 것이죠. 목록에 들어갈 항목들은 컴포넌트로 표현하기 적합해 보입니다. 이런 항목 컴포넌트를 조립해서 목록 컴포넌트를 만들어 봅시다.
그럼 먼저 피자목록 컴포넌트 부터 시작해 볼까요? 프로젝트 루트 폴더에서 다음과 같이 실행합니다.
> ng generate component pizzaList
installing component
create src/app/pizza-list/pizza-list.component.css
create src/app/pizza-list/pizza-list.component.html
create src/app/pizza-list/pizza-list.component.spec.ts
create src/app/pizza-list/pizza-list.component.ts
update src/app/app.module.ts
기본 설정으로 시작한 프로젝트라면 다음과 같이 src/app/
경로 밑에 pizza-list
라는 폴더를 생성한 후 4개의 파일을 생성하고, 1개의 파일을 수정할 것입니다.
생성된 파일부터 알아봅시다. 컴포넌트를 이루는 3가지 구성요소인 스타일, 도큐먼트, 행위에 해당하는 CSS, HTML 그리고 타입스크립트 파일들이 생성되고 추가로 테스트를 위한 spec.ts 파일도 생성이 됩니다. 그리고 해당 컴포넌트가 포함될 모듈에 이 컴포넌트를 등록하는 수정도 수행됩니다. 진행된 내용들을 자세히 알아볼까요?
먼저, 타입스크립트 파일부터 확인해 보겠습니다.
<예제> src/app/pizza-list/pizza-list.component.ts
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'pz-pizza-list',
templateUrl: './pizza-list.component.html',
styleUrls: ['./pizza-list.component.css']
})
export class PizzaListComponent implements OnInit {
constructor() { }
ngOnInit() {
}
}
기본 구조가 정리된 모습을 볼 수 있습니다. 잠시 기술된 내용을 살펴보도록 하죠. @Component()
데코레이터에 옵션 중에 먼저 selector: 'pz-pizza-list'
라는 내용을 볼 수 있습니다. 앞서 프로젝트의 prefix 로 설정했던 [pz] 가 셀렉터의 prefix에도 사용되었습니다. 간단한 설정을 통해 사소하지만 유용한 부분을 도와주고 있습니다. 다음은 templateUrl 과 styleUrls 는 같이 생성된 HTML, CSS 파일을 담고 있습니다. 표현에 단/복수가 드러나 있듯이 templateUrl 은 하나의 요소만을 받고 있으며 styleUrls 는 배열을 통해 여러개의 스타일 파일을 받을 수 있습니다. 다음은 HTML을 열어봅시다.
<예제> src/app/pizza-list/pizza-list.component.html
<p>
pizza-list works!
</p>
매우 간단한 구조만 있네요. 앞으로 여기에 더 많은 내용을 담아보도록 하죠. 다음은 스타일인 CSS인데 사실 파일만 생성될 뿐 내용은 아무것도 없습니다. 사용할 시점에 더 살펴보도록 하겠습니다. 하나 짚어볼 부분은 templateUrl 대신 template 으로 인라인 템플릿 표현을 했던 것을 기억하나요? 스타일도 마찬가지로 styleUrls 대신 styles 로 인라인 스타일 표현으로 대체할 수 있습니다. 컴포넌트의 스타일이 많은 내용을 담고 있지 않다면, 인라인 스타일 표현으로 간결한 코드를 만드는 것도 고려할 만합니다. 표현이 복수인 것에 힌트를 느끼셨겠지만 역시 배열을 통해 여러개의 스타일 요소를 받을 수 있습니다.
<예제> src/app/pizza-list/pizza-list.component.ts
import { Component, OnInit } from '@angular/core';
/* ... */
export class PizzaListComponent implements OnInit {
/* ... */
ngOnInit() {
}
}
마지막으로 살펴볼 내용은 OnInit 을 구현하고 있다는 것 입니다. OnInit 은 Angular가 제공하는 컴포넌트의 라이프 사이클 구성 중 하나이고 OnInit 외에도 필요하다면 다른 라이프 사이클 구성 인터페이스를 추가로 구현하여 컴포넌트를 다룰 수 있습니다. 물론 구지 라이프 사이클을 구현하지 않아도 됩니다. 라이프 사이클 구성에 대해서는 6.에서 자세히 다루도록 하겠습니다.
이제 모듈의 수정부분을 확인해 봅시다.
<예제> src/app/app.module.ts
/* ... */
import { AppComponent } from './app.component';
import { PizzaListComponent } from './pizza-list/pizza-list.component';
@NgModule({
declarations: [
AppComponent,
PizzaListComponent
],
/* ... */
})
export class AppModule { }
위와 같이 PizzaListComponent 를 AppModule 의 declarations 에 포함하고 있습니다. 컴포넌트는 이렇게 어떤 모듈에 속해서 사용되므로 앞으로도 만든 컴포넌트들을 사용하기 전에 어떤 모듈에 포함하게 될 것인지 정하는 과정을 거칠 것입니다.
꽤나 설명하고 지나왔지만 실제 우리가 한 것은 단 한줄의 명령어였죠. angular-cli 는 이런 불필요한 반복작업을 도와주고, 그 과정에서 발생할지도 모르는 실수를 보완해 줍니다. 피자 목록을 만들기 전에 먼저, 피자 모델을 준비해보도록 하겠습니다. 프로젝트 루트 폴더에서 다음을 실행하세요.
> ng generate class pizza
installing class
create src/app/pizza.ts
이번엔 한개의 파일이 생성되는군요. 생성된 파일을 열어볼까요?
<예제> src/app/pizza.ts
export class Pizza {
}
공허하군요. 텅빈 클래스가 준비되었습니다. 뭔가 허전해 보이지만 이처럼 앵귤러에서 모델은 가장 기본적인 자바스크립트 객체로 표현됩니다. 부차적으로 더 기교될 것이 없죠. 우리는 준비된 피자 객체에 필요한 속성 몇가지를 추가해 봅시다.
<예제> src/app/pizza.ts
export class Pizza {
constructor(
public name:string,
public price:number
) { }
}
생성자에서 public 과 같은 접근제어자 표현을 함께 붙여 바로 속성으로 준비하는 패턴은 자주 쓰입니다. 불필요한 타이핑을 줄여주고 필수 입력을 누락하지 않도록 강제하는 효과가 있습니다. 이렇게 우리는 사용할 모델이 간단히 준비되었습니다. 다시 피자목록 컴포넌트로 돌아가도록 하겠습니다.
<예제> src/app/pizza-list/pizza-list.component.ts
/* ... */
import { Pizza } from '../pizza';
/* ... */
export class PizzaListComponent implements OnInit {
pizzaList:Pizza[] = [
new Pizza("치즈 피자", 13500),
new Pizza("페퍼로니 피자", 12500),
new Pizza("고구마 피자", 14000)
];
/* ... */
}
피자목록 컴포넌트를 열어서 방금 준비한 피자 모델타입으로 피자목록 데이터를 준비하는 과정입니다. 먼저 상위 폴더의 ../pizza
에서 Pizza 모델을 임포트하고 PizzaListComponent 가장 상단에 pizzaList 를 Pizza[] 타입으로 몇 개의 피자를 준비합니다. 이것으로 피자목록 컴포넌트의 속성으로 pizzaList 가 공개 되었습니다. 이렇게 공개된 속성은 뷰에서 참조하여 사용하도록 공개됩니다. 이제 템플릿을 만들어보도록 하겠습니다.
<예제> src/app/pizza-list/pizza-list.component.html
<div>
<b>{{pizzaList[0].name}}</b>
<i>{{pizzaList[0].price}}원</i>
</div>
<div>
<b>{{pizzaList[1].name}}</b>
<i>{{pizzaList[1].price}}원</i>
</div>
<div>
<b>{{pizzaList[2].name}}</b>
<i>{{pizzaList[2].price}}원</i>
</div>
기존에 있던 HTML코드 대신 새 HTML을 적었습니다. 코드를 보면서 벌써 마음이 불편해지고 계시겠지만, pizzaList[index].name 처럼 {{<expression>}}
안에는 직관적인 문장을 직접 사용하는 것도 가능합니다. 반복을 다루고 싶다면 ngFor 디렉티브(Directive)를 통해 더 쉽게 다룰 수 있습니다. 자세한 내용은 2.5. 기본 Directive로 간단한 구조 만들기에서 살펴보겠습니다. 이쯤에서 브라우져를 한번 봅시다. 헌데 아직까진 변화가 느껴지지 않습니다. 아직 한가지 작업이 남아있기 때문이죠. src/app/app.component.html
파일을 열어서 다음과 같이 추가합니다.
<예제> src/app/app.component.html
<header>
Welcome, {{title}} home
</header>
<section>
<pz-pizza-list></pz-pizza-list> <!-- 여기에 추가합니다. -->
</section>
수정을 감지한 dev-server가 빌드를 반영한 후 브라우져를 보면 다음과 같이 간단한 목록이 나오는 것을 확인할 수 있습니다. 이렇게 우리의 첫 컴포넌트가 생성되었습니다.
Wrap Up
- angular-cli를 통해 새 컴포넌트를 생성합니다.
- angular-cli를 통해 새 모델을 생성합니다.
- 컴포넌트의 속성을 통해 데이터를 공개합니다.
- 공개된 컴포넌트의 데이터를 뷰에서 참조합니다.
- ngFor를 사용하여 반복된 구조를 뷰에 표현합니다.
- 준비한 컴포넌트를 사용합니다.