13.6 Angular Component - Template Syntax

템플릿 문법

angular Logo

템플릿 문법은 템플릿을 작성하기 위한 Angular 고유의 확장 표기법으로 템플릿과 컴포넌트 간 데이터 공유를 위한 단방향/양방향 데이터 바인딩과 동적으로 DOM 구조, 스타일 등을 변경할 수 있는 빌트인 디렉티브 등을 지원한다. 정적인 뷰는 HTML만으로 정의할 수 있지만 컴포넌트와 연계하여 동적으로 변화하는 뷰를 정의하기 위해서 템플릿 문법을 사용한다.

Angular가 제공하는 템플릿 문법은 아래와 같다.

  • 데이터 바인딩
    • 인터폴레이션(Interpolation)
    • 프로퍼티 바인딩(Property binding)
    • 어트리뷰트 바인딩(Attribute binding)
    • 클래스 바인딩(Class binding)
    • 스타일 바인딩(Style binding)
    • 이벤트 바인딩(Event binding)
    • 양방향 데이터 바인딩(Two-way binding)
  • 빌트인 디렉티브(Built-in directive)
    • 빌트인 어트리뷰트 디렉티브(Built-in attribute directive)
      • ngClass
      • ngStyle
    • 빌트인 구조 디렉티브(Built-in structural directive)
      • ngIf
      • ngFor
      • ngSwitch
  • 템플릿 참조 변수(Template reference variable)

  • 템플릿 표현식 연산자(Template expression operator)

템플릿 문법의 사용에는 아래와 같은 조건이 전제된다.

템플릿 내 사용 금지 항목 비고
script 요소 보안 상 문제
대입연산자(=, +=, -=), 증감 연산자(++, –), 비트 연산자(|, &), 객체 생성 연산자(new) 템플릿 표현식 내에서 데이터를 변경할 수 있는 연산은 사용을 금지한다(Unidirectional data flow 정책) 예를 들어 {{ foo=bar }}는 에러를 발생시킨다.
전역 스코프를 갖는 빌트인 객체 window, document, location, console 등

html, body, base 요소는 사용이 금지되지는 않지만 사용해서는 않된다. 최상위 컴포넌트인 루트 컴포넌트는 html, body 요소의 자식 요소이고 모든 컴포넌트는 루트 컴포넌트의 자식 컴포넌트이기 때문에 컴포넌트의 뷰는 언제나 html, body 요소의 자식 요소이다. 따라서 컴포넌트 템플릿에서 html, body 요소를 사용하면 html, body 요소는 중복된다. base 요소는 head 요소 내에 포함되는 요소로서 상대경로의 루트를 정의한다. Angular는 src/index.html에 base 요소를 사용하여 상대경로 루트를 정의해 두었기 때문에 컴포넌트에서 base 요소를 사용할 이유는 없다.

1. 데이터 바인딩

Angular는 단방향 데이터 바인딩(One-way data binding)과 양방향 데이터 바인딩(Two-way data binding)을 지원한다. 기존 웹 프로그래밍에서 사용하는 DOM 조작 방식보다 간편하게 데이터를 가져와서 뷰에 표현할 수 있다.

Angular는 아래와 같이 7가지 데이터 바인딩을 제공한다.

데이터 바인딩 데이타의 흐름 문법
인터폴레이션 컴포넌트 클래스 ⟹ 템플릿 {{ expression }}
프로퍼티 바인딩 컴포넌트 클래스 ⟹ 템플릿 [property]=”expression”
어트리뷰트 바인딩 컴포넌트 클래스 ⟹ 템플릿 [attr.attribute-name]=”expression”
클래스 바인딩 컴포넌트 클래스 ⟹ 템플릿 [class.class-name]=”expression”
스타일 바인딩 컴포넌트 클래스 ⟹ 템플릿 [style.style-name]=”expression”
이벤트 바인딩 컴포넌트 클래스 ⟸ 템플릿 (event)=”statement”
양방향 데이터 바인딩 컴포넌트 클래스 ⟺ 템플릿 [(ngModel)]=”variable”

1.1 인터폴레이션(Interpolation)

표현식을 두개의 중괄호로 열고 닫은 형식을 인터폴레이션이라 한다. 인터폴레이션은 단방향 바인딩(One-way binding)에 사용되는 템플릿 문법으로 표현식의 평가 결과를 문자열로 변환하여 템플릿에 바인딩한다.

{{ expression }}

표현식(Expression)은 값, 변수, 연산자의 조합이며 이 조합은 연산을 통해 하나의 값을 만든다. 즉 표현식은 하나의 값으로 평가될 수 있는 식이다. 템플릿에서 사용하는 표현식에는 대입연산자(=, +=, -=), 증감 연산자(++, –), 비트 연산자(|, &), 객체 생성 연산자(new)와 같이 템플릿에서 컴포넌트 클래스의 데이터를 변경할 있는 연산은 금지된다. 이는 인터폴레이션 뿐만 아니라 템플릿에서 사용하는 모든 표현식에 적용된다.

프로퍼티 바인딩의 사용 예는 아래와 같다.

import { Component } from '@angular/core';
@Component({
  selector: 'app-root',
  template: `
    <p>name: {{ name }}</p>
    <p>age: {{ age }}</p>
    <p>admin: {{ admin }}</p>
    <p>address: {{ address.city }} {{ address.country }}</p>
    <p>gender: {{ gender }}</p>
    <p>sayHi(): {{ sayHi() }}</p>
    <p>age * 10: {{ age * 10 }}</p>
    <p>age > 10: {{ age > 10 }}</p>
    <p>'stirng': {{ 'stirng' }}</p>
  `
})
export class AppComponent {
  name = 'Angular';
  age = 20;
  admin = true;
  address = {
    city: 'Seoul',
    country: 'Korea'
  };

  sayHi() {
    return `Hi! my name is ${ this.name }.`;
  }
}

컴포넌트 클래스의 프로퍼티가 문자열이 아닌 경우 문자열로 변환되며 존재하지 않는 프로퍼티에 접근하는 경우 에러 발생없이 아무것도 출력하지 않는다.

name: Angular

age: 20

admin: true

address: Seoul Korea

gender:

sayHi(): Hi! my name is Angular.

age * 10: 200

age * 10: true

'stirng': stirng

1.2 프로퍼티 바인딩(Property binding)

프로퍼티 바인딩은 컴포넌트 클래스의 데이터와 템플릿 간의 단방향 바인딩(One-way binding)에 사용되는 템플릿 문법으로 표현식의 평가 결과를 DOM 프로퍼티에 바인딩한다.

<element [property]="expression">...</element>

