Skip to content

6. 부모/자식 데이터 공유하기


Angular에서는 부모 컴포넌트와 자식 컴포넌트가 데이터를 주고받는 패턴이 자주 사용된다. 이 패턴은 @Input(), @Output() 데코레이터로 구현한다.


이 문서에서 설명하는 내용은 라이브 예제 링크 / 다운로드 링크에서 직접 확인하거나 다운받아 확인할 수 있다.


이런 구조가 있다고 하자:


<parent-component>
  <child-component></child-component>
</parent-component>


이 구조에서 <parent-component><child-component>의 컨텍스트를 제공하는 역할을 한다.


@Input(), @Output() 데코레이터를 활용하면 자식 컴포넌트가 부모 컴포넌트와 통신할 수 있다. 이 때 @Input()은 부모 컴포넌트에서 자식 컴포넌트로 데이터를 전달할 때 사용한다. 그리고 @Output()은 반대로 자식 컴포넌트에서 부모 컴포넌트로 데이터를 전달할 때 사용한다.


1. 자식 컴포넌트로 데이터 전달하기

@Input() 데코레이터는 자식 컴포넌트/디렉티브에 있는 특정 프로퍼티가 부모 컴포넌트/디렉티브에서 값을 받는다는 것을 지정하는 데코레이터이다.


001


@Input() 데코레이터는 부모-자식 관계에서만 사용할 수 있다.


1) 자식 컴포넌트 설정하기

자식 컴포넌트에서 @Input() 데코레이터를 사용하려면 먼저 Input 심볼을 로드해야 한다. 이렇게 구성하면 된다.


src/app/item-detail/item-detail.component.ts
import { Component, Input } from "@angular/core"; // Input 심볼을 로드합니다.
export class ItemDetailComponent {
  @Input() item = ""; // 프로퍼티에 @Input() 데코레이터를 지정합니다.
}


이렇게 구현하면 @Input() 데코레이터는 string 타입으로 선언된 item 프로퍼티가 입력 프로퍼티라는 것을 지정하는데, @Input() 프로퍼티에는 number, string, boolean, object 등 어떠한 타입이라도 자유롭게 전달될 수 있다. item 프로퍼티에 맞는 타입으로 데이터를 전달하는 것은 부모 컴포넌트의 몫이다.


다음에는 자식 컴포넌트 템플릿에 이런 코드를 추가한다:


src/app/item-detail/item-detail.component.html
<p>Today's item: {{item}}</p>


2) 부모 컴포넌트 설정하기

다음에는 부모 컴포넌트 템플릿에서 프로퍼티를 바인딩해야 한다. 이 문서에서 다루는 예제에서 부모 컴포넌트의 템플릿 파일은 app.component.html 파일이다.


부모 컴포넌트 템플릿에 자식 컴포넌트 셀렉터 <app-item-detail>를 추가한다.


프로퍼티 바인딩을 사용해서 부모 컴포넌트의 currentItem 프로퍼티를 자식 컴포넌트의 item 프로퍼티로 바인딩한다.


src/app/app.component.html
<app-item-detail [item]="currentItem"></app-item-detail>


부모 컴포넌트 클래스에서 currentItem 값을 할당한다:


src/app/app.component.ts
export class AppComponent {
  currentItem = "Television";
}


@Input() 데코레이터를 사용하면 부모 컴포넌트의 currentItem 프로퍼티 값이 자식 컴포넌트 item 프로퍼티로 전달되기 때문에 자식 컴포넌트 템플릿에 정의된 대로 Television이라는 값이 화면에 렌더링된다.


아래 그림을 보면서 구조를 확인해 보자:


002


이 때 프로퍼티 바인딩 대상이 되는 프로퍼티는 부모 컴포넌트 템플릿에서 대괄호([])로 감싼 프로퍼티이며, 자식 컴포넌트 클래스에서 @Input() 데코레이터를 지정한 프로퍼티이다. 바인딩 하는 대상은 등호(=) 오른쪽에 있는 부모 컴포넌트 클래스 프로퍼티이다.


