컴포넌트 커뮤니케이션
1) 부모-> 자식 컴포넌트
✅ 부모 컴포넌트에서 자식 컴포넌트로 데이터를 전달할 때는 @Input 데코레이터를 이용하면 부모->자식 컴포넌트로 속성값을 세팅할 수 있다.
✅ 자식 컴포넌트의 속성을 ES6 setter를 이용해서 데이터를 가공한 후에 비공개 속성값을 세팅할 수도 있다. get을 통해 비공개 속성값을 공개적으로 가져오게 된다.
✅ @ViewChild 데코레이터를 이용하여 자식 컴포넌트 클래스에 인스턴스 레퍼런스를 부모 컴포넌트에서 직접 전달받을 수도 있다.
2) 자식->부모 컴포넌트
✅ 자식 컴포넌트에서 부모 컴포넌트로 이벤트를 발송할 때는 @Output 데코레이터와 EventEmitter를 이용하여 특정 이벤트와 데이터를 발생시킬 수 있다.
✅ 이벤트 바인딩을 통해 데이터를 전달 받거나,
✅ 자식 컴포넌트가 부모 컴포넌트를 생성자를 통해 직접 주입받을 수 있다. (단, 강력한 의존관계가 성립되므로 꼭 필요한 경우에만 사용하는 것이 좋음)
TODO 리스트 예제 응용하기
Todos 부모 컴포넌트와 Todos, AddTodo 라는 2개의 자식 컴포넌트 간 데이터를 주고받는지를 알아본다.
1. todo 컴포넌트 생성
이전 포스팅에서 반복문으로 표현했던 Todo 목록을 별도의 컴포넌트로 분리할 것이다. todos 폴더 우클릭 후 [New Component]를 선택하여 입력창에 todo를 입력한다. 그러면 오른쪽 사진과 같이 todos/todo 컴포넌트가 생성된다.
2. 모델 클래스 만들기
arc/app/todo 폴더에서 share 폴더 생성 후 todo.model.ts 파일을 생성한다. 하나의 모델 클래스를 만들어놓고, 여러 컴포넌트에서 참조해서 쓸 수 있도록 할 것이다.
src/app/todo/share/todo.model.ts 파일
// Todo 클래스는 2개의 속성을 정의한다.
export class Todo {
done: boolean = false;
text: string = '';
}
src/app/todo/todos/todos.component.ts 파일
import { Todo } from '../share/todo.model'; // 모델 추가
export class TodosComponent {
// todos: { // 변경 전 : 모델로 분리하기 전
// done: boolean,
// text: string
// }[]
todos: Todo[]; // 변경 후 : 모델로 분리한 후 (모델 클래스 Todo[]로 대체)
}
3. @Input 사용하기
src/app/todo/todos/todo/todo.component.ts 파일
import { Todo } from '../../share/todo.model';
export class TodoComponent {
@Input() todo: Todo = new Todo; // 하나의 객체를 의미하는 Todo 클래스로 대체
// @Input으로 부모->자식 컴포넌트로 데이터 전달
src/app/todo/todos/todos.component.html 파일
<div *ngFor="let todo of todos" (click)="toggleTodo(todo)">
<!-- app-todo 셀렉터로 todo 속성을 설정 -->
<app-todo [todo]="todo"></app-todo>
</div>
src/app/todo/todos/todo/todo.component.html 파일
<!-- 체크박스 로직을 분리 -->
<input type="checkbox" [checked]="todo.done"><label>{{ todo.text }}</label>
여기까지 진행하면 흐름이 이렇게 된다.
4. add-todo 컴포넌트 생성
이제 @Input과 반대의 케이스인 @Output 케이스를 보기 위해 별도의 컴포넌트로 분리할 것이다. todos 폴더 우클릭 후 [New Component]를 선택하여 입력창에 add-todo를 입력한다. 그러면 오른쪽 사진과 같이 todos/add-todo 컴포넌트가 생성된다.
src/app/todo/todos/add-todo/add-todo.component.html 파일
<!-- 할 일 추가 로직을 분리 -->
<input type="text" placeholder="할 일 추가" [(ngModel)]="newText">
<button (click)="addTodo(newText)">추가</button>
5. @Output 사용하기
src/app/todo/todos/add-todo/add-todo.component.ts 파일
import { Component, Output, EventEmitter } from '@angular/core';
// 생략...
export class AddTodoComponent {
// @Input 및 EventEmitter를 이용하여 자식->부모 컴포넌트로 이벤트 및 데이터를 전달
@Output() onTodoAdded = new EventEmitter();
newText: string = '';
addTodo(newText: string) {
this.onTodoAdded.emit(newText);
this.newText = '';
}
}
src/app/todo/todos/todos.component.html 파일
<div>
<!-- 할 일 추가 부분에 onTodoAdded 내용 추가 -->
<app-add-todo (onTodoAdded)="addTodo($event)"></app-add-todo>
</div>
여기까지 진행하면 흐름이 이렇게 된다.
여기까지의 최종 소스
src/app/todo/todos/add-todo/add-todo.component.html
<button (click)="addTodo(newText)">+</button>
<input type="text" placeholder="할 일 추가" [(ngModel)]="newText">
src/app/todo/todos/add-todo/add-todo.component.ts
import { Component, Output, EventEmitter } from '@angular/core';
@Component({
selector: 'app-add-todo',
templateUrl: './add-todo.component.html',
styleUrl: './add-todo.component.css'
})
export class AddTodoComponent {
@Output() onTodoAdded = new EventEmitter();
newText: string = '';
addTodo(newText: string) {
this.onTodoAdded.emit(newText);
this.newText = '';
}
}
src/app/todo/todos/todo/todo.component.html
<input type="checkbox" [checked]="todo.done"><label>{{ todo.text }}</label>
src/app/todo/todos/todo/todo.component.ts
import { Component, Input } from '@angular/core';
import { Todo } from '../../share/todo.model';
@Component({
selector: 'app-todo',
templateUrl: './todo.component.html',
styleUrl: './todo.component.css'
})
export class TodoComponent {
@Input() todo: Todo = new Todo;
}
src/app/todo/todos/todos.component.html
<div class="title">
<h1>나의 일정</h1>
<h2>{{ today | date : 'M월 d일'}}</h2>
</div>
<div>
<div *ngFor="let todo of todos" (click)="toggleTodo(todo)">
<app-todo [todo]="todo"></app-todo>
</div>
</div>
<div>
<app-add-todo (onTodoAdded)="addTodo($event)"></app-add-todo>
</div>
src/app/todo/todos/todos.component.ts
import { Component } from '@angular/core';
import { Todo } from '../share/todo.model';
@Component({
selector: 'app-todos',
templateUrl: './todos.component.html',
styleUrl: './todos.component.css'
})
export class TodosComponent {
newText: string = '';
todos: Todo[];
constructor() {
this.todos = [
{ done: false, text: '운동하기' },
{ done: true, text: '공부하기' },
]
}
toggleTodo(todo: any) {
todo.done = !todo.done
}
addTodo(newText: string) {
this.todos.push({
done : false,
text : newText
})
}
}
컴포넌트 css 처리 추가
src/app/todo/todos/todos.component.css
.title {
background-color: blueviolet;
padding: 46px 26px 26px 16px;
color: white;
font-weight: normal;
}
h1, h2 {
margin: 0;
font-weight: normal;
}
h1 {
margin-bottom: 16px;
}
app-todo, app-add-todo {
border-bottom: 1px solid #cccccc;
}
src/app/todo/todos/todo/todo.component.css
:host {
display: block;
padding: 16px;
color: darkgray;
background-color: white;
}
input {
position: relative;
}
input:before {
content: "";
display: inline-block;
width: 20px;
height: 20px;
background-color: white;
border-radius: 20px;
position: absolute;
top: -6px;
left: -8px;
border: 1px solid dimgray;
}
input:checked:after{
content: '\2713';
display: inline-block;
font-size: 18px;
width: 20px;
height: 20px;
border-radius: 20px;
position: absolute;
top: -6px;
left: -8px;
border: 1px solid dimgray;
background-color: dimgray;
text-align: center;
color: white;
}
input:checked + label {
text-decoration: line-through;
}
src/app/todo/todos/add-todo/add-todo.component.css
:host {
display: block;
padding: 16px 16px 16px 10px;
background-color: white;
}
input {
display: inline-block;
font-size: 18px;
border: none;
}
input:focus, button:focus {
outline: none;
}
button {
width: 24px;
height: 24px;
border-radius: 24px;
color: white;
font-size: 16px;
line-height: 17px;
border: 1px solid dimgray;
background-color: darkblue;
}
src/styles.css
/* You can add global styles to this file, and also import other style files */
body {
margin: 0;
background-color: #dddddd;
}
css까지 입힌 모습이다.
관련 내용 읽어보기
'Frontend > Angular' 카테고리의 다른 글
앵귤러 NullInjectorError: No provider for _HttpClient! 오류 해결 (1) | 2024.03.29 |
---|---|
앵귤러 Could not fine the '@angular-devkit/build-angular:dev-server' builder's node package. 오류 해결 (0) | 2024.03.25 |
앵귤러 모듈 컴포넌트 개념 및 Todo 리스트 예제 살펴보기 (0) | 2024.03.22 |
앵귤러 프로젝트 기본 구조 및 핵심 파일 살펴보기 (0) | 2024.03.21 |
앵귤러 Property has no initializer and is not definitely assigned in the constructor 오류 해결 (0) | 2024.03.20 |