Angular 튜터리얼 - 다중 콤포넌트

Multiple Components

현재 AppComponent에서 모든 것을 하고 있습니다. 처음에는 하나의 Hero에 대한 상세 보기를 보여주었습니다. 그런 다음 Hero들 목록과 Hero 세부 정보가 모두 포함된 master/detail 형식이 되었습니다. 곧 새로운 요구 사항과 기능이 생길 것입니다. 한 Component에 모든 기능을 유지할 수는 없습니다.

특정 작업이나 워크 플로에 중점을 둔 하위 Component로 구성해야합니다. 결국 AppComponent는 이러한 하위 Component를 호스팅하는 단순한 shell이 될 수 있습니다.

이 페이지에서 Hero의 세부 사항을 별도의 재사용 가능한 Component로 쪼갬으로써 하위 Component로 만드는 첫 번째 단계를 수행하게됩니다. 모두 완료되면 응용 프로그램은 이 라이브 예제/예제 다운로드처럼 보일 것입니다.

어디까지 했나?

이 페이지를 시작하기 전에 이전 페이지에서 작업했던 Tour of Heroes의 구조와 동일한지 확인하십시오. 그렇지 않은 경우 이전 페이지로 돌아가십시오.

1
2
3
4
5
6
7
8
9
10
11
12
angular-tour-of-heroes
┣ src
┃ ┣ app
┃ ┃ ┣ app.component.ts
┃ ┃ ┗ app.module.ts
┃ ┣ main.ts
┃ ┣ index.html
┃ ┣ styles.css
┃ ┣ systemjs.config.js
┃ ┗ tsconfig.json
┣ node_modules ...
┗ package.json

이전처럼 터미널 창에 npm start 명령을 입력하여 Tour of Heroes를 빌드하는 동안 앱이 계속 transpiling과 실행을 계속하도록 합니다.

Hero 상세 component 만들기

hero-detail.component.ts라는 파일을 app/ 폴더에 추가합니다. 이 파일에 새로운 HeroDetailComponent를 저장할 것입니다.

파일 및 Component 이름은 Angular 스타일 안내서에 설명된 표준을 따릅니다.

  • Component 클래스 이름은 upper camel case로 쓰여지고 “Component”라는 단어로 끝나야합니다. Hero 상세 Component 클래스는 HeroDetailComponent입니다.
  • Component 파일 이름은 소문자로 입력해야하며, 각 단어는 대시로 구분하고 .component.ts로 끝나야합니다. HeroDetailComponent 클래스는 hero-detail.component.ts 파일에 들어갑니다.

다음과 같이 HeroDetailComponent 작성을 시작합니다.

app/hero-detail.component.ts (initial version)

1
2
3
4
5
6
7
import { Component } from '@angular/core';
@Component({
selector: 'hero-detail',
})
export class HeroDetailComponent {
}

Component를 정의하려면 항상 Component 심볼을 import 해야합니다.

@Component DecoratorComponent의 Angular 메타 데이터를 제공합니다. CSS 선택자 이름인 hero-detail은 상위 Component의 템플릿 내에서 이 Component를 식별하는 엘리먼트 태그와 일치합니다. 이 튜토리얼 페이지의 끝 부분에서 <hero-detail> 엘리먼트를 AppComponent 템플릿에 추가할 것입니다.

항상 다른곳에서 import하기 때문에 컴포넌트 클래스는 언제나 export해야 합니다.

Hero 상세 템플릿

Hero 상세 보기를 HeroDetailComponent로 옮기려면, AppComponent 템플릿의 맨 아래에서 Hero 상세 내용을 잘라내서 @Component 메타 데이터의 새로운 template 프로퍼티에 붙여 넣으십시오.

HeroDetailComponent에는 선택된 Hero가 아닌 그냥 Hero가 있습니다. 템플릿의 “hero”라는 단어로 “selectedHero”라는 단어를 대체하십시오. 완료되면 새 템플릿은 다음과 같이 표시됩니다.