3) @Input() 변화 감지하기

@Input() 프로퍼티로 전달되는 값이 변경되는 것을 감지하려면 Angular 라이프싸이클 후킹 함수 중 OnChanges를 활용하면 된다.


2. 부모 컴포넌트로 데이터 전달하기

자식 컴포넌트/디렉티브에 @Output() 데코레이터를 사용하면 부모 컴포넌트/디렉티브로 데이터를 전달할 수 있다.


003


@Output() 데코레이터는 자식 컴포넌트 프로퍼티 중 부모 컴포넌트로 데이터를 보내는 프로퍼티를 지정하는 역할을 한다.


그리고 나면 자식 컴포넌트에서 이벤트를 발생시켜서 부모 컴포넌트로 보내면 된다. 이벤트를 발생시키기 위해 @Output() 데코레이터는 반드시 EventEmitter 타입의 프로퍼티에 선언해야 한다. 이 커스텀 이벤트 클래스는 @angular/core 패키지에서 제공하는 클래스이다.


이번에는 자식 컴포넌트 템플릿에 있는 HTML <input> 엘리먼트에서 데이터를 받아 부모 컴포넌트로 전달하는 예제를 알아보자.


@Output() 데코레이터를 사용하려면 부모 컴포넌트와 자식 컴포넌트를 모두 수정해야 한다.


1) 자식 컴포넌트 설정하기

이제부터 살펴볼 예제에서 사용자가 <input> 엘리먼트에 문자열을 입력하고 버튼을 클릭하면 이벤트를 발생시킨다. 그리고 EventEmitter를 사용해서 부모 컴포넌트로 이 이벤트를 전달할 것이다.


자식 컴포넌트 클래스 파일에 Output, EventEmitter 심볼을 로드한다:


src/app/item-output/item-output.component.ts
import { Output, EventEmitter } from "@angular/core";


컴포넌트 클래스에서 프로퍼티에 @Output() 데코레이터를 지정한다. 아래 예제 코드는 EventEmitter 타입으로 선언된 newItemEvent 프로퍼티에 @Output() 데코레이터를 지정한 코드이다.


src/app/item-output/item-output.component.ts
@Output() newItemEvent = new EventEmitter<string>();


이전 예제와는 이런 점이 다르다:


  • @Output(): 자식 컴포넌트에서 부모 컴포넌트로 데이터를 전달하는 프로퍼티를 지정한다.
  • newItemEvent: @Output() 데코레이터가 지정된 프로퍼티이다.
  • EventEmitter<string>: @Output() 데코레이터의 타입이다.
  • new EventEmitter<string>(): 문자열 타입으로 이벤트를 전달하는 이벤트 이미터 인스턴스를 생성한다.


Note


자식 컴포넌트 클래스에 addNewItem() 메서드를 추가한다:


src/app/item-output/item-output.component.ts
export class ItemOutputComponent {
  @Output() newItemEvent = new EventEmitter<string>();

  addNewItem(value: string) {
    this.newItemEvent.emit(value);
  }
}


addNewItem() 함수는 @Output() 데코레이터가 지정된 newItemEvent를 활용해서 이벤트를 발생시키며, 이 때 <input> 엘리먼트에 사용자가 입력한 값을 함께 전달한다.


2) 자식 컴포넌트 템플릿 설정하기

자식 컴포넌트 템플릿에는 폼 컨트롤이 2개 있다. 하나는 사용자가 값을 입력할 수 있는 HTML <input> 엘리먼트이며, 이 엘리먼트에는 템플릿 참조 변수 newItem이 지정되어 있다. 사용자가 <input> 엘리먼트에 입력한 값을 참조하려면 #newItem 변수의 value 프로퍼티를 참조하면 된다.


src/app/item-output/item-output.component.html
<label for="item-input">Add an item:</label>
<input type="text" id="item-input" #newItem />
<button (click)="addNewItem(newItem.value)">Add to parent's list</button>


