본문 바로가기

1. Component와 Directive (1)

0. Decorator

데코레이터란, 데코레이터 바로 하단의 클래스를 Angular가 어떤 타입으로 어떤 메타데이터를 가지고 사용할 것인가를 정의하는 장식자입니다. 데코레이터는 '@' 접두어로 표기됩니다.

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html'
})
export class AppComponent {}

1. Component

컴포넌트(@Component()) : HTML 템플릿을 컴포넌트 데코레이터의 template으로 지정하면 해당 템플릿을 뷰로 지정하여 Angular 프레임워크가 제공하는 디렉티브와 데이터를 바인딩할 수 있는 마크업을 작성할 수 있습니다.

 

 

 

2. Directive

디렉티브(@Directive()) : 컴포넌트보다 더 큰 의미로, DOM을 관리하기 위한 지시자로서 앱 전역에서 사용가능한 로직을 컴포넌트에서 분리하여 구현한 것입니다. 디렉티브를 사용하면 코드의 재사용성을 향상시키고, 컴포넌트의 복잡도를 낮출 수 있어 가독성이 좋아지는 장점이 있습니다.

2.1 디렉티브의 종류

디렉티브는 용도에 따라 크게 세 가지로 구분됩니다.

  • 컴포넌트 디렉티브: 컴포넌트의 템플릿을 지정하기 위한 디렉티브로 @Component 데코레이터가 컴포넌트 디렉티브의 서브셋입니다.  @Component 데코레이터의 selector 속성에 템플릿을 정의하여 컴포넌트 인스턴스를 생성하는데 다른 디렉티브와 달리 오직 하나의 컴포넌트 인스턴스만이 템플릿에 생성됩니다.
  • 어트리뷰트 디렉티브: DOM 요소의 attributes처럼 정의하여 호스트 요소의 스타일과 동작을 제어할 수 있습니다. 앵귤러에 기본적으로 내장된 ngClass, ngStyle 속성이 이에 해당하며 엘리먼트의 속성처럼 작성하여 존재하는 엘리먼트의 동작이나 속성을 동적으로 변경할 수 있습니다.
  • 구조 디렉티브: DOM 요소를 반복적으로 생성하거나 조건문을 통한 DOM의 레이아웃 변화 등 DOM의 구조를 변경하는 디렉티브입니다. ngFor, ngIf가 이에 해당합니다.

아래 예시는 사용자 지정 어트리뷰트 디렉티브입니다.

 

3. 데이터 바인딩

위에서 작성한 컴포넌트와 템플릿 HTML은 어떻게 유기적으로 동작할까요?

앵귤러에서는 컴포넌트와 템플릿 간의 데이터 바인딩을 손쉽게 할 수 있습니다. 보통 양방향 바인딩이라고 불리는데 이는 데이터는 컴포넌트에서 템플릿으로, 템플릿에서 발생하는 이벤트는 컴포넌트로 바인딩되기 때문에 양방향 바인딩이라 불립니다.

 

먼저 데이터 바인딩은 프로퍼티 바인딩이라고 불리는데 엘리먼트의 어트리뷰트를 정의하는 것처럼 사용합니다.

 

4장에서 더 자세히 설명하겠지만 프로퍼티 바인딩으로 전달받은 데이터는 @Input 데코레이터로 받을 수 있습니다.

 

위에서 엘리먼트 어트리뷰트에 대괄호([])가 없으면 문자열 바인딩이 되어 "data"라는 문자열이 전달되므로 주의합니다.

 

이벤트 바인딩은 템플릿에서 발생한 유저 인터렉션을 컴포넌트의 메소드와 연결하여 사용합니다.

 

4. 컴포넌트 간 데이터 통신

여러 개의 컴포넌트들이 서로 데이터를 주고받는 방법은 총 7가지가 있습니다.

 

많게 느껴지실 수 있지만 상황에 따라 가장 유용한(혹은 이렇게 할 수 밖에 없는) 방법을 선택합니다. 이들 중에서는 사용을 지양하는 방법 또한 있습니다. 그러나 배우는 입장인 만큼 모두 공부하였습니다.

 

4.1 @Input, @Output 데코레이터

위의 프로퍼티 바인딩으로 전달한 데이터는 자식 컴포넌트에서 @Input 데코레이터로 받을 수 있습니다.

 

반면, @Output 데코레이터는 자식의 데이터를 부모에 전달하는데 사용합니다.

 

@Output 데코레이터는 주로 이벤트를 바인딩하여 사용하는데 자식 컴포넌트에서 발생한 이벤트를 부모 컴포넌트가 이벤트 바인딩해서 데이터를 전달받게 됩니다.

 