src/app/hero-detail.component.ts (template)

1
2
3
4
5
6
7
8
9
10
11
12
13
@Component({
selector: 'hero-detail',
template: `
<div *ngIf="hero">
<h2>{{hero.name}} details!</h2>
<div><label>id: </label>{{hero.id}}</div>
<div>
<label>name: </label>
<input [(ngModel)]="hero.name" placeholder="name"/>
</div>
</div>
`
})

hero 프로퍼티 추가

HeroDetailComponent 템플릿은 컴포넌트의 hero 프로퍼티에 바인딩됩니다. 그 프로퍼티를 다음과 같이 HeroDetailComponent 클래스에 추가합니다.

src/app/hero-detail.component.ts (hero property)

1
hero: Hero;

hero 프로퍼티는 Hero의 인스턴스로 입력됩니다. Hero 클래스는 여전히 app.component.ts 파일에 있습니다. 이제 Hero 클래스를 참조해야하는 두 가지 Component가 존재합니다. Angular 스타일 가이드는 파일당 하나의 클래스를 권장합니다.

Hero 클래스를 app.component.ts에서 자신의 hero.ts 파일로 옮깁니다.

src/app/hero.ts

1
2
3
4
export class Hero {
id: number;
name: string;
}

Hero 클래스가 자체 파일에 있기 때문에, AppComponentHeroDetailComponentimport 해야합니다. app.component.ts 파일과 hero-detail.component.ts 파일의 상단에 다음 import 문을 추가합니다.

src/app/hero-detail.component.ts

1
import { Hero } from './hero';

hero 프로퍼티는 input 프로퍼티입니다.

이 문서의 뒤에 있는 부모인 AppComponentHeroDetailComponenthero 프로퍼티에 selectedHero를 바인딩하여 표시할 Hero를 자식 HeroDetailComponent에게 알려줍니다. 바인딩은 다음과 같습니다.

src/app/app.component.html

1
<hero-detail [hero]="selectedHero"></hero-detail>

등호(=)의 왼쪽에 있는 hero 프로퍼티 주위에 대괄호를 넣으면 프로퍼티 바인딩 표현식의 대상이됩니다. target 바인딩 프로퍼티를 input 프로퍼티로 선언 해야합니다. 그렇지 않으면 Angular가 바인딩을 거부하고 오류를 던집니다.

먼저 @angular/core import 문을 수정하여 Input 심볼을 포함시킵니다.

src/app/hero-detail.component.ts (excerpt)

1
import { Component, Input } from '@angular/core';

그런 다음 가져온 @Input Decorator를 추가하여 hero입력 프로퍼티라고 선언합니다.

src/app/hero-detail.component.ts (excerpt)

1
@Input() hero: Hero;

Attribute Directive 페이지에서 입력 프로퍼티에 대해 자세히 읽어보십시오.

이게 전부입니다. heroHeroDetailComponent 클래스의 유일한 프로퍼티입니다.

src/src/app/hero-detail.component.ts

1
2
3
export class HeroDetailComponent {
@Input() hero: Hero;
}

하는 일은 hero 입력 프로퍼티를 통해 Hero 객체를 받은 다음 템플릿의 해당 프로퍼티에 바인딩하는 것입니다.

다음은 완전한 HeroDetailComponent입니다.

src/app/hero-detail.component.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import { Component, Input } from '@angular/core';
import { Hero } from './hero';
@Component({
selector: 'hero-detail',
template: `
<div *ngIf="hero">
<h2>{{hero.name}} details!</h2>
<div><label>id: </label>{{hero.id}}</div>
<div>
<label>name: </label>
<input [(ngModel)]="hero.name" placeholder="name"/>
</div>
</div>
`
})
export class HeroDetailComponent {
@Input() hero: Hero;
}

AppModule에 HeroDetailComponent 선언

모든 Component는 하나의(그리고 오직 한번만) Angular 모듈로 선언되어야합니다.