그리고 다른 엘리먼트는 click 이벤트가 바인딩 된 <button> 엘리먼트이다.


이 엘리먼트의 (click) 이벤트는 자식 컴포넌트 클래스의 addNewItem() 메서드와 바인딩 되어 있다. addNewItem() 메서드는 #newItem.value 값을 인자로 받는다.


3) 부모 컴포넌트 설정하기

이 문서에서 다루는 예제에서 AppComponent에는 항목들을 배열 형태로 저장하는 items 배열이 있다.


src/app/app.component.ts
export class AppComponent {
  items = ["item1", "item2", "item3", "item4"];

  addItem(newItem: string) {
    this.items.push(newItem);
  }
}


그리고 부모 컴포넌트의 addItem() 메서드는 인자로 받은 문자열 items 배열에 저장한다.


4) 부모 컴포넌트 템플릿 설정하기

부모 컴포넌트 메서드와 자식 컴포넌트의 이벤트는 부모 컴포넌트 템플릿에서 바인딩 한다.


부모 컴포넌트 템플릿 파일 app.component.html에 자식 컴포넌트 셀렉터 <app-item-output>를 추가한다.


src/app/app.component.html
<app-item-output (newItemEvent)="addItem($event)"></app-item-output>


이벤트 바인딩 (newItemEvent)='addItem($event)'에 사용된 문법을 보면, 자식 컴포넌트에서 newItemEvent 이벤트가 발생하면 부모 컴포넌트 메서드 addItem()을 실행한다. $event 객체는 자식 컴포넌트에서 보낸 데이터가 담겨 있다. 이 예제에서는 자식 컴포넌트 템플릿의 <input>에 사용자가 입력한 값이 전달된다.


@Output() 데코레이터가 동작하는 것을 확인하기 위해 부모 컴포넌트에 이런 코드를 추가한다:


src/app/app.component.html
<ul>
  <li *ngFor="let item of items">{{item}}</li>
</ul>


*ngForitems 배열을 순회하며 템플릿을 반복해서 렌더링한다. 이제 사용자가 자식 컴포넌트 템플릿에서 <input>에 값을 입력하고 버튼을 누르면 자식 컴포넌트에서 이벤트가 발생하며, 이벤트가 발생하면 이 이벤트와 바인딩된 부모 컴포넌트 addItem() 메서드가 실행되면서 items 배열에 새로운 항목이 추가되고 화면에 렌더링된다.


3. @Input(), @Output() 함께 사용하기

@Input() 데코레이터와 @Output() 데코레이터는 자식 컴포넌트에서 함께 사용할 수도 있다:


src/app/app.component.html
<app-input-output [item]="currentItem" (deleteRequest)="crossOffItem($event)">
</app-input-output>


@Input() 데코레이터가 지정된 item 프로퍼티는 부모 컴포넌트의 currentItem 프로퍼티에서 값을 받아온다. 그리고 사용자가 삭제 버튼을 클릭하면 자식 컴포넌트에서 deleteRequest 이벤트가 발생하는데, 이 이벤트는 부모 컴포넌트가 감지하고 있다가 crossoffItem() 메서드를 실행한다.


아래 그림을 보면서 자식 컴포넌트 <app-input-output>에 사용된 @Input() 데코레이터와 @Output() 데코레이터가 어떻게 연결되는지 확인해 보자.


004


이 코드에서 자식 컴포넌트 셀렉터는 <app-input-output>이며, 자식 컴포넌트에 있는 item 프로퍼티와 deleteRequest 프로퍼티는 각각 @Input() 데코레이터와 @Output() 데코레이터가 지정되었다. 그리고 currentItem 프로퍼티와 crossOffItem() 메서드는 부모 컴포넌트 클래스에 정의되어 있다.


Note

  • 프로퍼티 바인딩과 이벤트 바인딩을 결합한 상자 안에 있는 바나나 ([()]) 문법에 대해 자세하게 알아보려면 양방향 바인딩 문서를 참고한다.

References