6장 트렐로에 기능추가하기
이번 장에서 다룰 내용
- Typescript의 반복자
- 비동기 프로그래밍
- Angular에서 Typescript를 사용하여 비동기 프로그래밍 구현
- Angular의 사용자 정의 파이프
- 테스트 기능을 추가하기 위한 의존성 주입
반복자(iterator)
- 값에 순차적으로 접근하기 위한 수단
- 반복 가능한 모든 객체에는 Symbol.iterator 프로퍼티가 있어야 함
- Symbol.iterator 프로퍼티는 객체의 요소를 반복 처리할 수 있는 메서드를 제공
Javascript 반복자
let stringArray = 'Learning TypeScript';
for(let c of stringArray){
console.log(c);
}
- 문자열의 각 문자를 반복하여 출력
- 문자열에 Symbol.iterator 프로퍼티가 있고
- for … of가 반복 가능한 객체에 대해 루프를 돌며 출력
- Symbol.iterator 프로퍼티를 명시적으로 사용하여 다시 작성 가능
let stringArray = 'Learning TypeScript';
let iter = stringArray[Symbol.iterator]();
console.log(iter.next().value);
TypeScript 반복자
- iteratable 프로퍼티를 가진 데이터 타입을 제공
- Array 인터페이스에는 IterableIterator 인터페이스 타입의 Symbol.iterator 프로퍼티가 있음
- IterableIterator 인터페이스는 iterator 인터페이스를 구현하여 next(필수), return, throw의 3개의 메서드를 가짐
- next, return, throw 메서드는 IteratorResult 타입이면 done, value 두가지 프로퍼티를 가지고 있음
TypeScript 사용자정의 반복자
- 1부터 시작, 100이 되면 다시 0으로 돌아가는 카운터
class customCounter implements Iterator<number>{
private calculatedVal:number = 0;
next(value?: any): IteratorResult<number>{
this.calculatedVal = this.calculatedVal > 99 ? 0 : ++this.calculatedVal;
return{
done: false,
value: this.calculatedVal
}
}
}
let c = new customCounter();
for(let i = 0; i < 101; i++){
console.log(c.next().value);
}
- Iterator를 구현하는 customCounter 클래스
- 이 클래스는 calculatedVal 이라는 private 프로퍼티가 하나 있음
- 이 프로퍼티는 0으로 초기화 되었고 반복자에서 반환하는 값
- Iterator 인터페이스는 next, return, throw 3개의 메서드가 있지만 코드에서는 필수인 next 만 구현
- next 메서드는 두개의 프로퍼티가 있는 IteratorResult 타입의 인터페이스 객체를 반환
- next 메서드는 현재 값이 99보다 크면 0으로 초기화 하고 그렇지 않다면 1을 더해 그 값을 반환
- customCounter 객체를 만들고 메서드는 101번 호출하여 값 출력
TypeScript for…of와 for…in 루프
- for…of는 Javascript에서 지원
- TypeScript는 for…in이라는 다른 함수도 제공
- 두 함수의 차이점은 어떤 값으로 반환하는지 여부
- for…of는 각 요소의 값을 반환
- for…in은 각 요소의 인덱스를 반환
let sampleArry = ['TypeScript','Angular','Node'];
for(let val of sampleArry){
console.log(val);
}
for(let val in sampleArry){
console.log(val);
}
- 두 함수는 반환되는 값은 다르지만 Iterator 인터페이스를 사용하여 객체를 순환
TypeScript를 이용한 비동기 프로그래밍
- 비동기 프로그래밍은 이해하기 까다롭지만 반응형 애플리케이션을 만들 때 필수적인 개념
- Javascript는 단일 스레드 애플리케이션 이므로 해당 스레드에서 모든 작업을 수행하면 느려짐
콜백 함수
- 비동기 처리가 완료된 후에 호출되는 예약함수
- 파라미터 형태로 다른 함수에 전달되고 비동기 처리가 완료될 때 호출
setTimeout(callback,2000);
function callback(){
document.writeln('콜백 함수 호출');
}
TypeScript 콜백
function doWork(clientName:string,callback:(boards:Board[],status:string)=>void):void{
let response:Board[];
// id를 기준으로 웹 서비스를 호출하고 보드의 목록을 구한다.
// 그 다음 필요한 파라미터와 함께 지정한 콜백 함수를 호출한다.
callback(response,'Success')
}
- doWork 함수는 두개의 파라미터를 입력으로 사용
- 하나는 보드를 가져올 클라이언트의 이름
- 다른 하나는 콜백함수
- 콜백함수에는 타입도 정의되어 있으며 두개의 파라미터, 보드배열과 웹서비스 호출 상태를 받음
- 함수가 호출되면 내부적으로 웹 서비스를 호출하고 웹 서비스 응답 후에 콜백함수가 실행
function callback(boards:Board[],status:string){
if(status != 'Success'){
console.log(status)
}else{
boards.forEach(x=>console.log(x.title));
}
}
- 상태를 확인하고 성공이 아니면 상태를 출력
- 성공이라면 모든 내용을 콘솔에 출력
- 이런 방식은 애플리케이션이 응답을 처리하는 동안에도 반응형으로 동작
콜백 함수 정리
- 인터페이스를 사용하면 좀 더 간결하고 재사용 가능하게 사용 가능
interface ICallBack{
(boards:Board[],status:string):void;
}
function doWork(clientName:string,callback:ICallBack):void{
let response:Board[];
// id를 기준으로 웹 서비스를 호출하고 보드의 목록을 구한다.
// 그 다음 필요한 파라미터와 함께 지정한 콜백 함수를 호출한다.
callback(response,'Success')
}
프로미스(Promise)
- 콜백은 코드가 길고 절차가 복잡
- 비동기 안에서 비동기 작업을 여러번 호출하려는 경우 콜백 지옥에 빠질 수 있음
- Typescript tsconfig.json 에서 target 옵션을 ES2015로 해야 사용 가능
- 프로미스는 비동기 호출에 의해 반환되는 개체로 호출에 의해 최종적으로 반환되는 값을 나타냄
- 반환되는 값을 임시로 메꿔주는 공간, 실제 값이 반환되면 실제 값으로 채워 짐
- 콜백 함수와의 차이점은 프로미스는 콜백을 전달하지 않고 API를 사용하여 콜백을 프로미스에 첨부한다는 점
- 콜백보다 이해하기 쉽고 간결하게 코드 작성이 가능
프로미스 API
- 프로미스의 서명은 비동기 작업을 수행하는 함수를 파라미터로 받음
- 이 함수는 resolve(성공), reject(실패)라는 두개의 파라미터를 사용
let p:Promise<Board[]> = new Promise((resolve, reject) => {
// ...
// 임의의 비동기 작업 수행
// ...
if(success){
resolve(result);
}else{
reject(errorMsg);
}
});
- 새로운 객체를 만들고 화살표 함수로 전달
- 화살표 함수는 두개의 파라미터를 사용, 호출에 성공하면 resolve, 실패하면 reject를 호출
- 변수 p는 제네릭 서명이 있는 Promise 타입, 보드 배열 타입의 프로미스를 할당 -> 프로미스가 성공하면 보드 배열 타입의 결과를 반환
응답 처리
- 프로미스에 의해 반환된 응답을 처리
- then, catch 두 가지 기능을 제공
- then 함수 -> 프로미스의 성공적인 완료를 위해 호출
- 프로미스가 resolve될 때 호출하는 또 다른 함수를 지정해야 함
p.then(boards=>boards.forEach(x=>console.log(x.title)));
- 보드 배열을 가져온 다음 각 요소를 반복하여 보드의 제목을 출력
- 프로미스가 성공적으로 반환되면 이 메서드가 호출 됨
p.chtch(msg=>console.log(msg));
- reject 함수가 오류 메시지를 전달했기 때문에 chtch에서 오류 메시지만 출력
- 여러 프로미스를 연결하는 것도 가능
promise 연결(chaining)
- 여러 비동기 작업을 차례로 호출하는 경우 여러 호출을 관리할 수 있는 체인을 제공
- then 함수는 초기 프로미스 객체와 다른 또 다른 새로운 프로미스를 반환
- 이전 프로미스 작업과 해당 프로미스의 콜백이 완료되었음을 나타냄
let p:Promise<Board[]> = new Promise((resolve, reject) => {
// ...
// 임의의 비동기 작업 수행
// ...
if(success){
resolve(result);
}else{
reject(errorMsg);
}
});
let newPromise = p.then(boards=>boards.forEach(x=>console.log(x.title)));
newPromise.then(result => console.log(result));
- p가 resolve될 때 then 함수에서 또 다른 doSomeMoreAsyncWork 비동기 작업을 호출
- then 함수는 새로운 프로미스를 반환
- 이를 통해 여러 개의 비동기 작업을 연결하고 이전 작업 완료 후 다름 작업이 실행될 수 있도록 함
- catch 함수를 통해 작업 실패시 실행될 작업을 연결할 수도 있음
let newPromise = p.catch(boards=>doSomeMoreAsyncWork(boards));
newPromise.then(result => console.log(result));
Async-await
- Typescript Async-await는 프로미스를 기반으로 구축되었고 보다 직관적으로 비동기 코드를 작성할 수 있게 해줌
- 개발자가 동기 방식의 코드를 작성하는 것과 비슷하게 작성하여 가독성이 높고 깨끗한 코드를 제공
1
2
3
4
5
6
7
8
9
10
11
12
async function callAsyncFunction(id:number){
console.log('비동기 작업 호출 전');
await doAsyncWork(id);
console.log('비동기 작업 완료');
return '성공';
}
function doAsyncWork(id:number){
// 웹 서비스 호출
}
console.log('비동기 작업 호출 전');
let p = callAsyncFunction(1).then(x=>console.log(x));
console.log('비동기 작업 호출 후');
- async 함수가 프로미스 위에서 실행되기 때문에 결국은 프로미스를 반환
- 1행에서 async 키워드가 접두어로 사용되는 callAsyncFunction 함수는 컴파일러에게 함수 내부에 병렬 수행할 부분임을 알려줌
- 병렬로 수행되길 원하는 메서드는 doAsyncWork와 같이 await 키워드를 접두어로 사용
- callAsyncFunction 메서드는 문자열을 반환하므로 반환 타입은 컴파일러에 의해 Promise
으로 반환, 비동기 작업 수행 후 프로미스를 반환한다는 뜻 - 만약 callAsyncFunction 함수에서 return이 없었다면 Promise는 void 타입이었을 것
- callAsyncFunction 함수가 프로미스를 반환하기 때문에 프로미스 API의 then을 사용하여 함수의 반환값을 가져올 수 있음
Async-await 에러 처리
- 프로미스와 동일하게 catch 메서드를 사용해 에러 처리
let p = callAsyncFunction(1).then(x=>console.log(x)).catch(errorMsg=>console.log(errorMsg));
예제 트렐로 애플리케이션에 기능 추가
- http 데이터 : http 호출을 통해 데이터를 가져옴, 프로미스를 사용하여 구현
- 기능 구현 : 새 작업 추가, 새 하위 작업 추가, 새 보드 추가
- 데이터 포맷팅 : Angular는 특정 데이터를 실행 중에 포맷팅 할 수 있는 파이프 메카니즘 제공
예제 트렐로에서 프로미스 사용하기
보드 JSON
- /src/api/board 경로에 다음과 같이 json 파일 생성
/src/api/boardboards.json
...
[
{
"id":1,
"title":"Learn TypeScript",
"task":[
{
"id":"1",
"title":"Basics",
"taskheaderId":"1",
"subtask":[
{
"id":"1",
"title":"Types"
},
{
"id":"2",
"title":"Classes and Interfaces"
}
]
},
{
"id":"2",
"title":"Advanced",
"taskheaderId":"2",
"subtask":[
{
"id":"1",
"title":"Generics"
},
{
"id":"2",
"title":"Modules"
}
]
}
]
},
....
]
- http 호출을 사용하여 이 json을 가져온 다음 Board 객체를 홈페이지 컴포넌트에 전달
프로미스 구현
- http 호출과 미로미스 객체에서 응답을 가져오기 위해 의존성을 추가해야 함
HTTP 의존성 추가
/src/app/app.module.ts
import { HttpClientModule } from '@angular/common/http';
....
@NgModule({
...
imports: [
..
HttpClientModule,
...
],
...
})
/src/app/service/trello.service.ts
import { HttpClient } from '@angular/common/http';
...
@Injectable()
export class TrelloService {
...
constructor(private _http: HttpClient) { }
...
}
HTTP 호출 로직
- _http 객체를 사용하여 웹 서비스 호출
/src/app/service/trello.service.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
...
@Injectable()
export class TrelloService {
...
getBoardsWithPromises(): Promise<Board[]> {
if (this.Boards == undefined) {
return this._http.get(this._boardUrl).toPromise()
.then((response: Response) => {
this.Boards = response as unknown as Board[];
return response as unknown as Board[];
});
} else {
return Promise.resolve(this.Boards);
}
}
}
- 6행 : 함수의 이름과 반환 타입, Board 타입의 프로미스를 반환
- 7행 : 보드를 이미 가져온 적이 있는지 확인, 가져온 적이 없다면 http 호출, 가져온 적이 있다면 14행처럼 이미 저장한 보드 배열 객체를 사용하여 프로미스를 반환
- http 객체를 사용하여 HTTP GET 호출
- rxjs의 toPromise를 사용하여 응답을 프로미스로 변환
- Promise API의 then 메서드를 사용해 API 결과를 확인
- 응답을 확인하고 지역 변수인 보드 배열을 응답으로 설정
홈페이지 컴포넌트에서 호출하기
- 이전 장에서는 homepage.component.ts 에서 trello.service.ts의 seedData 메서드를 호출해서 데이터를 가져왔음
- 이것을 프로미스를 사용하는 새로운 메서드로 대체
/src/app/homepage/homepage.component.ts
...
export class HomepageComponent implements OnInit {
...
ngOnInit() {
// this.boards.push(this._trelloService.seedData()); 코드 삭제
this._trelloService.getBoardsWithPromises()
.then(boards => this.boards = boards,
error => this.errorMessage = error as any);
}
...
}
- getBoardsWithPromises 메서드를 호출 한 다음 then 함수를 사용해 응답을 받음
- then 함수에서 성공과 실패를 함께 처리, 첫 번째 파라미터는 성공, 두 번째 파라미터는 실패
기능 구현
- 작업을 추가하는 기능
- 특정 작업에서 새 하위 작업을 추가하는 기능
- 보드, 작업, 하위 작업의 제목을 변경하는 기능
- 새 작업이 추가되면 홈페이지에 변경 사항을 반영하는 기능
구현 - 새로운 작업 추가
- 사용자가 새 작업 추가 섹션을 클릭하면 입력 상자에 커서 포커스
- 사용자가 입력을 완료하고 엔터키를 누르면 보드에 새 작업 추가
- 엔터키를 누르지 않고 포커스가 아웃되더라도 새 작업 추가
보드 컴포넌트
/src/app/board/board.component.html
...
<section *ngIf="board" id="main">
...
<div class="add-task" (click)="enableAddtask()">
<input (keyup)="addtaskOnEnter($event)" (blur)="addtaskOnBlur()" [(ngModel)]="addtaskText" placeholder="작업추가" />
</div>
</section>
- 작업 추가 입력 상자를 캡슐화 하는 div
- enableAddtask, addtaskOnEnter, addtaskOnBlur 세가지 함수
- addtaskText 프로퍼티 - 사용자가 입력한 텍스트
/src/app/board/board.component.ts
enableAddtask 함수
- 새로운 작업을 추가하려는 입력 상자를 찾아 포커스를 설정
...
export class BoardComponent implements OnInit {
...
enableAddtask() {
const input = this.el.nativeElement
.getElementsByClassName('add-task')[0]
.getElementsByTagName('input')[0];
setTimeout(() => input.focus(), 0);
}
...
}
addtaskOnEnter 함수
- 사용자가 입력 상자에 입력을 완료하고 엔터키를 누를 때 호출
- 보드에 새로운 작업을 추가 하는 것이 목적
...
export class BoardComponent implements OnInit {
...
addtaskOnEnter(event: KeyboardEvent) {
if (event.keyCode === 13) {
if (this.addtaskText && this.addtaskText.trim() !== '') {
this.addtask();
} else {
this.clearAddtask();
}
} else if (event.keyCode === 27) {
this.clearAddtask();
}
}
...
}
- 내부적으로 몇 가지 다른 함수를 호출(addtask, clearAddtask)
- 엔터키(키코드 13)을 누르면 입력 값이 있을 경우 addtask를 호출
- ESC(키코드 27)을 누르면 작업 취소라고 생각하고 작업을 추가 하지 않음
addtask 함수
- 보드 배열에 작업을 추가하는 메서드, 해당 내용은 UI에도 반영
...
export class BoardComponent implements OnInit {
...
addtask() {
const newID = this.board.task.length + 1;
const newtask = {
title: this.addtaskText,
id: newID
} as Task;
this.board.task.push(newtask);
this.addtaskText = '';
}
...
}
- 새로운 작업에 대한 작업 번호를 1씩 증가시켜 새로운 id에 할당
- 새로운 Task 객체를 만들고 id와 title을 설정
- 새로운 Task 객체는 Board 컴포넌트에 있는 board 객체의 task에 추가
- @Input, @Output으로 양방향 바인딩 연결을 해두었으므로 추가된 자동으로 UI에 업데이트
clearAddtask 함수
- addtaskText 프로퍼티의 값을 지워서 UI 입력 상자의 placeholder의 텍스트를 다시 보이게 함
...
export class BoardComponent implements OnInit {
...
clearAddtask() {
this.addtaskText = '';
}
...
}
addtaskOnBlur 함수
- 입력 상자에 텍스트가 있는지 확인, 있으면 addtask 메서드 호출, 없으면 clearAddtask 호출
...
export class BoardComponent implements OnInit {
...
addtaskOnBlur() {
if (this.addtaskText && this.addtaskText.trim() !== '') {
this.addtask();
}
this.clearAddtask();
}
}
구현 - 새로운 하위 작업 추가하기
- 새 작업을 추가하는 것과 유사하지만 작업하는 데이터 객체에 차이가 있음
- 새 하위 작업을 추가하려면 먼저 부모 작업이 무엇인지부터 식별
- 그 후 추가한 변경 사항을 부모 작업에 반영
작업 템플릿
/src/app/task/task.component.html
- 작업 템플릿의 일부로 하위 작업을 추가하기 위한 UI 제공
<div class="task" [attr.task-id]="task.id">
...
<div class="add-subTask" (click)="enableAddsubTask()">
<input (keyup)="addsubTaskOnEnter($event)" (blur)="addsubTaskOnBlur()" [(ngModel)]="addsubTaskText" placeholder="Add a new SubTask" />
</div>
</div>
- 새로운 작업을 추가할 때와 동일한 이벤트를 처리
- 하위 작업을 작업 목록에 추가하는 것
/src/app/task/task.component.ts
...
export class TaskComponent implements OnInit {
...
enableAddsubTask() {
const input = this.el.nativeElement
.getElementsByClassName('add-subTask')[0]
.getElementsByTagName('input')[0];
setTimeout(() => input.focus(), 0);
}
addsubTaskOnEnter(event: KeyboardEvent) {
if (event.keyCode === 13) {
if (this.addsubTaskText && this.addsubTaskText.trim() !== '') {
this.addsubTask();
this.addsubTaskText = '';
} else {
this.clearAddsubTask();
}
} else if (event.keyCode === 27) {
this.clearAddsubTask();
}
}
addsubTaskOnBlur() {
if (this.addsubTaskText && this.addsubTaskText.trim() !== '') {
this.addsubTask();
}
this.clearAddsubTask();
}
addsubTask() {
this.subTasks = this.subTasks || [];
const newsubTask = { // 새로운 하위 작업 객체를 생성
title: this.addsubTaskText // 객체의 프로퍼티에 title을 할당
} as SubTask;
let selectedtask: Task;
for (const v of this.board.task) { // 보드에 있는 모든 작업을 반복
if (v.id == this.task.id) { // 선택한 작업이 무엇인지 식별
selectedtask = v;
break;
}
}
if (selectedtask.subtask == undefined) { // 선택한 작업에 하위 작업 배열이 있는지 확인
selectedtask.subtask = new Array(); // 하위 작업 배열이 없다면 비어있는 배열로 초기화
}
selectedtask.subtask.push(newsubTask); // 하위 작업을 선택된 작업에 추가
this.subTasks = selectedtask.subtask;
this.onAddsubTask.emit(newsubTask); // 부모 컴포넌트에 변경 사항을 알리고 새 하위 작업 컴포넌트를 전달하기 위해 이벤트를 발생
}
}
보드, 작업, 하위 작업의 제목 변경하기
- 이벤트를 처리하고 각 프로퍼티를 업데이트 하는 로직은 세 컴포넌트 모두 비슷
- 자세한 코드는 Github에서 확인
보드 제목 변경하기
- 보드 제목을 클릭하면 제목을 바꿀수 있는 기능 구현
/src/app/board/board.component.html
<div *ngIf="board" class="board-title">
<span [style.display]="editingTitle ? 'none' : ''" (click)="editTitle()">{{ board?.title }}</span>
<input [style.display]="editingTitle ? '' : 'none' " (keyup)="blurOnEnter($event)" (blur)="updateBoard()" [(ngModel)]="board.title" />
</div>
...
- 사용자가 작업의 제목(span)을 클릭하면 editTitle을 호출
- keyup, blur 이벤트로 보드 제목을 내부적으로 업데이트 하도록 함
/src/app/board/board.component.ts
...
export class BoardComponent implements OnInit {
...
updateBoard() {
this.editingTitle = false;
document.title = this.board.title + ' | 일반 작업 관리자';
this._trelloService.Boards
.find(x => x.id == this.board.id) // 선택한 보드를 식별
.title = this.board.title; // 보드 제목을 업데이트
}
...
}
/src/app/board/board.component.html
[(ngModel)]="board.title"
...
- board 객체의 title 프로퍼티를 보드 컴포넌트에 직접 바인딩되어 있음
홈페이지 변경사항 반영하기
- Homepage 컴포넌트는 Angular 바인딩을 사용하여 보드 이름과 함께 작업의 수를 보여줌
- Angular 바인딩은 UI에 바인딩된 모든 프로퍼티를 추적하며 변경이 발생되면 새 값을 전파(board.component.ts 의 updateBoard 메소드)
Angular의 데이터 포맷팅
- Angular는 파이프라는 기능을 제공하여 사용자 인터페이스에 따라 데이터를 포맷팅 가능
내장 파이프
- 내장 파이프를 사용하면 별도의 import 없이 사용
- 파이프 기호 뒤에 원하는 함수명 입력
- task.title 프로퍼티에 uppercase 파이프 함수를 사용
/src/app/task/task.component.html
...
<h4 [style.display]="editingtask ?'none' : '' " (click)="edittask()">{{task.title | uppercase}}</h4>
...
- 다음은 통화 파이프 예시
{{book.price | currency:'USD':true:'1.1-2'}}
사용자 정의 파이프
- Angular는 사용자 정의 파이프를 만들 수 있는 인터페이스를 제공
- 새로운 서비스, 컴포넌트를 만드는 것과 매우 유사
트렐로용 정렬 파이프
/src/app/shared/customsort_pipe.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import {Pipe, PipeTransform} from '@angular/core';
import { Task } from '../model/task';
@Pipe({
name: 'customSort'
})
export class CustomSort implements PipeTransform {
transform(value: Task[], sort: boolean): Task[] {
if (sort) {
return value.sort(this.compare);
} else {
return value;
}
}
private compare(a: any, b: any) {
if (a.title < b.title) {
return -1;
}
if (a.title > b.title) {
return 1;
}
return 0;
}
}
- 1행 : 파이프 모듈을 import
- 3행 : @Pipe 데코레이터를 사용해 클래스 정의
- 6행 : PipeTransform 인터페이스 구현
- 7행 : PipeTransform 인터페이스는 하나의 메서드 transform만 노출
- 첫번째 파라미터는 파이프 함수에 전달되는 값
- 두번째 파라미터는 정렬을 할지 여부
- 반환 타입은 내용을 재정렬하기만 했으므로 작업 배열 타입 그대로
- 파이프 함수 내부의 로직은 객체 배열을 문자열 기준으로 정렬하는 표준적인 코드
파이프 의존성 추가
/src/app/app.module.ts
...
import { CustomSort } from './shared/customsort_pipe';
...
@NgModule({
declarations: [
...
CustomSort
],
...
})
...
사용자 정의 파이프 사용하기
- 내장 파이프를 사용하는 것과 동일
/src/app/board/board.component.html
...
<section *ngIf="board" id="main">
<div *ngFor="let task of board.task | customSort:true" class="sortable-task">
...
</div>
...
</section>
Angular 의존성 주입 이해하기
- 의존성 주입은 Angular의 중요한 개념 중 하나
- Angular 애플리케이션을 쉽게 테스트할 수 있는 주요 이유
- 크렐로 애플리케이션은 다음과 같이 클래스 내에 의존성을 주입
constructor(private _route: ActivatedRoute, private _trelloService: TrelloService) { } // board.component.ts
constructor(private _http: HttpClient) { } // trello.service.ts
의존성 관리
- Angular는 특정 클래스의 의존성을 판별하면 객체 생성 시 해당 의존성을 클래스에 주입
- trello의 경우
- Angular는 HttpClient 모듈이 어디 있는지 확인하고 app.module 파일에서 찾음
- 그런 다음 새로운 HTTP 인스턴스를 만들거나 기존 HTTP 인스턴스를 트렐로 서비스의 생성자에 전달
- 개발자는 클래스의 의존성을 확인하고 생성하는 책임에서 벗어날 수 있음
- 의존성을 고려하지 않음으로써 보다 쉽게 테스트할 수 있는 코드를 작성