DOM 프로퍼티는 HTML 요소의 어트리뷰트(Attribute)와는 다른 것이다. 브라우저는 HTML 문서를 파싱하여 DOM 트리로 변환하여 메모리에 적재한다. 이때 HTML 요소는 DOM 노드 객체로, HTML 어트리뷰트는 DOM 노드 객체의 프로퍼티로 변환된다.

프로퍼티 바인딩의 사용 예는 아래와 같다.

import { Component } from '@angular/core';
@Component({
  selector: 'app-root',
  template: `
    <!-- value 프로퍼티에 컴포넌트 클래스의 name 프로퍼티 바인딩 -->
    <input type="text" [value]="name">

    <!-- innerHTML 프로퍼티에 컴포넌트 클래스의 contents 프로퍼티 바인딩 -->
    <p [innerHTML]="contents"></p>

    <!-- src 프로퍼티에 컴포넌트 클래스의 imageUrl 프로퍼티 바인딩 -->
    <img [src]="imageUrl"><br>

    <!-- disabled 프로퍼티에 컴포넌트 클래스의 isUnchanged 프로퍼티 바인딩 -->
    <button [disabled]="isDisabled">disabled button</button>
  `
})
export class AppComponent {
  name = 'ungmo2';
  contents = 'Lorem ipsum dolor sit amet, consectetur adipisicing elit.';
  imageUrl = 'http://lorempixel.com/400/200/';
  isDisabled = true;
}

인터폴레이션은 템플릿의 어디에서도 사용할 수 있다. 인터폴레이션은 순수한 문자열이며 HTML 콘텐츠로 사용할 수도 있고 HTML 어트리뷰트의 값으로 사용할 수도 있다.

<p>{{ contents }}</p>
<input type="text" value="{{ name }}">

Angular는 인터폴레이션을 렌더링 이전에 프로퍼티 바인딩으로 변환한다. 사실 인터폴레이션은 프로퍼티 바인딩의 Syntatic sugar인 것이다. 위 코드는 아래의 코드와 동일하게 동작한다.

<p [innerHTML]="contents"></p>
<input type="text" [value]="name">

1.3 어트리뷰트 바인딩(Attribute binding)

어트리뷰트 바인딩은 컴포넌트 클래스의 데이터와 템플릿 간의 단방향 바인딩(One-way binding)에 사용되는 템플릿 문법으로 표현식의 평가 결과를 HTML 어트리뷰트에 바인딩한다.

<element [attr.attribute-name]="expression">...</element>

앞에서 살펴본 프로퍼티 바인딩과 차이점을 이해하기 위해서 HTML 어트리뷰트(attribute)와 DOM 프로퍼티(property)에 대해서 알아보도록 하자. 어트리뷰트와 프로퍼티는 모두 속성으로 변역되어 같은 것으로 오해할 수 있으나 이들은 서로 다른 것이다. 바인딩이 동작하는 방식을 이해하기 위해서는 HTML의 어트리뷰트와 프로퍼티의 차이를 파악하는 것이 중요하다.

브라우저는 HTML 문서를 파싱하여 DOM 트리로 변환하고 메모리에 적재한다. 이때 HTML 요소는 DOM 노드 객체로, HTML 어트리뷰트는 DOM 노드 객체의 프로퍼티로 변환된다. HTML 어트리뷰트의 값은 언제나 문자열이지만 DOM 프로퍼티는 객체를 비롯하여 모든 값을 가질 수 있다. 주의하여야 할 것은 어트리뷰트와 프로퍼티가 언제나 1:1로 매핑되는 것은 아니라는 것이다. 예를 들어 살펴보자.

  • id 어트리뷰트와 id 프로퍼티와 1:1 매핑한다.
  • class 어트리뷰트는 classList 프로퍼티로 변환된다.
  • td 요소의 colspan 어트리뷰트의 경우 매핑하는 프로퍼티가 존재하지 않는다.
  • textContent 프로퍼티의 경우 어트리뷰트가 존재하지 않는다.
  • input 요소의 value 어트리뷰트는 value 프로퍼티와 1:1 매핑하지만 서로 다르게 동작한다.

아래의 input 요소는 3개의 어트리뷰트를 가지고 있다.

<input id="user" type="text" value="ungmo2">

브라우저가 위의 코드를 파싱하면 DOM 노드 객체 HTMLInputElement가 생성되고 이 객체는 다양한 프로퍼티를 소유한다. input 요소의 모든 어트리뷰트는 HTMLInputElement 객체의 attributes 프로퍼티로 변환되고 getAttribute()로 취득 가능하다.

document.getElementById('user').getAttribute('value') // ungmo2

id 어트리뷰트는 id 프로퍼티와 1:1 매핑하므로 DOM 노드 객체 HTMLInputElement에는 id 프로퍼티가 생성되고 id 어트리뷰트의 값 ‘user’가 할당된다. 하지만 value 어트리뷰트는 value 프로퍼티와 1:1 매핑하지만 서로 다르게 동작한다. DOM 노드 객체에 value 프로퍼티가 생성되고 value 어트리뷰트의 값 ‘ungmo2’이 할당된다. 여기까지는 1:1 매핑하는 id 어트리뷰트와 동일하지만 사용자에 의해 input 요소에 새로운 값이 입력되면 다르게 동작하기 시작한다. 만약 사용자에 의해 “lee”가 입력되면 DOM 노드 객체의 value 프로퍼티는 “lee”로 변경된다. 하지만 value 어트리뷰트는 초기값 “ungmo2”인 상태에서 변경되지 않는다. 이는 HTML 요소가 DOM 노드 객체로 변환된 이후에 HTML 요소의 어트리뷰트는 변하지 않기 때문이다. 하지만 DOM 프로퍼티는 언제든지 바뀔 수 있다. 즉 어트리뷰트는 DOM 프로퍼티의 초기값을 의미하며 DOM 프로퍼티는 현재값을 의미한다.

지금까지 알아본 DOM 프로퍼티와 HTML 어트리뷰트를 차이점을 바탕으로 Angular는 아래의 코드를 어떻게 HTML로 출력할 것인지 예측하여 보자.

import { Component } from '@angular/core';
@Component({
  selector: 'app-root',
  template: `
    <!-- 프로퍼티 바인딩 -->
    <input id="user" type="text" [value]="name">
    <!-- 어트리뷰트 바인딩 -->
    <input id="user" type="text" [attr.value]="name">
  `
})
export class AppComponent {
  name = 'ungmo2';
}

프로퍼티 바인딩은 DOM 노드 객체에 컴포넌트 클래스 프로퍼티를 바인딩하고 어트리뷰트 바인딩은 HTML 요소의 어트리뷰트에 컴포넌트 클래스 프로퍼티를 바인딩한다. 따라서 위 코드는 아래와 같이 변환될 것이다.

