5장 두번째 애플리케이션 - 트렐로
이번 장에서 다룰 내용
- 예제 트렐로 애플리케이션 소개
- Typescript 네임스페이스와 모듈
- Typescript 제네릭
- Angular 고급 컴포넌트
- Angular 생명주기 훅
트렐로 예제 애플리케이션 소개
실제 트렐로(일정관리와 협업을 지원하는 개발도구)의 축소 버전
애플리케이션 개요
- 작업리스트가 있는 보드를 만들고
- 보드마다 새로운 작업과 하위 작업을 추가 할 수 있음
기술 개요
- 컴포넌트를 추가하고 컴포넌트 간 데이터를 공유하는 방법을 실습
- 싱글톤 서비스를 사용하여 컴포넌트 사이에 공통으로 사용되는 데이터를 생성
Typescript 네임스페이스와 모듈
- Namespace(네임스페이스) : 내부모듈
- Module(모듈) : 외부모듈
유니버셜 네임스페이스
- Javascript는 모든 클래스와 함수를 window 네임스페이스에 추가(window는 현재 창과 관련된 모든 함수, 클래스를 포함하는 유니버셜 네임스페이스)
- 이러한 함수, 클래스는 웹애플리케이션의 모든 곳에서 접근이 가능
- 애플리케이션 규모가 커지면 하나의 네임스페이스 아래 모든 함수와 클래스를 사용하는 것이 좋지 않음
- 유니버셜 네임스페이스가 모든 함수와 클래스를 갖게 되면 충돌이 일어날 수 있음
- 같은 이름을 가진 클래스나 함수를 선언 할 수 없음
/* 자바스크립트 */
function getDate(){
return true;
}
function getDate(){ //getDate 함수를 덮어 씀
return false;
}
window.getDate(); // false가 출력됨
- 이런 문제를 해결하기 위해서 컨테이너(네임스페이스와 모듈)가 있음
- 컨테이너가 있으면 각 클래스나 함수가 컨테이너 안에서 캡슐화 되고 컨테이너 이름만 유니버셜 네임스페이스에 노출됨
Typescript 네임스페이스
- namespace 라는 키워드를 제공하여 관련 함수, 클래스와 인터페이스를 하나의 namespace 아래 캡슐화
namespace WebSeriveResponse{}
- 네임스페이스 외부의 모든 사용자는 다음과 같은 점 표기법으로 접근 가능
«네임스페이스이름».«함수/클래스 이름»
- 네임스페이스 아래 정의된 모든 함수와 클래스는 네임스페이스 외부에는 표시 안됨
- 14행에서 WebServiceResponse에 WebResponse 속성이 없다는 Typescript 경고가 뜸
- 10행에서는 네임스페이스 내부이기 때문에 WebResponse 클래스에 엑세스 가능
export 키워드
- export 키워드를 사용하여 네임스페이스 내부의 특정 멤버를 외부에 공개할 수 있음
namespace WebServiceResponse{
export class WebResponse{
getResponse(){
return 'Success'
}
sendResponse() {
return '200 OK'
}
}
let localResponse = new WebResponse();
localResponse.getResponse();
localResponse.sendResponse();
}
let response = new WebServiceResponse.WebResponse();
response.getResponse();
response.sendResponse();
중첩(nested) 네임스페이스
- 네임스페이스 안에 또 다시 네임스페이스를 추가하여 중첩된 캡슐화를 지원
- 외부에 노출되는 중첩 네임스페이스에도 export 키워드가 붙어야 함
- 특정 기능 테스트나 하위 기능 테스트에 유용
namespace WebServiceResponse{
let url: string;
export namespace ServiceResponse{ // 중첩 네임스페이스
export class WebResponse {
getResponse() {
return 'Success'
}
sendResponse() {
return '200 OK'
}
}
}
let localResponse = new ServiceResponse.WebResponse();
localResponse.getResponse();
localResponse.sendResponse();
}
let response = new WebServiceResponse.ServiceResponse.WebResponse(); // 외부네임스페이스.내부네임스페이스.내부클래스
response.getResponse();
response.sendResponse();
Typescript 모듈
- 모듈 장점 : 모듈로더를 사용할 수 있음, 비동기 동작을 제공, 결과적으로 애플리케이션의 속도를 높일 수 있음
- 사용가능한 모듈로더가 몇가지 있으며 구문과 관리 방법이 각각 다름
정의
- 모든 파일이 별도의 모듈이고 파일 이름이 모듈 이름
- 파일을 생성하여 모듈을 만들 수 있음
- 명시적으로 정의되지 않는 한 모듈 내부의 모든 컨텐츠는 캡슐화 되어 외부에서 접근 불가
- 다음과 같은 코드로 service.ts 라는 파일을 만들면 service라는 모듈이 만들어지고 다른 모듈에서 이 모듈을 참조할 수 있음
interface iBoardService{
url:string;
getBoardInformation();
}
class BoardService{
url:string;
getBoardInformation(){
return '사용가능한 보드 없음';
}
}
let boardService = new BoardService();
boardService.getBoardInformation();
모듈 export
- 모듈의 멤버를 공개하려면 export 키워드를 사용
- export를 사용하여 노출 시킬 멤버를 명시적으로 정의
export class BoardService{
url:string;
getBoardInformation(){
return '사용가능한 보드 없음';
}
}
모듈 import
- export 한 모듈을 사용하려면 명시적으로 다른 모듈에서 import 해야 함
import { BoardService } from './module/service';
- 필요한 모듈만 선택적으로 import 가능
- import를 하면 로컬멤버와 마찬가지로 사용 가능
import { BoardService } from './module/service';
let board = new BoardService();
board.getBoardInformation();
- import 하는 멤버를 편리하게 사용할 수 있도록 멤버의 이름을 변경 할 수 있음
import { BoardService as Service } from './module/service';
let board = new Service();
board.getBoardInformation();
- import 할 멤버가 모듈의 export된 모든 멤버일 경우에는 별표를 사용하여 한번에 가져올 수도 있음
import * as Service from './module/service';
let board = new Service.BoardService();
board.getBoardInformation();
let interface: Service.iBoardService;
Tyescript 제네릭
- 함수 또는 클래스를 정의할 때 파라미터에 기대하는 타입을 명시적으로 정의하지 않고 실제 호출할 때 적절한 타입을 정의하는 개념
- 제네릭을 사용하면 재사용이 가능하고 일관성이 있는 컴포넌트를 만들 수 있음
- 복잡성을 낮추어 코드 관리의 부하를 줄일 수 있음
정의
- 함수 또는 클래스는 타입 파라미터를 사용하여 제네릭을 정의
- 타입 파라미터는 함수의 파라미터가 무엇인지 또는 클래스의 인스턴스가 어떤 타입이어야 하는 정의
- 타입 파라미터는 함수에 전달되는 일반 파라미터가 아니라 특별한 종류의 파라미터
- 함수 또는 클래스 이름 다음에 꺽쇠 괄호를 사용해 정의
- 일반적으로 타입 파라미터는 T로 정의하지만 단지 규칙일 뿐
- 함수 또는 클래스가 사용할 실제 타입은 해당 함수 또는 클래스 인스턴스를 호출할 때 정의
함수
- 함수에서 사용되는 타입을 지정하는 방법을 제외하고는 일반 함수와 매우 유사
/* 숫자를 입력 파라미터로 사용하여 해당 숫자를 배열에 추가 */
data = [];
pushNumberToArray(item: number){
this.data.push(item);
}
- 문자열을 동일한 배열에 추가 해야 하는 추가 요구사항이 생길 경우에는 문자열을 입력받는 또 다른 함수를 작성해야 함
pushStringToArray(item: string){
this.data.push(item);
}
- 새로운 요구사항이 생길 때마다 새로운 기능을 추가해야 함
- 모든 타입을 받을 수 있는 제네릭 함수로 만들수 있음
- 제네릭을 사용해서 가장 좋음 점은 호출자가 전송하려는 타입을 결정할 수 있다는 것
pushGenericToArray<T>(item: T){
this.data.push(item);
}
this.pushGenericToArray<string>('10');
this.pushGenericToArray<number>(10);
클래스
- 제네릭 클래스도 일반 함수와 비슷하게 타입 파라미터를 사용
class GenericClass<T>{
items: T[] = [];
pushDate(val:T){
this.items.push(val);
}
getDate(index:number):T{
return this.items[index];
}
}
- T 타입 파라미터를 사용하여 클래스를 정의한 후
-
이것을 사용하여 items 변수를 정의
- 제네릭을 사용하면 여러 타입으로 사용 가능
- number 타입을 타입 파라미터로 사용하여 클래스 인스턴스를 생성
let numClass = new GenericClass<number>();
numClass.pushDate(10);
numClass.pushDate(20);
let num:number = numClass.getDate(0);
- Typescript 컴파일러는 클래스 인스턴스를 만들고 인스턴스에서 함수를 호출할 때 해당 타입이 number임을 알게 됨
- pushDate 함수를 호출할 때 number이 아닌 다른 타입을 넘기려고 하면 컴파일러에서 오류 경고를 띄움
-
호출자가 타입을 정의하게 되므로 코드의 일관성을 유지
- 클래스를 사용하게 되면 다음 예제와 같이 사용자 정의 타입을 만들 수 있음
- Person 클래스를 새로 정의함
class Person{
firstName: string;
lastName: string;
}
let personClass = new GenericClass<Person>();
personClass.pushDate({ firstName: 'Homer', lastName: 'Simpeon' });
personClass.pushDate({ firstName: 'Marge', lastName: 'Simpeon' });
let person: Person = personClass.getDate(0);
제네릭 제약
- 너무 많은 제네릭의 사용은 코드가 일관되지 않게 동작할 수도 있음
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class GenericClass<T>{
items: T[] = [];
pushDate(val: T) {
this.items.push(val);
}
getDate(index: number): T {
return this.items[index];
}
getSpecificItem(title:string):T{
for(let value of this.items){
if(value.title == title){
return value;
}
}
}
}
- 11행에 T 타입에 title 프로퍼티가 존재하지 않는다는 오류 발생
-
T가 어떤 타입이 될지 알 수 없고 모든 타입에 title 프로퍼티가 있는 것은 아니기 때문
- 이런 경우 제약조건을 줄 수 있음
- 특정 프로퍼티가 포함된 타입 파라미터만 받도록 하는 것
- 제네릭에서 제약 조건은 extends 키워드를 사용해 정의
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
interface ITitle{
title: string;
}
class GenericClass<T extends ITitle>{
items: T[] = [];
pushDate(val: T) {
this.items.push(val);
}
getDate(index: number): T {
return this.items[index];
}
getSpecificItem(title:string):T{
for(let value of this.items){
if(value.title == title){
return value;
}
}
}
}
- 1행 title 프로퍼티가 있는 타입을 정의(예제에서는 인터페이스를 사용했지만 클래스를 사용해도 됨)
- 4행 인터페이스를 클래스 타입 파리미터에 제약 조건으로 추가
- 제네릭에 제약조건을 추가하면 보다 구체적으로 타입 파라미터를 정의할 수 있고 코드의 일관성이 향상
트렐로 예제 애플리케이션
- Typescript 모듈을 사용하여 코드를 작성하는 법
- Angular에서 제공하는 통신 기능 살펴보기
- 모델을 만들고 Observeble을 사용하면서 Typescript의 제네릭 사용법을 살펴보기
- Angular 컴포넌트의 생명주기에 대해 살펴보고 컴포넌트 생성과 소멸과정에서 사용자정의 기능을 추가하는 방법 살펴보기
애플리케이션 아키텍쳐
- 홈페이지, 보드, 작업, 하위작업 네가지 컴포넌트
- 홈페이지 클래스와 보드 컴포넌트 사이에 통신을 하는 서비스 클래스
- 작업과 하위 작업은 보드의 자식 컴포넌트
- 홈페이지와 보드 컴포넌트 사이의 통신은 서비스를 사용
- 보드와 작업/하위작업 사이의 통신은 @Input 과 @Output 속성을 사용
- 데이터는 json 파일을 만들어서 가져옴
- 데이터 저장은 하지 않음
코드 설정
ng new SampleTrelloApplication
트렐로 홈페이지
- 사용 가능한 모든 보드를 보여줌
- 보드 이름과 해당 보드의 작업 개수 표시
- 새 보드 추가 기능
- 보드에 작업과 하위 작업 추가
ng g component homepage
모델
- 기본 데이터 구조는 보드와 작업 / 하위작업 으로 구성
- 각각에 해당하는 Board, Task, SubTask 세가지 모델의 Typescript 모듈을 정의
/app/model/subtask.ts
export class SubTask {
id: string;
title: string;
}
/app/model/task.ts
import { SubTask } from './subtask';
export class Task {
id: number;
title: string;
subtask: SubTask[];
taskheaderId: string;
}
/app/model/board.ts
import { Task } from './task';
export class Board {
id: number;
title: string;
task: Task[];
}
홈페이지 컴포넌트
- UI에 표시할 초기 보드와 작업 목록 가져오기
- 새 보드 만들기 이벤트를 처리하여 UI에 새 보드를 추가하기
/app/app.component.html
<header>
<nav>
<a class="btn">홈페이지</a>
<a class="logo">예제 트렐로 애플리케이션</a>
</nav>
</header>
<div id="content-wrapper">
<app-homepage></app-homepage>
</div>
보드 서비스
/app/service/trello.service.ts
@Injectable()
export class TrelloService {
public Boards: Board[];
constructor(private _http: HttpClient) { }
public seedData() {
const temptask: Task = new Task();
const tempSubTask: SubTask = new SubTask();
const board: Board = new Board();
temptask.id = 1;
temptask.title = 'Hello 작업!!';
temptask.taskheaderId = '1';
tempSubTask.id = '1';
tempSubTask.title = 'Hello 작업 헤더!!';
temptask.subtask = Array();
temptask.subtask.push(tempSubTask);
board.id = 1;
board.title = 'Hello 보드';
board.task = new Array();
board.task.push(temptask);
this.Boards = new Array();
this.Boards.push(board);
return board;
}
}
- TrelloService 클래스에는 seedData라는 하나의 메서드만 있는데 이 메서드는 Task와 SubTask로 채워진 보드 객체를 반환
- Task, SubTask, Board 모듈을 사용하기 위해 꼭 import 해줘야 함
- Injectable 사용하기 위해 @angular/core에서 Injectable을 import
- HttpClient 사용하기 위해 @angular/common/http에서 HttpClient을 import
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Board } from '../model/board';
import { Task } from '../model/task';
import { SubTask } from '../model/subtask';
- 서비스 인스턴스 주입
/app/app.module.ts
import { HttpClientModule } from '@angular/common/http';
import { TrelloService } from './service/trello.service';
...
@NgModule({
...
imports:[
...
HttpClientModule
...
],
providers: [TrelloService],
...
})
...
홈페이지 - 데이터 초기화
- 홈페이지에 데이터를 채우려면 seedDate 메서드를 호출하려 로컬 Board 객체에 값을 할당하면 됨
-
이를 위해 TrelloService를 호출
- Angular는 컴포넌트의 작업 흐름을 관리하기 위해 생명주기 훅(Lift cycle hook)을 제공
- Angular가 컴포넌트를 만들 때, 컴포넌트를 초기화 할 때, 컴포넌트를 렌더링 할 때, 컴포넌트를 파괴할 때 등 특정 시기에 호출
- 모든 생명주기 훅은 Angular의 core 모듈에서 노출
OnInit
- Angular가 컴포넌트를 초기화하고 데이터 바인딩된 프로퍼티를 표시할 때 호출
- 컴포넌트에 대한 초기화 작업을 하거나 입력 프로퍼티를 설정할 때 호출
- 초기 데이터를 가져오기 위한 서비스 호출
- 컴포넌트 디스플레이에 필요한 복잡한 계산을 하려는 경우
OnDestroy
- Angular 컴포넌트를 파괴하려고 할 때
- 리소스를 정리하고 자동으로 발생하는 타이머나 작업을 중지, 메모리를 정리
- 애플리케이션의 다른 컴포넌트나 다른 부분에 컴포넌트가 삭제된다는 사실을 알려줘서 추가로 필요한 작업을 수행할 수도 있음
OnChange
- 컴포넌트에 바인딩된 프로퍼티에서 변경사항이 생겼을 때 발생
- 프로퍼티가 수정될 때마다 Angular는 OnChange 이벤트를 발생, 파라미터로 템플릿에 바인딩된 프로퍼티를 전달
- 현재 값과 이전 값이 전달되어 변경 사항을 확인하고 논리를 추가 할 수 있음
-
프로퍼티의 값이나 작업 흐름의 변경 사항에 대해 유효성 검사를 처리할 때 사용
- 홈페이지 컴포넌트는 OnInit 를 사용하여 보드에 대한 데이터를 가져와 프로퍼티에 바인딩
- @angular/core 모듈에서 OnInit 모듈을 import 해야 함
/app/homepage/homepage.component.ts
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-homepage',
templateUrl: './homepage.component.html',
styleUrls: ['./homepage.component.css']
})
export class HomepageComponent implements OnInit {}
- OnInit 인터페이스는 초기화 이벤트가 발생할 때 Angular에 의해 호출되는 ngOnInit 메서드를 하나만 노출, 여기서 필요한 로직을 구현
- 생성자에서 TrelloService를 참조
- seedData 메서드 호출 데이터 가져오기
import { Component, OnInit } from '@angular/core';
import { Board } from '../model/board';
import { SubTask } from '../model/subtask';
import { Task } from '../model/task';
import { TrelloService } from '../service/trello.service';
@Component({
selector: 'app-homepage',
templateUrl: './homepage.component.html',
styleUrls: ['./homepage.component.css']
})
export class HomepageComponent implements OnInit {
boards: Board[] = Array();
errorMessage: string;
constructor(private _trelloService: TrelloService) { }
ngOnInit() {
this.boards.push(this._trelloService.seedData());
}
}
홈페이지 - 템플릿
- 사용 가능한 보드를 표시하는 블록
- 새 보드를 추가할 수 있게 하는 블록
/app/homepage/homepage.component.html
<div class="boards-wrapper">
<h2>Boards</h2>
<div id="boards">
<a class="board" *ngFor="let board of boards">
<span class="title">{{board?.title}}:
<label style="font-size: smaller">Total Task: {{board?.task.length}}</label>
</span>
</a>
<a href="#" class="board add-board" (click)="addBoard()">
<span class="title">Create a new board...</span>
</a>
</div>
</div>
홈페이지 - 새로운 보드 추가
- 보드를 추가하면 새 보드 객체를 생성, id와 title을 할당, 전역 Boards 객체에 객체를 추가
/app/homepage/homepage.component.ts
import { Component, OnInit } from '@angular/core';
import { Board } from '../model/board';
import { SubTask } from '../model/subtask';
import { Task } from '../model/task';
import { TrelloService } from '../service/trello.service';
@Component({
selector: 'app-homepage',
templateUrl: './homepage.component.html',
styleUrls: ['./homepage.component.css']
})
export class HomepageComponent implements OnInit {
boards: Board[] = Array();
errorMessage: string;
constructor(private _trelloService: TrelloService) { }
ngOnInit() {
this.boards.push(this._trelloService.seedData());
}
public addBoard() {
console.log('Adding new board');
const newBoard: Board = new Board();
newBoard.id = this.boards.length + 1;
newBoard.task = Array();
newBoard.title = 'New Board';
this.boards.push(newBoard);
console.log('new board added');
}
}
트렐로 - 보드 컴포넌트
- Angular 라우팅을 사용하여 해당 보드 페이지로 이동
- 라우팅 시 파라미터를 통해 어떤 보드를 선택했는지 전달
- 보드 페이지에서 각각의 작업과 하위 작업 가져오기, 트렐로 서비스를 공유함으로 구현
- 데이터와 함께 작업과 하위 작업을 호면에 표시, Task 와 SubTask 컴포넌트를 만들고 필요한 데이터 전달
- Task 와 SubTask 사이의 통신은 @Input 와 @Output 프로퍼티로 처리
ng g component board
라우팅
/app/app.module.ts
import { RouterModule, Routes } from '@angular/router';
...
const appRoutes: Routes = [
{ path: 'board/:id', component: BoardComponent, pathMatch: 'full' },
{ path: '', component: HomepageComponent },
];
...
@NgModule({
...
imports:[
...
RouterModule.forRoot(appRoutes)
...
],
...
})
...
/app/homepage/homepage.component.html
...
<a class="board" *ngFor="let board of boards" [routerLink]="['/board', board.id]">
...
/app/app.component.html
<header>
<nav>
<a class="btn">홈페이지</a>
<a class="logo">예제 트렐로 애플리케이션</a>
</nav>
</header>
<div id="content-wrapper">
<router-outlet></router-outlet>
</div>
보드 컴포넌트 라우팅
app/board/board.component.ts
- router 모듈 import
import { Params, ActivatedRoute } from '@angular/router';
- 라우터 서비스 주입
constructor(private _route: ActivatedRoute) { }
- 생성자에 트렐로 서비스 참조
import { TrelloService } from '../service/trello.service';
constructor(private _route: ActivatedRoute, private _trelloService: TrelloService) { }
- OnInit 메서드를 사용하여 보드 템플릿의 데이터 초기화
ngOnInit() {
const boardId = this._route.snapshot.params['id'];
}
보드 컴포넌트 - 데이터 추출
- 트렐로 서비스를 사용하여 다음 코드와 같이 id를 기반으로 보드를 필터링
import { Board } from '../model/board';
...
export class BoardComponent implements OnInit {
board: Board = new Board();
constructor(private _route: ActivatedRoute, private _trelloService: TrelloService) { }
ngOnInit() {
const boardId = this._route.snapshot.params.id;
console.log(boardId);
this.board = this._trelloService.Boards.find(x => x.id == boardId);
}
}
- 트렐로 서비스를 사용하여 모든 레코드를 가져온 다음
- 전달된 id를 기반으로 보드를 가져오도록 필터링
- 홈페이지 컴포넌트와 보드 컴포넌트는 같은 트렐로 서비스를 사용하여 데이터를 공유
보드 컴포넌트 - 자식 컴포넌트에 데이터 전달하기
ng g component task
ng g component subtask
- 보드 컴포넌트는 선택한 id를 기반으로 데이터를 구했고 이제 Task와 SubTask 컴포넌트를 사용하여 데이터를 채우기
- 보드 컴포넌트 -> 작업 컴포넌트 -> 하위작업 컴포넌트 의 구조로 데이터가 전달됨
- 모든 데이터를 보드 컴포넌트에 보관할 수도 있으나 데이터가 커질수록 관리가 어려워짐
- 항상 “단일 책임 원칙”을 따르는 것이 좋음
@Input을 사용해 자식 컴포넌트에 데이터 전달
- @Input 데코레이터 : 하위 컴포넌트가 부모 컴포넌트에게 프로퍼티를 노출할 수 있도록 함
- 모든 프로퍼티가 대상이 됨
- TaskComponent에는 외부에 노출할 task 프로퍼티가 있음
- BoardComponent는 특정 작업을 task 프로퍼티에 지정하여 작업 템플릿을 채우는데 사용
/app/task/task.component.ts
import { Component, OnInit, Input } from '@angular/core';
import { Task } from '../model/task';
import { SubTask } from '../model/subtask';
@Component({
selector: 'app-task',
templateUrl: './task.component.html',
styleUrls: ['./task.component.css']
})
export class TaskComponent implements OnInit {
@Input()
task: Task; // Task 컴포넌트의 task 프로퍼티를 노출 할 것임을 표시
@Input()
subTasks: SubTask[];
constructor() { }
ngOnInit() {
}
}
- 보드 컴포넌트는 프로퍼티 바인딩을 사용하여 작업과 하위 작업 프로퍼티를 설정하는데 필요한 데이터를 전달할 수 있음
- 프로퍼티 바인딩은 대괄호 안에 바인딩 대상을 설정하면 됨
/app/board/board.component.html
<div *ngFor="let task of board.task" class="sortable-task">
<app-task [task]="task" [subTasks]="task.subtask"></app-task>
</div>
- 보드 컴포넌트의 작업 또는 하위 작업 프로퍼티의 값이 변경될때마다 해당 작업 컴포넌트에 반영됨
자식 컴포넌트의 데이터를 부모 데이터로 전달하기
- @Output 데코레이터를 사용하여 자식이 부모에게 데이터를 다시 전달
/app/task/task.component.ts
@Output()
public onAddsubTask: EventEmitter<SubTask>;
- EventEmitter는 제네릭 타입이며 사용할 때 사용할 타입을 지정
- 출력이벤트를 사용하려면 다음과 같이 괄호를 사용하여 상위 컴포넌트에 이벤트를 바인딩
/app/board/board.component.html
(onAddsubTask)="addsubTask($event)"
- 자식 컴포넌트가 부모 컴포넌트에게 onAddsubTask 이벤트를 방출할 수 있음을 알려줌
-
해당 이벤트가 발생하면 addsubTask라는 부모 컴포넌트의 메서드를 호출
- 자식 컴포넌트에서 이벤트를 발생시키려면 작업 컴포넌트의 addsubTask에서 emit 메서드를 호출
/app/task/task.component.ts
this.onAddsubTask.emit(newsubTask);
- Angular의 모든 이벤트 방출기는 emit 함수를 제공
- 자식 컴포넌트에서 부모 컴포넌트로 이벤트를 전달해야 함을 알려줌
- onAddsubTask 이벤트 수신 시 호출되는 코드로 전달된 이벤트 정보를 출력
addsubTask(event){
console.log('이벤트 발생');
console.log(event);
}