Notice
Recent Posts
Recent Comments
Link
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
Tags
more
Archives
Today
Total
관리 메뉴

개발을 하기는 합니다만

[Angular][03] 컴포넌트란? 본문

Angular/Angular Official Guide

[Angular][03] 컴포넌트란?

jaeyoung-lee 2020. 3. 15. 11:48

본 글은 앵귤러 공식 홈페이지(http://angular.io)의 DOC 항목을 한국어로 (공부 목적으로) 요약(?)정리(?)한 글임을 밝힙니다.


컴포넌트란?

컴포넌트는 뷰라고 불리우는 화면의 일부를 제어합니다. 예를 들어, 예시 포트폴리오 사이트 내의 개별 컴포넌트는 다음과 같은 뷰를 정의하고 컨트롤합니다. (Vue.js를 이용하여 개발되었지만 설명을 위해 첨부합니다.)

개인 포트폴리오 사이트입니다.

  • 상단에 페이지 메뉴 바를 가진 app root
  • SKILLS 리스트와 SKILLS 에서 EXPEREIENCE와 ETC로 이동할 수 있는 버튼
  • 우측면의 상태 표시 바

또한 Tour-of-Heroes 예시 사이트는 다음과 같은 뷰를 정의하고 관리합니다.

Tour-of-Heroes

  • Dashboard와 Heroes 를 오갈 수 있는 navigation link를 포함한 app root
  • heroes의 리스트
  • hero를 편집할 수 있는 hero editor

컴포넌트의 클래스 내부에서 컴포넌트가 뷰를 위해 어떠한 동작을 하는지 설명하는 애플리케이션 로직을 정의할 수 있습니다. 클래스는 프로퍼티와 메소드의 API를 통해 뷰와 상호작용합니다.

 

예를 들면, HeroListComponent는 hero 배열을 담는 heroes 프로퍼티를 가집니다. selectHero()메서드는 유저가 클릭을 통해 hero를 선택하면 selectedHero 프로퍼티를 수정합니다. 컴포넌트는 service로부터 heroes를 받아와야하며, service는 생성자 내부의 TypeScript 인자 프로퍼티(parameter property)를 통해 의존성 주입(Dependency Injection)된다고 할 수 있습니다.

//src/app/hero-list.component.ts (class)

export class HeroListComponent implements OnInit {
  heroes: Hero[];
  selectedHero: Hero;

  // heroes를 제공하는 HeroService를 TS parameter property를 통해 참조
  constructor(private service: HeroService) { }

  //Lifecycle Hook
  ngOnInit() {
    this.heroes = this.service.getHeroes();
  }

  selectHero(hero: Hero) { this.selectedHero = hero; }
}

앵귤러는 유저가 앱을 사용함에 따라 컴포넌트를 생성, 수정, 소멸 시킵니다. 앵귤러 앱은 각 생애주기(lifecylce) 마다 필요에 따라 ngOnInit()과 같은 생애주기 후킹 함수(lifecycle hooks)을 통해 동작을 취할 수 있습니다.

컴포넌트 메타데이터

@Component 데코레이터는 바로 밑의 클래스가 컴포넌트 클래스임을 밝히며 메타데이터를 전달합니다. 하단 예시 코드에서, HeroListComponent가 어떠한 앵귤러 문법도 가지지 않은 일반 클래스라는 것을 알 수 있습니다. @Component 데코레이터로 수식되기 전까지 클래스는 컴포넌트가 될 수 없습니다.

 

컴포넌트의 메타데이터는 컴포넌트와 뷰가 생성되고 보여지기 위해서 어떠한 곳에서 어떤 필요 요소를 받아야 하는지 앵귤러 앱에게 말해줍니다. 자세히 말하자면, 메타데이터는 컴포넌트를 메타데이터 내부에 정의된 inline 템플릿이나 외부 파일에 있는 템플릿을 컴포넌트에 연결시켜 뷰를 표현합니다.

 

템플릿을 메타데이터 내부에 포함시킬지, 외부에서 불러올지 결정하는 것 이외에도 @Component 메타데이터는 HTML 내에서 컴포넌트가 어떻게 표현될지, 어떠한 services를 필요로하는지 명시해줍니다.

 

밑의 코드는 HeroListComponent의 기본적인 메타데이터입니다.

src/app/hero-list.component.ts (metadata)

@Component({
  selector:    'app-hero-list',
  templateUrl: './hero-list.component.html',
  providers:  [ HeroService ]
})
export class HeroListComponent implements OnInit {
/* . . . */
}

예시에서 볼 수 있는 @Component의 유용한 메타데이터는 다음과 같습니다.

selector: `태그 이름`

CSS selector는 앵귤러에게 HTML 템플릿 상에서 명시한 태그(여기서는 )가 발견되면 해당 컴포넌트를 생성하고 (DOM 트리 내부에)주입해야 함을 말해줍니다. 예를 들어, 앱의 HTML이 를 포함하고 있다면, 앵귤러는 HeroListComponent를 두 opening, closing 태그 사이에 추가해야합니다.

templateUrl: 'HTML파일의 상대적 위치' / template: `inline HTML`

컴포넌트에 포함될 HTML 템플릿의 모듈 상대적 위치(module-relative address 현 모듈과 타켓의 상대적 위치)입니다. 이 대신, HTML 템플릿을 template 프로퍼티의 값으로써 inline 형식으로 제공할 수도 있습니다. 위의 템플릿은 컴포넌트의 호스트뷰를 정의합니다.

src/app/./hero-list.component.html
<p> HeroList Works!</p>

src/app/hero-list.component.ts (metadata)
...
  templateURL: './hero-list.component.html',
  // 동일한 표현!!
  template: `
      <p> HeroList Works!</p>
  `
...

providers: [서비스 배열]

컴포넌트가 필요로하는 서비스 제공자의 배열. 이 예시에서는 providers는 컴포넌트의 생성자가 heroes 배열을 받기 위해 필요한 HeroService (싱글턴)인스턴스를 어떻게 제공해야하는지 앵귤러에게 알려줍니다.

템플릿과 뷰

컴포넌트위 뷰는 템플릿으로 정의합니다. 템플릿은 HTML형식이며 앵귤러에게 어떻게 컴포넌트를 렌더링해야하는지 말해줍니다.

 

뷰는 보통 계층적으로 구성되며, 이는 UI 섹션이나 페이지를 유닛으로써 수정하고, 표현하며 숨길 수 있습니다. 템플릿은 해당 컴포넌트의 호스트 뷰를 정의하는 (최상위) 컴포넌트와 연결됩니다. 또한 컴포넌트는 자체적으로 뷰 계층을 정의하여 하위 컴포넌트가 표현하는 의존 뷰(embedded views) 포함할 수 있습니다.

컴포넌트 트리

뷰 계층은 동일한 NgModul에 있는 컴포넌트들의 뷰를 포함할 수 있지만, 다른 NgModules에 정의된 컴포넌트의 뷰를 포함 할 수도 있으며 흔히 후자가 행해집니다.

템플릿 문법

템플릿은 일반적인 HTML 같아 보이지만, HTML을 앱 로직, 앱 상태와 DOM 데이터에 기반하여 변환하는 앵귤러 템플릿 문법을 포함합니다. 템플릿은 데이터 바인딩을 통하여 앱과 DOM을 표시할 수 있고, 파이프를 사용하여 게시 전에 데이터를 수정할 수 있으며, 디렉티브를 활용하여 게시되어지는 뷰에 앱 로직을 적용할 수도 있습니다.

다음은 HeroListComponent의 템플릿이다.

//src/app/hero-list.component.html

<h2>Hero List</h2>

<p><i>Pick a hero from the list</i></p>
<ul>
  <li *ngFor="let hero of heroes" (click)="selectHero(hero)">
    {{hero.name}}
  </li>
</ul>

<app-hero-detail *ngIf="selectedHero" [hero]="selectedHero"></app-hero-detail>

위 템플릿은 h2나 p, ui, li 와 같은 일반적인 HTML 엘리먼트를 사용하지만, *ngFor, {{ hero.name }}, (click), [hero]와 과 같은 앵귤러 템플릿 문법 또한 포함합니다. 템플릿 문법 엘리먼트들을 프로그램 로직과 데리터를 사용하여 어떻게 HTML 을 렌더링 해야하는지 앵귤러에게 알려줍니다.

  • *ngFor 디렉티브는 앵귤러에게 리스트를 어떠한 망신으로 순회하는지 명시해줍니다.
  • {{ hero.name }}, (click), [hero]는 사용자 동작이나 프로그램 데이터를 DOM과 연결합니다. 이를 "데이터 바인딩(data binding)"이라고 합니다.
  • 태그는 새로운 컴포넌트인 HeroDetailComponent를 표현하는 엘리먼트입니다. HeroDetailComponent는 HeroListComponent의 자식 컴포넌트로 hero의 세부사항을 보여주는 뷰를 정의합니다. 이러한 커스텀 컴포넌트가 native HTML와 같은 레이아웃에서 자연스럽게 어울릴 수 있다는 것에 주목해야 합니다.

데이터 바인딩

프레임워크를 사용하지 않는다면, HTML에 직접 데이터를 넣어줘야하고 사용자의 반응을 직접 동작과 값들의 변화로 변환 해주어야 합니다. 이러한 push/pull 로직(데이터를 DOM에 넣고, 끌어오는 과정)을 직접 작성하는 것은 이미 많은 프론트엔드 개발자들이 경험했듯이 너무나 번거롭고, 빈번한 에러를 발생시키는 끔찍한 작업입니다.

 

앵귤러는 템플릿의 일부와 컴포넌트의 일부를 연결시켜주는 "양방향 데이터 바인딩(two-way data binding)"을 지원합니다. 템플릿 HTML에 바인딩을 추가하게 되면 앵귤러에게 어떠한 방식으로 컴포넌트와 뷰를 연결시켜야하는지 명시해주게 됩니다.

 

하단의 그림은 4가지 종류의 데이터 바인딩 방식을 보여줍니다. 각각의 형식은 DOM으로, DOM으로부터, 양방향의 방향성을 가집니다.

 

위에서 부터 DOM으로, DOM으로, DOM에서부터, 양방향의 방향성을 가집니다.

 

HeroListComponent 템플릿 예시는 이 중 세 가지의 형식을 포함합니다.

//src/app/hero-list.component.html (binding)

<li>{{hero.name}}</li>
<app-hero-detail [hero]="selectedHero"></app-hero-detail>
<li (click)="selectHero(hero)"></li>
  • {{ hero.name }} "문자열 바인딩(interpolation, 보간법)" 은 컴포넌트의 hero.name 프로퍼티의 값을
  • 컴포넌트 내에 표시합니다
  • [hero] "프로퍼티 바인딩(property binding)" 은 부모인 HeroListComponent의 selectedHero 값을 자식인 HeroDetailComponent의 hero 프로퍼티에 전달합니다.
  • (click) "이벤트 바인딩(event binding)"은 사용자가 hero의 이름을 클릭하였을 때 컴포넌트의 selectHero 메소드를 호출합니다.

템플릿 기반 폼에서 많이 사용되는 양방양 데이터바인딩은 프로퍼티 바인딩과 이벤트 바인딩을 합쳐 놓은 것 입니다. 아래는 ngModel 디렉티브를 사용하여 양방향 바인딩을 한 예시입니다.

//src/app/hero-detail.component.html (ngModel)

<input [(ngModel)]="hero.name">

양방향 바인딩에서 프로퍼티 값은 프로퍼티 바인딩과 함께 컴포넌트로부터 인풋 박스로 전달됩니다. 사용자의 입력값이 변하면 변화된 값은 컴포넌트로 다시 전파(from DOM to Comp)되며 마치 이벤트 바인딩과 같이 프로퍼티 값을 최신화합니다(from Comp to DOM).

 

앵귤러는 JavaScript 이벤트 싸이클마다 애플리케이션의 최상위 컴포넌트부터 자식 컴포넌트로 내려가며 데이터 바인딩을 모두 실행합니다.

 

템플릿-컴포넌트 간 바인딩

데이터 바인딩은 템플릿과 컴포넌트, 그리고 부모와 자식 컴포넌트 사이의 커뮤니케이션에서 중요한 역할을 합니다.

 

부모-자식 컴포넌트 간 바인딩

파이프(pipes)

앵귤러 파이프는 데이터가 템플릿 HTML에 표시될 때 형식 변환 방식을 설정할 수 있습니다. @Piep 데코레이터가 있는 클래스는 인풋 값을 받아 뷰에 표시될 아웃풋 값으로 변환해주는 함수를 정의합니다.

앵귤러는 다양한 날짜 파이프, 통화 파이프 등 다양한 파이프를 가지고 있으며, 새로운 파이프를 직접 정의할 수도 있습니다. 자세한 사항은 파이프 API 리스트에서 확인할 수 있습니다.

HTML 템플릿 내에서 파이프를 이용한 값 변환을 하기 위해서는 파이프 연산자(|)를 사용해야합니다.

{{ 변환되는 값 | 파이프 명 }}

특정 파이프의 아웃풋이 다른 파이프의 인풋으로 넣으면서 파이프를 체인(chain)화 할 수도 있습니다. 파이프는 또한 해당 파이프가 어떠한 방식으로 변환을 하게 될지 제어하는 인자를 받아들일 수도 있습니다. 다음과 같이 date 파이프가 원하는 형식을 변환하도록 인자를 전달 할 수 있습니다.

<!-- 기본 형식: 'Jun 15, 2015'-->
 <p>Today is {{today | date}}</p>

<!-- fullDate 형식: 'Monday, June 15, 2015'-->
<p>The date is {{today | date:'fullDate'}}</p>

 <!-- shortTime 형식: '9:43 AM'-->
 <p>The time is {{today | date:'shortTime'}}</p>

디렉티브

앵귤러 템플릿은 동적입니다. 앵귤러가 템플릿을 렌더링 할 때, 앵귤러는 디렉티브가 제공한 로직에 따라 DOM을 변형시킵니다. 디렉티브는 @Directive() 데코레이터가 있는 클래스입니다.

 

컴포넌트 또한 문법적으로 디렉티브 입니다. 그러나 컴포넌트라는 디렉티브의 일종은 앵귤러 앱에서 너무나도 개별적이고 중요한 요소이기에 @Component()라는 데코레이터를 따로 두었으며, @Component() 데코레이터는 @Directive() 데코레이터에 템플릿 기능을 추가한 것이라고 볼 수 있습니다.

 

컴포넌트 외에도 "구조적 디렉티브(structural directive)"와 "어트리뷰트 디렉티브(attribute directive)"라는 두 종류의 디렉티브가 존재합니다. 앵귤러는 두 종류에 속하는 다양한 디렉티브를 정의하고 있으며, 필요에 따라 @Directive() 데코레이터를 사용하여 직접 디렉티브를 정의할 수 있습니다.

 

컴포넌트처럼, 디렉티브도 데코레이터로 수식되어지는 클래스에 메타데이터를 지정할 수 있으며, selector를 지정함으로써 HTML 내부에 넣을 수도 있습니다. 템플릿에서 디렉티브는 보통 엘리먼트 내부의 어트리뷰트로 표현되며, 이 어트리뷰트에 표현식을 연결하거나 데이터를 바인딩 할 수 있습니다.

구조적 디렉티브(Structural Directives)

구조적 디렉티브는 DOM에 요소를 더하고, 없애고, 대체함으로써 레이아웃을 변환합니다. 하단의 예시코드에서는, 뷰의 랜더링 방식을 표현하는 애플리케이션 로직을 더하기 위하여 두 내장(built-in) 구조적 디렉티브를 사용합니다.

//src/app/hero-list.component.html (structural)

<li *ngFor="let hero of heroes"></li>
<app-hero-detail *ngIf="selectedHero"></app-hero-detail>
  • *ngFor은 heroes 배열을 순회하며 각 hero 마다 상응하는
  • 엘리먼트를 생성하도록 합니다.
  • *ngIf는 조건식으로 selectedHero 가 true를 반환할 때, 즉 사용자가 hero를 선택했을 때 HeroDetail 컴포넌트를 보여줍니다.

어트리뷰트 디렉티브(Attribute Directives)

어트리뷰트 디렉티브는 이미 존재하는 엘리먼트의 모양이나 동작을 변형합니다. 이름에서 알 수 있다 싶이, 템플릿 내에서 어트리뷰트 디렉티브는 평범한 HTML 어트리뷰트처럼 보입니다.

 

양방향 데이터 바인딩을 지원하는 ngModel 디렉티브는 어트리뷰트 디렉티브의 한 종류입니다. ngModel 은 해당 엘리먼트의 표시 값을 설정하고 변화되는 값에 반응을 함으로써 이미 존재하는 엘리먼트(보통은 input)의 동작을 변형합니다.

//src/app/hero-detail.component.html (ngModel)

<input [(ngModel)]="hero.name">

이해를 돕기위해 외부의 예시를 가지고 왔습니다.

<h3>My Budget: ₩{{budget}}</h3> 
<input type="text" [(ngModel)]="budget" >

// [(ngModel)]은 내부적으로 아래와 같이 동작합니다.
// [ngModel]="budget", (ngModelChange)="budget =$event"
  • ngModel의 양방향 바인딩을 통하여 사용자의 인풋은 컴포넌트 내부의 buget 값을 수정(from DOM, 이벤트 바인딩)하는 동시에 템플릿을 통해 표현되는 DOM의엘리먼트 내부 budget을 수정(to DOM, 프로퍼티 바인딩)합니다.

이 외에도 기본 디렉티브에는 조건에 따라 레이아웃을 선택해서 표시하는 ngSwitch, DOM 엘리먼트와 컴포넌트에 스타일을 지정하며 수정하는 ngStyle과 클래스를 지정하는 ngClass가 있습니다.

Comments