<!-- 프로퍼티 바인딩의 변환 결과 -->
<input id="user" type="text">
<!-- 어트리뷰트 바인딩의 변환 결과(name = 'ungmo2'일때) -->
<input id="user" type="text" value="ungmo2">

또 다른 경우를 살펴보자. td 요소의 colspan 어트리뷰트의 경우 매핑하는 프로퍼티가 존재하지 않는다.

import { Component } from '@angular/core';
@Component({
  selector: 'app-root',
  template: `
    <table>
      <tr>
        <!-- colspan 프로퍼티는 존재하지 않는다. -->
        <td [colspan]="length">A + B</td>
      </tr>
      <tr>
        <td>C</td><td>D</td>
      </tr>
    </table>
  `,
  styles: [`
    table, td {
      width: 200px;
      border: 1px solid black;
      text-align: center;
    }
  `]
})
export class AppComponent {
  length = 2;
}

위 코드는 존재하지 않는 DOM 프로퍼티 colspan에 접근하려 때문에 아래와 같은 에러를 발생시킨다.

Unhandled Promise rejection: Template parse errors:
Can't bind to 'colspan' since it isn't a known property of 'td'.

이와 같이 경우, 프로퍼티 바인딩 대신 어트리뷰트 바인딩을 사용한다.

import { Component } from '@angular/core';
@Component({
  selector: 'app-root',
  template: `
    <table>
      <tr>
        <!-- colspan 프로퍼티는 존재하지 않기 때문에 어튜리뷰트 바인딩을 사용한다. -->
        <td [attr.colspan]="length">A + B</td>
      </tr>
      <tr>
        <td>C</td><td>D</td>
      </tr>
    </table>
  `,
  styles: [`
    table, td {
      width: 200px;
      border: 1px solid black;
      text-align: center;
    }
  `]
})
export class AppComponent {
  length = 2;
}

이와 같이 DOM의 프로퍼티는 HTML 요소의 어트리뷰트와는 다르게 동작하기 때문에 프로퍼티 바인딩과 어트리뷰트 바인딩은 구분되어 사용하여야 한다.

1.4 클래스 바인딩(Class binding)

클래스 바인딩을 사용하면 HTML 클래스 어트리뷰트에 클래스를 추가 또는 삭제할 수 있다.

<element [class.class-name]="booleanExpression">...</element>
<element [class]="class-name-list">...</element>

클래스 바인딩은 우변의 표현식을 평가한 후 HTML class 어트리뷰트를 변경한다. HTML class 어트리뷰트에 의해 이미 클래스가 지정되어 있을 때 한개의 클래스를 대상으로 하는 클래스 바인딩([class.class-name])은 HTML class 어트리뷰트를 병합(merge)하여 새로운 HTML class 어트리뷰트를 작성한다. 하지만 복수의 클래스를 대상으로 하는 클래스 바인딩([class])은 기존 HTML class 어트리뷰트를 삭제하고 새로운 HTML class 어트리뷰트를 작성한다. 사용 방법은 아래와 같다.

import { Component } from '@angular/core';
@Component({
  selector: 'app-root',
  template: `
    <!-- 조건의 의한 클래스 바인딩
         우변의 표현식이 true이면 클래스를 추가한다 -->
    <div [class.text-large]="isLarge">text-large</div>
    <!-- 조건의 의한 클래스 바인딩
         우변의 표현식이 false이면 클래스를 삭제한다 -->
    <div class="text-small color-red" [class.color-red]="isRed">text-small</div>
    <!-- 여러개의 클래스를 한번에 지정할  있다 -->
    <div [class]="myClasses">text-large color-red</div>
    <!-- 클래스 바인딩은 기존 클래스 어트리뷰트보다 우선한다.
         따라서 기존 클래스 어트리뷰트는 클래스 바인딩에 의해 reset된다.
         클래스 바인딩의 위치는 관계없다. -->
    <div class="text-small color-blue" [class]="myClasses">text-large color-red</div>
  `,
  styles: [`
    .text-small { font-size: 18px;}
    .text-large { font-size: 36px;}
    .color-blue { color: blue;}
    .color-red { color: red;}
  `]
})
export class AppComponent {
  isLarge = true;
  isRed = false;
  myClasses = 'text-large color-red';
}

클래스 바인딩은 주로 하나의 클래스를 조건에 의해 추가 또는 삭제하는 용도로 사용한다. 여러개의 클래스를 지정할 경우에도 클래스 바인딩을 사용할 수 있으나 ngClass 디렉티브를 사용하면 좀더 세밀한 제어가 가능하다.

1.5 스타일 바인딩(Style binding)

스타일 바인딩을 사용하면 HTML 요소 스타일 어트리뷰트에 스타일을 지정할 수 있다.

<element [style.style-property]="expression">...</element>

스타일 바인딩은 우변의 표현식을 평가한 후 HTML style 어트리뷰트를 변경한다. HTML style 어트리뷰트에 의해 이미 스타일이 지정되어 있을 때 스타일 바인딩은 중복되지 않은 스타일은 병합(merge)하여 그대로 사용하고 중복된 스타일은 스타일 바인딩의 스타일으로 덮어쓴다. 스타일 프로퍼티(border-radius 등)는 케밥표기법(kebab-case) 또는 카멜표기법(camelCase)을 사용한다. 사용 방법은 아래와 같다.

import { Component } from '@angular/core';
@Component({
  selector: 'app-root',
  template: `
    <button class="btn"
      [style.background-color]="isActive ? '#4CAF50' : '#f44336'"
      [style.font-size.em]="isActive ? 1.2 : 1"
      (click)="isActive=!isActive">Toggle</button>
  `,
  styles: [`
    .btn {
      background-color: #4CAF50;
      border: none;
      border-radius: 8px;
      color: white;
      padding: 10px;
      cursor: pointer;
      outline: none;
    }
  `]
})
export class AppComponent {
  isActive = false;
}

스타일 바인딩은 주로 하나의 인라인 스타일을 조건에 의해 추가하는 용도로 사용한다. 여러개의 인라인 스타일을 추가할 경우에는 ngStyle 디렉티브를 사용한다.

1.6 이벤트 바인딩(Event binding)

이벤트 바인딩은 뷰의 상태 변화(버튼 클릭, 체크박스 체크, input에 텍스트 입력 등)에 의해 이벤트가 발생하면 이벤트 핸들러를 호출하는 것을 말한다.

지금까지 살펴본 데이터 바인딩은 컴포넌트 클래스에서 템플릿으로 데이터가 이동하였지만 이벤트 바인딩은 템플릿에서 컴포넌트 클래스로 데이터가 이동한다.

<element (event)="statement">...</element>

간단한 예제를 살펴보자.