4.2 inputs, outputs declarations

위의 @Input, @Output 데코레이터는 클래스 내의 속성으로 받았지만, declarations로 inputs과 outputs을 정의하여 디렉티브의 metadata로 전달하는 방법입니다.

 

그러나 공식 문서에서는 이 방법을 권장하지 않는데 그 이유는 다음과 같습니다.

  • 데코레이터로 정의하는 것이 속성명 수정이 간편합니다.
  • 디렉티브의 metadata 정보는 간결하게 작성할 것을 권장합니다.
  • 데코레이터로 한 줄씩 정의하는 것이 의미 파악이 더욱 쉽습니다.

4.3 setter를 사용한 Interception

getter, setter 함수를 통해 입력받은 데이터를 intercept하여 가공하거나 로직을 수행할 수 있습니다.

 

4.4 OnChanges LifeCycle Hook

앵귤러에는 리액트처럼 컴포넌트가 일정한 라이프사이클을 따릅니다.

 

리액트와 다른 점은 이 라이프사이클 함수들이 인터페이스에 저장되어 있어 해당 인터페이스를 implements해야 사용할 수 있습니다.

 

라이프사이클에 대한 자세한 내용은 여기를 참고하시면 더 자세히 알 수 있습니다.

 

이 라이프사이클 함수 중 컴포넌트가 최초로 생성될 때, 그리고 그 후 입력 프로퍼티가 변할 때마다 실행되는 ngOnChanges 함수에서 입력 프로퍼티의 변화에 따른 로직을 수행할 수 있습니다.

 

ngOnChanges는 OnChanges 인터페이스를 implement해야 사용할 수 있습니다.

 

setter함수로 가공해야 할 입력 데이터가 많거나 데이터가 서로 의존적인 경우에 ngOnChanges를 사용하면 한번에 처리할 수 있습니다.

 