에디터에서 app.module.ts 파일을 열고 HeroDetailComponent 파일을 import해서 참조합니다.

src/app/app.module.ts

1
import { HeroDetailComponent } from './hero-detail.component';

HeroDetailComponent를 모듈의 declarations 배열에 추가합니다.

src/app/app.module.ts

1
2
3
4
declarations: [
AppComponent,
HeroDetailComponent
],

일반적으로 declarations 배열은 모듈에 속한 Component, PipeDirective 응용 프로그램 목록을 포함합니다. 다른 Component가 참조하기 전에 Component를 모듈에서 선언해야합니다. 예제의 모듈은 AppComponentHeroDetailComponent라는 두 개의 어플리케이션 컴포넌트만을 선언합니다.

NgModules 가이드에서 Angular 모듈에 대해 자세히 읽어보십시오.

AppComponentHeroDetailComponent 추가

AppComponent는 여전히 master/detail입니다. 템플릿의 해당 부분을 잘라내기 전에 Hero 상세 보기를 표시하는데 사용되었습니다. 이제 HeroDetailComponent에 위임할 것입니다.

HeroDetailComponent 메타 데이터의 CSS selectorhero-detail을 다시 보겠습니다. 이것은 HeroDetailComponent를 나타내는 엘리먼트의 태그 이름입니다.

AppComponent 템플릿 밑에 <hero-detail> 엘리먼트를 추가합니다. 이 엘리먼트에 Hero 상세 보기가 보여집니다.

AppComponentselectedHero 프로퍼티를 HeroDetailComponenthero 프로퍼티에 바인딩함으로써 master AppComponentHeroDetailComponent와 연결합니다.

app.component.ts (excerpt)

1
<hero-detail [hero]="selectedHero"></hero-detail>

이제 selectedHero가 바뀔 때마다 HeroDetailComponent는 새로운 Hero를 표시합니다.

수정된 AppComponent 템플릿은 다음과 같습니다.

app.component.ts (excerpt)

1
2
3
4
5
6
7
8
9
10
11
12
template: `
<h1>{{title}}</h1>
<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>
<hero-detail [hero]="selectedHero"></hero-detail>
`,

무엇이 바뀌었나?

이전과 마찬가지로 사용자가 Hero 이름을 클릭할 때마다 Hero 상세 보기가 Heroes 목록 아래에 표시됩니다. 이제 HeroDetailView에서 이러한 상세 보기를 제공합니다.

원래의 AppComponent를 두 개의 컴포넌트로 리팩토링하면 현재와 미래 모두 혜택을 얻을 수 있습니다.

  1. AppComponent의 책임을 줄임으로써 AppComponent를 단순화했습니다.
  2. 부모 AppComponent를 건드리지 않고도 HeroDetailComponent를 풍부한 Rich Hero editor로 확장시킬 수 있습니다.
  3. Hero 상세보기를 건드리지 않고도 AppComponent를 확장시킬 수 있습니다.
  4. HeroDetailComponent는 미래의 상위 Component의 템플리트에서 다시 사용할 수 있습니다.

Review the app structure

다음과 같은 구조인지 확인하십시오.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
angular-tour-of-heroes
┣ src
┃ ┣ app
┃ ┃ ┣ app.component.ts
┃ ┃ ┣ app.module.ts
┃ ┃ ┣ hero.ts
┃ ┃ ┗ hero-detail.component.ts
┃ ┣ main.ts
┃ ┣ index.html
┃ ┣ styles.css
┃ ┣ systemjs.config.js
┃ ┗ tsconfig.json
┣ node_modules ...
┗ package.json

이 페이지에서 설명하는 코드 파일은 다음과 같습니다.