import { Component } from '@angular/core';
@Component({
  selector: 'app-root',
  template: `
    <!-- (1) -->
    <input type="text" [value]="name" (input)="onInput($event)">
    <!-- (2) -->
    <button (click)="onClick()">clear</button>
    <!-- (3) -->
    <p>name: {{ name }}</p>
  `
})
export class AppComponent {
  name = '';

  onInput(event) {
    console.log(event);
    // event.target.value에는 사용자 입력 텍스트가 담겨있다.
    this.name = event.target.value;
  }

  onClick() {
    this.name = '';
  }
}
  1. 사용자의 텍스트 입력에 의해 input 이벤트가 발생하면 이벤트 바인딩에 통하여 이벤트 핸들러 onInput을 호출한다. 이때 이벤트 정보를 담고 있는 DOM 이벤트 객체 $event를 이벤트 핸들러에 전달할 수 있다. 이벤트 핸들러 onInput은 input 이벤트를 발생시킨 input 요소(event.target)의 value 프로퍼티(사용자 입력 텍스트가 담겨있다)를 $event로 부터 추출하여 name 프로퍼티에 할당한다. name 프로퍼티는 프로퍼티 바인딩에 의해 다시 input 요소에 바인딩된다.

  2. 버튼이 클릭되면 click 이벤트가 발생하고 이벤트 바인딩에 의해 이벤트 핸들러 onClick을 호출한다. onClick은 name 프로퍼티를 초기화한다.

  3. name 프로퍼티는 인터폴레이션에 의해 템플릿에 바인딩된다.

이벤트 바인딩에는 input이나 click 이벤트 이외에도 다양한 표준 DOM 이벤트를 사용할 수 있다. 표준 DOM 이벤트는 아래의 웹사이트를 참조하기 바란다.

1.7 양방향 데이터 바인딩(Two-way binding)

양방향 데이터 바인딩는 뷰와 컴포넌트 클래스의 상태 변화를 상호 반영하는 것을 말한다. 즉 뷰의 상태가 변화하면 컴포넌트 클래스의 상태도 변화하고 그 반대로 컴포넌트 클래스의 상태가 변화하면 뷰의 상태도 변화하는 것이다.

<element [(ngModel)]="property">...</element>

ngModel 디렉티브를 이벤트 바인딩(())과 프로퍼티 바인딩([]) 형식으로 기술한 후 우변에 뷰와 컴포넌트 클래스가 공유할 프로퍼티를 기술한다. ngModel 디렉티브를 사용하기 위해서는 FormsModule을 모듈에 등록하여야 한다. Angular CLI를 통해 프로젝트를 생성하였다면 아래와 같이 FormsModule이 이미 등록되어 있으므로 별도의 등록이 필요없다.

// src/app/app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';
import { AppComponent } from './app.component';

