Angular Tutorial 3 — Datas
Previous article:
지난 글에서는 Angular 작업 환경은 어떤 방식으로 구성되어 있고, Angular 컴포넌트를 어떻게 생성하는 지에 대해서 살펴보았습니다. 또 ngModel
디렉티브를 활용하여 데이터를 바인딩 하고, 수정을 어떻게 이루는 지에 대해서도 살펴보았습니다.
이번 글에서는 Angular의 데이터 제어 및 처리에 대해서 이해하고, 애플리케이션 전체에 흐르는 복잡한 데이터를 어떻게 관리해야하는가에 대해 소개해보고자 합니다.
이 챕터의 목표
- 임시 데이터를 활용해 운동 목록을 노출시킵니다.
- 각 운동을 수정할 수 있게 합니다.
- 컴포넌트 분리 방식을 이해합니다.
운동 목록 나타내기
임시 데이터 (mock-data) 생성하기
언젠가 미래에는 데이터를 서버에서 불러오겠지만, 서버가 없는 상황을 가정하여 임시 데이터를 생성하고, 해당 데이터를 서버에서 불러온 것으로 가정하겠습니다.
src/app/mock-workouts.tsimport { Workout } from './Workout';export const WORKOUTS: Workout[] = [
{ id: 0, name: '데드리프트', count: 0 },
{ id: 1, name: '스쿼트', count: 0 },
{ id: 2, name: '벤치프레스', count: 0 },
{ id: 3, name: '레그프레스', count: 0 },
{ id: 4, name: '힙익스텐션', count: 0 }
]
이제 지난 튜토리얼에서 생성했던 workouts
컴포넌트를 수정해보겠습니다.
컴포넌트에서 데이터 보여주기
- 먼저 데이터를 불러옵니다.
- workouts 프로퍼티를 선언하고 Workouts 배열을 바인딩합니다.
- 템플릿에서
*ngFor
를 활용해 배열을 순회합니다.
workouts.component.tsimport { Component, OnInit } from '@angular/core';
import { WORKOUTS } from '../mock-workouts';export class WorkoutsComponent implements OnInit {
workouts = WORKOUTS;
constructor() { }
ngOnInit(): void { }
}
그러고 나서 템플릿을 수정합니다.
workouts.component.html<h2>오늘의 운동</h2>
<ul class="workouts">
<li *ngFor="let workout of workouts">
<article>
<h1>{{workout.name}}</h1>
<p>운동 횟수: {{workout.count}}</p>
<div>
<label>운동 횟수:
<input [(ngModel)]="workout.count"
type="number" placeholder="횟수">
</label>
</div>
</article>
</li>
</ul>
리스트를 나타내주기 위한 ul
과 li
를 추가한 점을 제외하면 지난 번에 작성한 내용에서 크게 다른 점은 없습니다. 반복을 위해서 *ngFor
를 사용하고 있다는 점에 주의하시길 바랍니다.
목록 컴포넌트 UI 개선하기
스크린샷에서 보았던 것처럼 수정할 생각도 없는 컴포넌트의 수정 UI가 보이니 영 어색한 느낌입니다. 그래서 유저가 클릭한 컴포넌트에 한해서 수정 UI가 보이도록 컴포넌트 UI를 개선해보겠습니다.
클릭 상태 추가하고 제어하기
workouts
컴포넌트에는 현재 ‘선택된 운동’을 나타내는 상태는 존재하지 않습니다. 선택된 운동을 나타내는 상태를 추가하고, 상태를 변경해주는 함수를 하나 생성해주겠습니다.
workouts.component.tsimport { Component, OnInit } from '@angular/core';
import { WORKOUTS } from '../mock-workouts';
import { Workout } from '../Workout';export class WorkoutsComponent implements OnInit {
workouts = WORKOUTS;
selectedWorkout: Workout;
constructor() { }
ngOnInit(): void { }
onSelect(workout: Workout): void {
this.selectedWorkout = workout
}
}
이제 onSelect
함수를 호출하면서, 운동을 나타내는 객체를 파라미터로 전달해주면 selectedWorkout
이 해당 객체 데이터로 바뀝니다.
이제 클릭 이벤트를 제어하는 영역과 상세 화면을 보여주는 영역을 분리하기 위해 템플릿을 조금 바꾸어두도록 하겠습니다. 여기서는 *ngIf
디렉티브를 사용하여 selectedWorkout
이 존재할 때만 상세 화면을 렌더링하게 처리해보겠습니다.
workouts.component.html<h2>오늘의 운동</h2>
<ul class="workouts">
<li *ngFor="let workout of workouts">
<article
(click)="onSelect(workout)"
[class.selected]="workout === selectedWorkout">
<h1>{{workout.name}}</h1>
<p>운동 횟수: {{workout.count}}</p>
</article>
</li>
</ul><div *ngIf="selectedWorkout">
<h2>{{selectedWorkout.name}}</h2>
<label>운동 횟수:
<input [(ngModel)]="selectedWorkout.count"
type="number" placeholder="횟수">
</label>
</div>
[class.selected]
는 class를 정의할 때 유용하게 사용할 수 있는데, selected
라는 클래스는 workout
이 selectedWorkout
과 동일할 때에만 적용되어, 추후에 CSS를 작성할 때 selected
클래스를 활용하면 됩니다.
*ngIf
는 참조하고있는 값이 true
면 템플릿을 반환해주지만 false
라면 빈 값을 반환해주어 렌더링되지 않게 합니다.
목록 컴포넌트 구조 개선하기
이미 느끼신 분도 계실 거라 생각하지만, 목록을 보여주는 컴포넌트에서 상세정보를 보여주는 컴포넌트가 함께 노출되면서 한 템플릿 내에서 데이터 흐름이 복잡해지는 이슈가 보입니다.
무엇보다 서로가 동일한 구조의 Workout
데이터를 제어하고 있는 데 어디서는 selectedWorkout
을 참조하고, 어디서는 workout
을 참조하니 데이터 구조가 헷갈리기도 합니다.
따라서 여기서는 운동의 상세 정보를 나타내는 workout-detail
컴포넌트를 생성하고, 데이터를 전달해주는 방식으로 변경하여 코드를 간단하게 분리해보겠습니다.
workout-detail 컴포넌트 생성하기
지난 번에 했던 것처럼 angular cli를 활용해보겠습니다.
ng generate component workout-detail
그리고 나서 운동 목록에 있던 템플릿을 가져와서 복사합니다.
workout-detail/workout-detail.component.html<div *ngIf="workout">
<h2>{{workout.name}}</h2>
<label>운동 횟수:
<input [(ngModel)]="workout.count"
type="number" placeholder="횟수">
</label>
</div>
여기서 수정하는 workout
데이터는 workout-detail
내에 있는 것이 아니라, workouts
컴포넌트에서 전달받아야하기 때문에 @Input()
데코레이터를 활용해서 Input 프로퍼티로 선언해야합니다.
workout-detail/workout-detail.component.tsimport { Component, OnInit, Input } from '@angular/core';
import { Workout } from '../Workout';@Component({
selector: 'app-workout-detail',
templateUrl: './workout-detail.component.html',
styleUrls: ['./workout-detail.component.css']
})
export class HeroDetailComponent implements OnInit {
@Input() workout: Workout;
constructor() { }
ngOnInit() { }
}
그리고 나서 부모 컴포넌트인 Workouts
컴포넌트에서 데이터를 주고받는 영역을 수정합니다.
workouts.component.html<h2>오늘의 운동</h2>
<ul class="workouts">
<li *ngFor="let workout of workouts">
<article
(click)="onSelect(workout)"
[class.selected]="workout === selectedWorkout">
<h1>{{workout.name}}</h1>
<p>운동 횟수: {{workout.count}}</p>
</article>
</li>
</ul>
<app-workout-detail [workout]="selectedWorkout">
</app-workout-detail>
이렇게 하면 코드 구조가 단순해져서 추후에 컴포넌트를 수정할 때에도 용이해지고, 재사용성을 고려하기도 좋아집니다.
마무리
이번 글에서는 데이터를 불러와서 화면에 노출시켜주고, 화면에 노출시켜 줄 때 데이터에 따라 다르게 보여주는 방식을 살펴보았습니다. 다음 글에서는 데이터 처리 영역과 뷰 영역을 분리하는 방법에 대해서 살펴보도록 하겠습니다.
감사합니다.