src/app/hero-detail.component.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import { Component, Input } from '@angular/core';
import { Hero } from './hero';
@Component({
selector: 'hero-detail',
template: `
<div *ngIf="hero">
<h2>{{hero.name}} details!</h2>
<div><label>id: </label>{{hero.id}}</div>
<div>
<label>name: </label>
<input [(ngModel)]="hero.name" placeholder="name"/>
</div>
</div>
`
})
export class HeroDetailComponent {
@Input() hero: Hero;
}

src/app/app.component.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
import { Component } from '@angular/core';
import { Hero } from './hero';
const HEROES: Hero[] = [
{ id: 11, name: 'Mr. Nice' },
{ id: 12, name: 'Narco' },
{ id: 13, name: 'Bombasto' },
{ id: 14, name: 'Celeritas' },
{ id: 15, name: 'Magneta' },
{ id: 16, name: 'RubberMan' },
{ id: 17, name: 'Dynama' },
{ id: 18, name: 'Dr IQ' },
{ id: 19, name: 'Magma' },
{ id: 20, name: 'Tornado' }
];
@Component({
selector: 'my-app',
template: `
<h1>{{title}}</h1>
<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>
<hero-detail [hero]="selectedHero"></hero-detail>
`,
styles: [`
.selected {
background-color: #CFD8DC !important;
color: white;
}
.heroes {
margin: 0 0 2em 0;
list-style-type: none;
padding: 0;
width: 15em;
}
.heroes li {
cursor: pointer;
position: relative;
left: 0;
background-color: #EEE;
margin: .5em;
padding: .3em 0;
height: 1.6em;
border-radius: 4px;
}
.heroes li.selected:hover {
background-color: #BBD8DC !important;
color: white;
}
.heroes li:hover {
color: #607D8B;
background-color: #DDD;
left: .1em;
}
.heroes .text {
position: relative;
top: -3px;
}
.heroes .badge {
display: inline-block;
font-size: small;
color: white;
padding: 0.8em 0.7em 0 0.7em;
background-color: #607D8B;
line-height: 1em;
position: relative;
left: -1px;
top: -4px;
height: 1.8em;
margin-right: .8em;
border-radius: 4px 0 0 4px;
}
`]
})
export class AppComponent {
title = 'Tour of Heroes';
heroes = HEROES;
selectedHero: Hero;
onSelect(hero: Hero): void {
this.selectedHero = hero;
}
}

src/app/hero.ts

1
2
3
4
export class Hero {
id: number;
name: string;
}

src/app/app.module.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { AppComponent } from './app.component';
import { HeroDetailComponent } from './hero-detail.component';
@NgModule({
imports: [
BrowserModule,
FormsModule
],
declarations: [
AppComponent,
HeroDetailComponent
],
bootstrap: [ AppComponent ]
})
export class AppModule { }

어디까지 했나?

이 페이지에서 달성한 내용은 다음과 같습니다.

  • 재사용 가능한 Component를 만들었습니다.
  • Component가 입력을 허용하는 방법을 배웠습니다.
  • Angular 모듈에서 필요한 애플리케이션 Directive를 선언하는 방법을 배웠습니다. NgModule Decoratordeclarations 배열에 Directive를 나열했습니다.
  • 부모 Component를 하위 Component에 바인딩하는 방법을 배웠습니다.

앱은 이 라이브 예제/ 예제 다운로드와 비슷해야합니다.

앞으로의 여정

Tour of Heroes 앱은 공유 Component로 재사용할 수 있지만 App (Mock) 데이터는 여전히 AppComponent 내에 하드 코딩되어 있습니다. 그것은 서스테이닝이 쉽지 않습니다. 데이터 액세스는 별도의 Service로 리팩토링 되어야하며 데이터가 필요한 Component간에 공유되어야합니다.

다음 튜토리얼 페이지에서 Service를 만드는 방법을 배웁니다.


이 내용은 나중에 참고하기 위해 제가 공부하며 정리한 내용입니다.
의역, 오역, 직역이 있을 수 있음을 알려드립니다.
This post is a translation of this original article [https://angular.io/tutorial/toh-pt3]

참고

공유하기