4.5 Template Reference Variable(#, "ref-")

자식 컴포넌트에서 템플릿 변수를 선언하면 해당 변수에는 자식 컴포넌트의 인스턴스를 참조하게 됩니다.

 

(참고) 만약 이 템플릿 변수를 input, a와 같이 일반 HTML 엘리먼트에 선언하면 해당 HTML Node를 가리키게 됩니다.

 

이 점을 활용하여 부모 컴포넌트의 템플릿에서 자식 컴포넌트의 메소드와 속성을 활용할 수 있습니다.

 

 

템플릿 변수는 이 용도말고도 조건문에 활용하여 Observable이 완료되지 않았을 때 로딩 ui 엘리먼트를 띄우는 등 다양하게 활용되니 잘 알아두면 좋습니다.

4.6 @ViewChild, @ViewChildren 데코레이터

위의 템플릿 변수로는 부모 컴포넌트 템플릿에서만 자식 컴포넌트를 참조할 수 있는 한계가 있지만 @ViewChild 데코레이터를 활용하면 부모 컴포넌트의 클래스에서 접근이 가능합니다.

 

첫번째 인자에 컴포넌트 클래스를 정의하면 그 클래스와 매치되는 DOM 요소를 찾고, 요소가 변경되면 해당 속성을 업데이트합니다.

 

위의 카운터 예제를 @ViewChild로 변경하면 아래와 같습니다.

 

 

4.7 서비스 프로바이딩

지금까지 부모-자식 관계에 놓인 컴포넌트 간 데이터를 주고받을 수 있는 방법을 알아보았습니다.

 

부모-자식 관계에 놓이지 않는 컴포넌트 간 데이터 통신은 앱에서 공통으로 사용되는 데이터와 기능을 정의한 서비스를 활용하여 구현합니다.

 

공식 문서에서는 컴포넌트에서는 뷰와 사용자 인터렉션과 관련된 로직만을 담고, 그 외의 서버 통신, 로깅 등의 기능은 서비스에서 구현하여 코드의 복잡도를 줄이고 재사용성을 향상시킬 것을 권장합니다.

이상적인 경우를 따지면 컴포넌트에는 해당 뷰에서 일어나는 사용자의 행동에 관련된 로직만 두는 것이 좋습니다. 컴포넌트에는 화면에 사용되는 프로퍼티나 데이터 바인딩에 사용하는 메소드만 정의하는 것이 좋으며, 컴포넌트는 템플릿이 렌더링된 뷰와 모델 을 정의하는 애플리케이션 로직을 중개하는 역할만 하는 것이 좋습니다.

서비스에 대한 자세한 사용법은 4장의 서비스에서 정리하기로 하고 여기서는 서비스에 정의한 데이터는 의존성 주입을 통해 어느 컴포넌트에서든 활용할 수 있다는 것만을 기억하기로 합니다.

5. [실습] 기본 컴포넌트 작성

이제 위에서 정리한 내용을 바탕으로 실습 프로젝트의 첫 구현을 시작합니다. 공식문서에서도 나와있듯이 angular-cli를 활용하여 첫 프로젝트를 생성할 수 있습니다. 완전한 첫 시작 외부 튜토리얼에서 많이들 소개하고 있으니 스킵합니다.

 

프로젝트 구조는 다음과 같습니다. (사내 프로젝트 구조를 참고하였습니다.)

아직 블로그에 정리하지 못한 내용도 간간히 보이지만 현재 컴포넌트에서 배운 것만을 적용할 수 있는 디렉토리는 다음입니다.

  • app.component.ts : 루트 컴포넌트
  • components/ : 2장에서 배울 lazy load되는 모듈을 감싸는 래퍼 컴포넌트를 정의
  • models/ : 타입스크립트 인터페이스 정의
[참고] 3장에서 자세히 다루겠지만 app.module.ts는 앵귤러의 ngModule을 정의한 파일로, 기능 단위 혹은 작업 흐름에 따른 컴포넌트들을 묶어 정의한 것입니다. 앵귤러는 컴포넌트를 모듈 단위로 구분지음으로써 코드의 재사용성을 높이는 것 뿐만 아니라 필요한 시점에만 해당 모듈에 정의된 컴포넌트를 렌더링함으로써 지연 로딩또한 가능하게 하였습니다.

우선, 모듈에 관한 내용은 잠시 미루고 간단한 컴포넌트를 작성하겠습니다.

 

사내에서는 접두어로 view-, pw-, ct- 등을 사용하고 있는데 의미는 다음과 같습니다.

  • pw- : page wrapper의 줄임말로, 레이아웃을 정의하는 컴포넌트
  • ct- : 리액트의 Container 컴포넌트와 같이 Store dispatch 등 로직을 수행할 수 있는 컴포넌트
  • view- : 리액트의 Presenter 컴포넌트처럼 데이터를 받아 출력하는 컴포넌트

 

일반적으로 웹 화면에는 로고와 네비게이션 바를 포함하는 헤더와 내용이 들어가는 컨텐츠, 회사 정보나 약관이 위치한 푸터로 이루어져 있으니 components/에 view-header/, view-wrapper/, view-footer/ 디렉토리를 생성하고 각각 component.ts, component.html, component.scss를 생성합니다.

[참고] 원래는 라우터 모듈을 활용하여 wrapper 컴포넌트에서 라우팅을 할 예정이라 이름을 view-contents가 아닌 view-wrapper로 지었습니다. 지금은 라우팅을 적용하지 않을 생각이라 view-wrapper/에 컴포넌트를 작성 후 모듈과 라우팅에서 이를 리팩토링하겠습니다.

다음은 view-header/ 예시입니다.

view-header와 view-footer를 생성한 후 이를 아래 코드처럼 view-wrapper 템플릿에 추가하면 다음과 같은 화면이 뜰것입니다.

화면 구조

이제 가운데 view-wrapper/에 들어갈 상품 리스트를 작성합니다. 원래라면 서버에서 상품 정보를 가져오겠지만 지금은 테스트 중이니 mock 데이터를 설정 후 이를 가져오겠습니다.

 

먼저 models/에 Product 인터페이스를 정의하여 정적 타입으로 지정합니다.

인터페이스는 주로 서버 api에서 오는 데이터 구조와 동일할 것입니다.

 

위에서 배운 기본 구조 디렉티브인 *ngFor과 *ngIf를 사용하여 메뉴를 렌더링할 수 있습니다.

아까 그린 페이지 구조 디자인이 구려서 몇몇 스타일을 추가하고 헤더와 푸터를 수정하면 다음과 같이 화면이 완성됩니다.

메뉴 화면

여기까지 첫번째 컴포넌트 설명이었습니다 :)

두번째 소개에서는 컴포넌트의 변화감지와 이에 관련한 최적화 및 Content Projection에 대해 공부하겠습니다 :)

'Today I Learn > Angular 기초' 카테고리의 다른 글

5. 서비스와 의존성 주입 (2)  (0) 2020.07.04
4. 서비스와 의존성 주입 (1)  (0) 2020.06.30
3. Modules  (0) 2020.06.26
2. Component와 Directive (2)  (0) 2020.06.20
0. Angular 공부 목록  (2) 2020.06.15