@NgModule({
  declarations: [AppComponent],
  imports: [BrowserModule, FormsModule, HttpModule],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

간단한 예제를 살펴보자.

import { Component } from '@angular/core';
@Component({
  selector: 'app-root',
  template: `
    <input type="text" [(ngModel)]="name">
    <p>name: {{ name }}</p>
  `
})
export class AppComponent {
  name = '';
}

컴포넌트 클래스의 name 프로퍼티는 템플릿의 input 요소와 양방향으로 바인딩되어 있다. 즉 input 요소의 value 프로퍼티가 변화하면 컴포넌트 클래스의 name 프로퍼티도 동일한 값으로 변화하고 반대로 컴포넌트 클래스의 name 프로퍼티가 변화하면 input 요소의 value 프로퍼티도 동일한 값으로 변화한다.

사실 Angular는 양방향 바인딩을 지원하지 않는다. [()](이것을 Banana in a box라고 부른다)에서 추측할 수 있듯이 양방향 바인딩은 이벤트 바인딩과 프로퍼티 바인딩을 축약 표현(Shorthand syntax)한 것일 뿐이다. 즉 양방향 바인딩은 문법적 편의를 위한 기능이며 실제 동작은 이벤트 바인딩과 프로퍼티 바인딩의 조합으로 이루어진다. 위 코드를 이벤트 바인딩과 프로퍼티 바인딩으로 표현하여 보자.

import { Component } from '@angular/core';
@Component({
  selector: 'app-root',
  template: `
    <input type="text" [value]="name" (input)="name=$event.target.value">
    <p>name: {{ name }}</p>
  `
})
export class AppComponent {
  name = '';
}

<input type="text" [(ngModel)]="name"><input type="text" [value]="name" (input)="name=$event.target.value">은 정확히 동일하게 동작한다. ngModel은 이벤트 바인딩과 프로퍼티 바인딩으로 구현되는 양방향 바인딩을 간편하게 작성할 수 있도록 돕는 디렉티브로서 사용자 입력과 관련돤 DOM 요소(input, textarea, select 등의 form 요소)에서만 사용할 수 있다. ngModel을 이벤트 바인딩과 프로퍼티 바인딩으로 표현하여 보자.

import { Component } from '@angular/core';
@Component({
  selector: 'app-root',
  template: `
    <input [ngModel]="name" (ngModelChange)="name=$event">
    <p>name: {{ name }}</p>
  `
})
export class AppComponent {
  name = '';
}

프로퍼티 바인딩 [ngModel]은 사용자 입력에 관련된 DOM 요소의 프로퍼티(위 예제의 경우 input 요소의 value 프로퍼티)를 업데이트한다. 그리고 이벤트 바인딩 (ngModelChange)은 이벤트를 수신하고 이벤트 핸들러를 통해 DOM의 변화를 외부에 알린다. 이때 ngModelChange는 $event에서 사용자 입력에 관련된 프로퍼티의 값(위 예제의 경우 target.value)를 내부적으로 추출하여 이벤트를 emit한다.

양방향 바인딩은 반드시 ngModel 디렉티브를 사용하여야 하는 것은 아니며 커스텀 양방향 데이터 바인딩도 작성할 수 있다. 이 방법에 대해서는 컴포넌트 간 데이터 통신을 학습한 이후 설명하도록 한다.

2. 빌트인 디렉티브(Built-in directive)

디렉티브(Directive 지시자)는 “DOM의 모든 것(모양이나 동작 등)을 관리하기 위한 지시(명령)”이다. HTML 요소 또는 어트리뷰트의 형태로 사용하여 디렉티브가 사용된 요소에게 무언가를 하라는 지시(directive)를 전달한다.

디렉티브는 애플리케이션 전역에서 사용할 수 있는 공통 관심사를 컴포넌트에서 분리하여 구현한 것으로 컴포넌트의 복잡도를 낮추고 가독성을 향상시킨다. 컴포넌트도 뷰를 생성하고 이벤트를 처리하는 등 DOM을 관리하기 때문에 큰 의미에서 디렉티브로 볼 수 있다.

간단한 예제를 살펴보자. textBlue 디렉티브는 해당 요소의 텍스트 컬러를 파란색으로 변경한다.

// src/app/text-blue.directive.ts
import { Directive, ElementRef, Renderer } from '@angular/core';
@Directive({ selector: '[textBlue]' })
export class TextBlueDirective {
  constructor(el: ElementRef, renderer: Renderer) {
    renderer.setElementStyle(el.nativeElement, 'color', 'blue');
  }
}

textBlue 디렉티브는 요소의 어트리뷰트로 사용한다.

import { Component } from '@angular/core';
@Component({
  selector: 'app-root',
  template: `
    <div textBlue>textBlue directive</div>
  `
})
export class AppComponent { }

디렉티브는 애플리케이션 전역에서 사용할 수 있는 공통 관심사를 컴포넌트에서 분리하여 구현한 것으로 컴포넌트의 복잡도를 낮추고 가독성을 향상시킨다. 컴포넌트도 뷰를 생성하고 이벤트를 처리하는 등 DOM을 관리하기 때문에 큰 의미에서 디렉티브로 볼 수 있다.

이전 버전인 AngularJS에는 70개 이상의 디렉티브가 존재하였으나 컴포넌트 기반의 Angular2 디렉티브는 단순화되어 아래와 같이 ngular는 3가지 유형의 디렉티브를 제공한다.

컴포넌트 디렉티브(Component Directives)
컴포넌트의 템플릿을 표시하기 위한 디렉티브이다. @component 데코레이터의 메타데이터 객체의 seletor 프로퍼티에서 임의의 디렉티브의 이름을 정의한다.
어트리뷰트 디렉티브(Attribute Directives)
어트리뷰트 디렉티브는 HTML 요소의 어트리뷰트로 사용하여 해당 요소의 모양이나 동작을 제어한다. ngClass, ngStyle와 같은 빌트인 디렉티브가 있다.
구조 디렉티브(Structural Directives)
구조 디렉티브는 DOM 요소를 반복 생성(ngFor), 조건에 의한 추가 또는 제거(ngIf, ngSwitch)를 통해 DOM 레이아웃(layout)을 변경한다.

이 장에서는 템플릿에 관련한 빌트인 디렉티브인 어트리뷰트 디렉티브와 구조 디렉티브에 집중하기로 한다. 커스텀 디렉티브는 디렉티브에서 자세히 살펴보도록 하자.

2.1 빌트인 어트리뷰트 디렉티브(Built-in attribute directive)

어트리뷰트 디렉티브는 HTML 요소의 어트리뷰트로 사용하여 해당 요소의 모양이나 동작을 제어한다. ngClass, ngStyle와 같은 빌트인 디렉티브가 있다.

2.1.1 ngClass

여러개의 CSS 클래스를 추가 또는 제거한다. 한개의 클래스를 추가 또는 제거할 때는 클래스 바인딩을 사용하는 것이 좋다.

<element [ngClass]="expression">...</element>

ngClass 디렉티브는 우변의 표현식을 평가한 후 HTML class 어트리뷰트를 변경한다. HTML class 어트리뷰트에 의해 이미 클래스가 지정되어 있을 때 ngClass 디렉티브는 HTML class 어트리뷰트를 병합(merge)하여 새로운 HTML class 어트리뷰트를 작성한다. 사용 방법은 아래와 같다.

import { Component } from '@angular/core';
@Component({
  selector: 'app-root',
  template: `
    <ul>
      <!-- 문자열에 의한 클래스 지정  -->
      <li [ngClass]="stringCssClasses">bold blue</li>
      <!-- 배열에 의한 클래스 지정  -->
      <li [ngClass]="ArrayCssClasses">italic red</li>
      <!-- 객체에 의한 클래스 지정  -->
      <li [ngClass]="ObjectCssClasses">bold red</li>
      <!-- 컴포넌트 메소드에 의한 클래스 지정 -->
      <li [ngClass]="getCSSClasses('italic-blue')">italic blue</li>
    </ul>
  `,
  styles: [`
    .text-bold   { font-weight: bold; }
    .text-italic { font-style: italic; }
    .color-blue  { color: blue; }
    .color-red   { color: red; }
  `]
})
export class AppComponent {
  state = true;

  // 문자열 클래스 목록
  stringCssClasses = 'text-bold color-blue';
  // 배열 클래스 목록
  ArrayCssClasses = ['text-italic', 'color-red'];
  // 객체 클래스 목록
  ObjectCssClasses = {
    'text-bold': this.state,
    'text-italic': !this.state,
    'color-blue': !this.state,
    'color-red': this.state
  };

  // 클래스 목록을 반환하는 컴포넌트 메소드
  getCSSClasses(flag: string) {
    let classes;
    if (flag === 'italic-blue') {
      classes = {
        'text-bold': !this.state,
        'text-italic': this.state,
        'color-red': !this.state,
        'color-blue': this.state
      };
    } else {
      classes = {
        'text-bold': this.state,
        'text-italic': !this.state,
        'color-red': this.state,
        'color-blue': !this.state
      };
    }
    return classes;
  }
}

2.1.2 ngStyle

여러개의 HTML 인라인 스타일을 추가 또는 제거한다. 한개의 인라인 스타일을 추가 또는 제거할 때는 스타일 바인딩을 사용하는 것이 좋다.

<element [ngStyle]="expression">...</element>

ngStyle 디렉티브는 우변의 표현식을 평가한 후 HTML style 어트리뷰트를 변경한다. HTML style 어트리뷰트에 의해 이미 스타일이 지정되어 있을 때 ngStyle 디렉티브는 HTML style 어트리뷰트를 병합(merge)하여 새로운 HTML style 어트리뷰트를 작성한다. 사용 방법은 아래와 같다.

import { Component } from '@angular/core';
@Component({
  selector: 'app-root',
  template: `
    <div>
      Width: <input type="text" [(ngModel)]="width">
      <button (click)="increaseWidth()">+</button>
      <button (click)="decreaseWidth()">-</button>
    </div>
    <div>
      Height: <input type="text" [(ngModel)]="height">
      <button (click)="increaseHeight()">+</button>
      <button (click)="decreaseHeight()">-</button>
    </div>
    <button (click)="isShow=!isShow">{{ isShow ? 'Hide' : 'Show' }}</button>
    <!-- 스타일 지정  -->
    <div
      [ngStyle]="{
        'width.px': width,
        'height.px': height,
        'background-color': bgColor,
        'visibility': isShow ? 'visible' : 'hidden'
      }">
    </div>
  `
})
export class AppComponent {
  width = 200;
  height = 200;
  bgColor = '#4caf50';
  isShow = true;

  increaseWidth()  { this.width  += 10; }
  decreaseWidth()  { this.width  -= 10; }
  increaseHeight() { this.height += 10; }
  decreaseHeight() { this.height -= 10; }
}

2.2 빌트인 구조 디렉티브(Built-in structural directive)

구조 디렉티브는 DOM 요소를 반복 생성(ngFor), 조건에 의한 추가 또는 제거를 수행(ngIf, ngSwitch)을 통해 뷰의 구조를 변경한다.

  • 구조 디렉티브에는 * 접두사를 추가하며 []을 사용하지 않는다.
  • 하나의 호스트 요소(디렉티브가 적용된 요소)에는 하나의 구조 디렉티브만을 사용할 수 있다.

2.2.1 NgIf

NgIf 디렉티브는 우변 표현식의 연산 결과가 참이면 해당 요소(호스트 요소)를 DOM에 추가하고 거짓이면 해당 요소(호스트 요소)를 DOM에서 제거한다. 우변의 표현식은 true 또는 false로 평가될 수 있는 값을 사용한다.

<element *ngIf="expression">...</element>

NgIf 디렉티브 앞에 붙은 *(asterisk)는 아래 구문의 문법적 설탕(syntactic sugar)이다. 즉 위 코드는 아래의 코드로 변환된다.

<ng-template [ngIf]="expression">
  <element>...</element>
</ng-template>

Angular는 *ngIf를 만나면 호스트 요소를 template 태그로 래핑하고 *ngIf를 프로퍼티 바인딩으로 변환한다. NgFor와 NgSwitch 디렉티브도 동일한 패턴을 따른다.

버튼 클릭에 의해 요소를 show/hide하는 간단한 예제를 살펴보자.

import { Component } from '@angular/core';
@Component({
  selector: 'app-root',
  template: `
    <!-- ngIf 의한 show/hide -->
    <p *ngIf="isShow">Lorem ipsum dolor sit amet</p>

    <!-- 스타일 바인딩에 의한 show/hide -->
    <p [style.display]="isShow ? 'block' : 'none'">Lorem ipsum dolor sit amet</p>
    <button (click)="isShow=!isShow">{{ isShow ? 'Hide' : 'Show' }}</button>
  `,
  styles: [`
    p { background-color: #CDDC39; }
  `]
})
export class AppComponent {
  isShow = true;
}

NgIf 디렉티브를 사용하지 않고 스타일 바인딩 또는 클래스 바인딩을 사용하여 요소의 표시/비표시를 구현할 수 있다. 하지만 스타일 바인딩 또는 클래스 바인딩에 의해 비표시된 요소는 브라우저에 의해 렌더링이 되지 않지만 DOM에 남아있다. NgIf 디렉티브에 의해 제거된 요소는 DOM에 남아있지 않고 완전히 제거되어 불필요한 자원의 낭비를 방지한다.

show-hide

NgIf에 의해 제거된 요소는 DOM에 남아있지 않는다.

Angular 4부터 ngIf else가 추가되었다. NgIf 우변의 표현식이 참이면 호스트 요소를 DOM에 추가하고 거짓이면 else 이후에 기술한 ng-template 요소의 자식을 DOM에 추가한다.

<!-- if else -->
<element *ngIf="expression; else elseBlock">Truthy condition</element>
<ng-template #elseBlock>Falsy condition</ng-template>

<!-- if else -->
<element *ngIf="expression; then thenBlock else elseBlock"></element>
<ng-template #thenBlock>Truthy condition</ng-template>
<ng-template #elseBlock>Falsy condition</ng-template>

<!-- if -->
<element *ngIf="expression; then thenBlock"></element>
<ng-template #thenBlock>Truthy condition</ng-template>

간단한 예제를 살펴보자.

import { Component } from '@angular/core';
@Component({
  selector: 'app-root',
  template: `
    <div>
      <input type="radio" id="one" name="skill" (click)="mySkill=$event.target.value" value="HTML"> <label for="one"> HTML</label>
      <input type="radio" id="two" name="skill" (click)="mySkill=$event.target.value" value="CSS"> <label for="two"> CSS</label>
    </div>

    <div *ngIf="mySkill==='HTML'; else elseBlock">HTML</div>
    <ng-template #elseBlock><div>CSS</div></ng-template>

    <div *ngIf="mySkill==='HTML'; then thenBlock_1 else elseBlock_1"></div>
    <ng-template #thenBlock_1><div>HTML</div></ng-template>
    <ng-template #elseBlock_1><div>CSS</div></ng-template>
  `
})
export class AppComponent {
  mySkill = 'HTML';
}

2.2.2 NgFor

NgFor 디렉티브는 컴포넌트 클래스의 컬렉션을 반복하여 호스트 요소(NgFor 디렉티브가 사용된 요소) 및 하위 요소를 DOM에 추가한다. 컬렉션은 일반적으로 배열을 사용한다.

<element *ngFor="let item of items; let i=index">...</element>

<element *ngFor="let item of items; let i=index; let odd=odd; trackBy: trackById">...</element>

NgFor 디렉티브 앞에 붙은 *(asterisk)는 아래 구문의 문법적 설탕(syntactic sugar)이다. 즉 위 코드는 아래의 코드로 변환된다.

<ng-template ngFor let-item [ngForOf]="items">
  <element>...</element>
</ng-template>

<ng-template ngFor let-item [ngForOf]="items" let-i="index" let-odd="odd" [ngForTrackBy]="trackById">
  <element>...</element>
</ng-template>

위 코드는 컴포넌트 클래스의 프로퍼티 items을 바인딩한 후 items의 갯수만큼 순회하며 개별 항목을 item에 할당한다. item(템플릿 입력 변수, template input variable)은 호스트 요소 및 하위 요소에서만 유효한 로컬 변수이다. items에 해당하는 바인딩 객체는 일반적으로 배열을 사용하지만 반드시 배열만 사용할 수 있는 것은 아니다. ES6의 for…of에서 사용할 수 있는 반복 가능한 객체(iterable object)라면 사용이 가능하다. 단 문자열, Map, 배열이 아닌 일반 객체는 사용할 수 없다.

인덱스를 취득할 필요가 있는 경우, 인덱스를 의미하는 index를 사용하여 변수에 인덱스를 저장할 수 있다. index 이외에도 first, last, even, odd와 같은 로컬 변수가 제공된다. 자세한 내용은 NgFor API reference를 참조하기 바란다.

<element *ngFor="let item of items; let i=index">...</element>

객체의 배열을 리스트로 출력하는 예제를 살펴보자.

import { Component } from '@angular/core';
@Component({
  selector: 'app-root',
  template: `
    <!-- user 추가한다 -->
    <input type="text" #name placeholder="이름을 입력하세요">
    <button (click)="addUser(name.value)">add user</button>
    <ul>
      <!-- users 배열의 length만큼 반복하며 li 요소와 하위 요소를 DOM 추가한다 -->
      <li *ngFor="let user of users; let i=index">
        {{ i }}: {{ user.name }}
        <!-- 해당 user 제거한다 -->
        <button (click)="removeUser(user)">X</button>
      </li>
    </ul>
    <pre>{{ users | json }}</pre>
  `
})
export class AppComponent {
  users = [
      { id: 1, name: 'Lee' },
      { id: 2, name: 'Kim' },
      { id: 3, name: 'Baek' }
    ];

  // user를 추가한다
  addUser(name): void {
    if (name) {
      this.users.push({ id: this.getLastId() + 1, name: name });
    }
  }

  // 해당 user를 제거한다.
  removeUser(user) {
    this.users = this.users.filter(({ id }) => id !== user.id);
  }

  // users의 요소 중 가장 큰 id를 반환한다
  getLastId(): number {
    let lastId = 1;
    this.users.map(({ id }) => id > lastId ? lastId = id : id);
    return lastId;
  }
}

users 배열의 length만큼 반복하며 li 요소와 하위 요소를 DOM에 추가한다. 템플릿의 for of 구문에서 사용된 user 변수는 users 배열의 개별요소를 일시적으로 저장하며 호스트 요소의 하위 요소에서만 유효한 로컬 변수이다.

NgFor 디렉티브는 컬렉션 데이터(users)가 변경되면 컬렉션과 연결된 모든 DOM 요소를 제거하고 다시 생성한다. 이는 컬렉션의 변경 사항을 추적(tracking)할 수 없기 때문이다. 때문에 크기가 매우 큰 컬렉션을 다루는 경우, 퍼포먼스 상의 문제를 발생시킬 수 있다. NgFor 디렉티브는 퍼포먼스를 향상시키기 위한 기능으로 trackBy를 제공한다.

trackBy 기능을 추가하여 위 예제를 수정하여 보자.

import { Component } from '@angular/core';
@Component({
  selector: 'app-root',
  template: `
    <!-- user 추가한다 -->
    <input type="text" #name placeholder="이름을 입력하세요">
    <button (click)="addUser(name.value)">add user</button>
    <ul>
      <!-- users 배열의 length만큼 반복하며 li 요소를 DOM 추가한다 -->
      <!-- 변경을 트랙킹을   있도록 trackBy 추가하였다. -->
      <li *ngFor="let user of users; let i=index; trackBy: trackByUserId">
        {{ i }}: {{ user.name }}
        <!-- 해당 user 제거한다 -->
        <button (click)="removeUser(user)">X</button>
      </li>
    </ul>
    <pre>{{ users | json }}</pre>
  `
})
export class AppComponent {
  users = [
      { id: 1, name: 'Lee' },
      { id: 2, name: 'Kim' },
      { id: 3, name: 'Baek' }
    ];

  // user를 추가한다
  addUser(name): void {
    if (name) {
      this.users.push({ id: this.getLastId() + 1, name: name });
    }
  }

  // 해당 user를 제거한다.
  removeUser(user) {
    this.users = this.users.filter(({ id }) => id !== user.id);
  }

  // 변경 트래킹 기준을 반환한다.
  trackByUserId(index, user) {
    // user.id를 기준으로 변경을 트래킹한다.
    return user.id; // or index
  }

  // users의 요소 중 가장 큰 id를 반환한다
  getLastId(): number {
    let lastId = 1;
    this.users.map(({ id }) => id > lastId ? lastId = id : id);
    return lastId;
  }
}

user 객체의 id 프로퍼티를 사용하여 변경을 트랙킹할 수 있도록 trackByUserId 메소드를 추가하였다. 이때 user 객체의 id 프로퍼티는 유니크하여야 한다. user 객체의 id 프로퍼티를 사용하지 않고 trackByUserId에 인자로 전달된 index를 사용하여도 무방하다.

add user 또는 X 버튼을 클릭하면 해당 user를 추가/제거한다. 예를 들어 3번째 user 객체를 제거하면 users의 변경을 DOM에 반영하여야 한다. 이때 trackBy를 사용하지 않는 경우 NgFor는 DOM을 다시 생성한다. trackBy를 사용한 경우 user.id를 기준으로 컬렉션의 변경을 트래킹하기 때문에 퍼포먼스가 향상된다.

일반적인 경우 NgFor는 충분히 빠르기 때문에 trackBy에 의한 퍼포먼스 최적화는 기본적으로 필요하지 않다. trackBy는 퍼포먼스에 문제가 있는 경우에만 사용한다.

2.2.3 NgSwitch

NgSwitch 디렉티브는 switch 조건에 따라 여러 요소 중에 하나의 요소를 선택하여 DOM에 추가한다. JavaScript의 switch문과 유사하게 동작한다.

<element [ngSwitch]="expression">
  <!-- switch 조건이 'case1'인 경우 DOM에 추가 -->
  <element *ngSwitchCase="'case1'">...<element>
  <!-- switch 조건이 'case2'인 경우 DOM에 추가 -->
  <element *ngSwitchCase="'case2'">...<element>
  <!-- switch 조건과 일치하는 ngSwitchCase가 없는 경우 DOM에 추가 -->
  <element *ngSwitchDefault>...<element>
</element>

NgSwitch 디렉티브 앞에 붙은 *(asterisk)는 아래 구문의 문법적 설탕(syntactic sugar)이다. 즉 위 코드는 아래의 코드로 변환된다.

<element [ngSwitch]="expression">
  <ng-template [ngSwitchCase]="'case1'">
    <element>...</element>
  </ng-template>
  <ng-template [ngSwitchCase]="'case2'">
    <element>...</element>
  </ng-template>
  <ng-template ngSwitchDefault>
    <element>...</element>
  </ng-template>
</element>

NgSwitch 디렉티브를 활용한 예제는 아래와 같다.

import { Component } from '@angular/core';
@Component({
  selector: 'app-root',
  template: `
    <input type='text' [(ngModel)] ="num" placeholder="숫자를 입력하세요">
    <div [ngSwitch]="num">
      <div *ngSwitchCase="'1'">One</div>
      <div *ngSwitchCase="'2'">Two</div>
      <div *ngSwitchCase="'3'">Three</div>
      <div *ngSwitchDefault>This is Default</div>
    </div>
  `
})
export class AppComponent {
  num: string;
}

3. 템플릿 참조 변수(Template reference variable)

템플릿 참조 변수는 DOM 요소에 대한 참조를 담고 있는 변수이다. 태그 내에서 해시 기호(#)를 변수명 앞에 추가하여 템플릿 참조 변수를 선언한다. 템플릿 참조 변수는 템플릿 내에서만 유효하다. 이벤트 바인딩에 의해 컴포넌트 클래스로 템플릿 참조 변수의 값을 전달할 수는 있지만 컴포넌트 클래스에 어떠한 부수 효과(Side effect)도 주지 않는다.

<element #myelement>...</element>

템플릿 참조 변수를 활용한 예제는 아래와 같다. 입력된 이메일의 형식을 체크하여 결과를 표시한다.

import { Component } from '@angular/core';
@Component({
  selector: 'app-root',
  template: `
    <div>
      <!-- 템플릿 참조 변수 email 선언 -->
      <input #email type='email' placeholder="이메일을 입력하세요">
      <!-- 템플릿 참조 변수 email 참조  -->
      <button (click)="checkEmail(email.value)">이메일 형식 체크</button>
    </div>
    <span *ngIf="result">{{ result }}</span>
  `
})
export class AppComponent {
  result: string;

  checkEmail(email: string) {
    const regexr = /^[0-9a-zA-Z]([-_\.]?[0-9a-zA-Z])*@[0-9a-zA-Z]([-_\.]?[0-9a-zA-Z])*\.[a-zA-Z]{2,3}$/i;

    if (regexr.test(email)) {
      this.result = '';
    } else {
      this.result = '이메일 주소의 형식이 유효하지 않습니다.';
    }
  };
}

4. 템플릿 표현식 연산자(Template expression operator)

4.1 세이프 내비게이션 연산자(Safe navigation operator)

세이프 내비게이션 연산자 ?는 컴포넌트 클래스의 프로퍼티의 값이 null 또는 undefined인 경우 발생하는 에러를 회피하기 위해 사용한다.

import { Component } from '@angular/core';
@Component({
  selector: 'app-root',
  template: `
    <!-- obj null 또는 undefined  아무것도 표시하지 않는다. -->
    {{ obj }}
    <!-- ERROR TypeError: Cannot read property 'id' of undefined -->
    {{ obj.id }}
    <!-- 세이프 내비게이션 연산자는 null 또는 undefined 프로퍼티를 만나면 처리를 종료하고 에러를 발생시키지 않는다. -->
    {{ obj?.id }}
  `
})
export class AppComponent { }

4.2 파이프 연산자(Pipe operator)

애플리케이션이 관리하는 데이터는 사용자가 실생활에서 익숙한 형태의 데이터가 아닌 경우가 많다. 예를 들어, Date 생성자 함수가 리턴하는 인스턴스를 문자열화하면 아래와 같다.

const today = new Date();

console.log(today.toString()); // Sat Sep 23 2017 00:26:55 GMT+0900 (KST)

Date 생성자 함수가 리턴한 인스턴스를 문자열화하면 사용자가 읽기 쉬운 형식은 아니다. 아마도 사용자는 “Sat Sep 23 2017 00:26:55 GMT+0900 (KST)” 형식보다는 “2017년 09월 23일 12시 26분 55초”과 같이 읽기 쉬운 형식으로 표시되기를 원할 것이다. 이때 데이터 자체를 변경하는 것은 사이드 이펙트가 있으므로 화면에 표시 형식만 변경하고 싶을 때 사용하는 것이 파이프이다.

파이프를 사용하여 사용자가 읽기 쉬운 형식으로 변환하여 보자.

// app.component.ts
import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  template: `
    <h1>{{ today }}</h1>
    <h1>{{ today | date }}</h1>
    <h1>{{ today | date: 'y년 MM월 dd일 hh시 mm분 ss초' }}</h1>
  `
})
export class AppComponent {
  today = new Date();
}

파이프를 사용한 위 컴포넌트의 출력 결과는 아래와 같다.

Sat Sep 23 2017 00:26:55 GMT+0900 (KST)

Sep 23, 2017

2017년 09월 23일 12시 26분 55초

이와 같이 파이프는 템플릿 내에서 원하는 형식으로 값을 변환하여 표시하는 기능이다. 파이프의 사용 방법은 아래와 같다.

{{ value | PipeName }}
{{ value | PipeName : customOption1 :  customOption2 }}
{{ value | PipeName1 | PipeName2 }}

위와 같이 값 뒤에 파이프 연산자 | 를 붙인 후 원하는 파이프를 지정한다. 예를 들어 문자열을 대문자로 변환하여 표시하는 파이프 uppercase를 사용하여 보자.

import { Component } from '@angular/core';
@Component({
  selector: 'app-root',
  template: `
    {{ name | uppercase }}
  `
})
export class AppComponent {
  name = 'lee';
}

Angular는 uppercase 이외에도 아래와 같은 빌트인 파이프를 지원한다.

파이프 의미
date 날짜 형식 변환 출력
JSON JSON 형식 변환 출력
uppercase 대문자 변환 출력
lowercase 소문자 변환 출력
currency 통화 형식 변환 출력
percent 퍼센트 형식 변환 출력
decimal 자리수 형식 변환 출력
slice 문자열 추출 출력
async 비동기 객체 출력

빌트인 파이프의 사용 예제는 아래와 같다. 자세한 사용법은 Angular Pipe API List을 참조하기 바란다.

// app.component.ts
import { Component } from '@angular/core';
import { Observable } from 'rxjs/Rx';

@Component({
  selector: 'app-root',
  template: `
    <h3>DatePipe</h3>
    <p>{{ today | date: 'y년 MM월 dd일 hh시 mm분 ss초' }}</p>

    <h3>CurrencyPipe</h3>
    <!-- 한국원:통화기호표시:소숫점위 최소 1자리 소숫점아래 1~2 -->
    <p>{{ price | currency: 'KRW':true:'1.1-2' }}</p>

    <h3>SlicePipe : array</h3>
    <ul>
      <li *ngFor="let i of collection | slice:1:3">{{i}}</li>
    </ul>

    <h3>SlicePipe : string</h3>
    <p>{{ str | slice:0:4 }}</p>

    <h3>JsonPipe</h3>
    <pre>{{ object | json }}</pre>

    <h3>DecimalPipe</h3>
    <p>{{ pi | number:'3.5-5' }}</p>

    <h3>PercentPipe</h3>
    <p>{{ num | percent:'4.3-5' }}</p>

    <h3>UpperCasePipe</h3>
    <p>{{ str | uppercase }}</p>

    <h3>LowerCasePipe</h3>
    <p>{{ str | lowercase }}</p>

    <h3>AsyncPipe</h3>
    <p>{{ second | async }}</p>
  `
})
export class AppComponent {
  today = new Date();
  price = 0.1234;
  collection: string[] = ['a', 'b', 'c', 'd'];
  str = 'abcdefghij';
  object: Object = { foo: 'bar', baz: 'qux', nested: { xyz: 3 } };
  pi = 3.141592;
  num = 1.3495;
  second = Observable.interval(1000).map(i => i).take(11);
}

커스텀 파이프에 대한 상세한 내용은 파이프에서 알아보도록 하자.

Reference

Back to top
Close