3.1. Template 활용과 데이터 바인딩

3.1. Template 활용과 데이터 바인딩

지금까지 Angular의 간단한 템플릿 문법이나 기본으로 제공하는 디렉티브를 활용해 보았습니다. 이제 이들에 대해서 조금 더 알아보도록 하겠습니다. 먼저, Angular의 템플릿 문법을 정리하도록 하겠습니다. 템플릿을 다룰 때는 보통 두 가지 측면에서의 도구를 제공합니다. 첫 번째는 데이터 바인딩이고 두 번째는 템플릿 구조입니다. Angular는 데이터 바인딩을 위한 4가지 문법을 가지고 있고, 템플릿 구조는 컴포넌트와 디렉티브, 특히 구조적 디렉티브(Structural Directives)를 통해서 이뤄집니다. 앞서 언급한 적이 있는 구조적 디렉티브에 특별히 "*" 표현을 사용하는 것도 별 뜻 없이 한 일은 아닐테지요.

3.1.1 데이터 바인딩

지금부터는 데이터 바인딩을 알아봅시다.

바인딩은 두 방향으로 고려됩니다. 컴포넌트의 데이터를 DOM 엘리먼트에 적용하는 방향과 DOM 엘리먼트가 사용자에게 받은 입력을 컴포넌트에 알리는 방향입니다. 바인딩을 이용하면 훨씬 편리한 화면구성 및 사용자 대응을 할 수 있습니다. 표시 할 내용이 변경된 것을 반영하기 위해서 직접 DOM 엘리먼트를 접근하여 변경하지 않아도 됩니다. 바인딩된 컴포넌트의 모델을 변경한 후 이를 반영하라고 하면 Angular가 알아서 변경된 내용을 화면에 반영할 것입니다. 심지어 Angular는 다시 반영해야할 시점까지 대부분 스스로 판단합니다. 또한, 사용자의 입력 결과를 알기 위해서 DOM 엘리먼트를 직접 찾지 않고 바인딩된 이벤트를 기다리면 된다는 멋진 장점을 가지고 있습니다.

이 중 한 방향에 대해서만 지정한 경우를 단방향 바인딩(One-way data binding)이라 하고 두 방향 모두를 지정하는 경우를 양방향 바인딩(Two-way data binding)이라 합니다. AngularJS에서는 기본적으로 양방향 바인딩을 제공하였습니다. 양방향 바인딩은 DOM 엘리먼트의 변경도 사용자의 입력도 모두 바인딩된 컴포넌트의 모델만 다루면 된다는 큰 장점을 가지고 있지만 자칫 변경을 체크하거나 변경에 의한 다른 변경이 이어지거나 하는 부작용을 보이기도 합니다. 마법같았던 양방향 바인딩의 매력은 좀 더 정교하고 복잡한 어플리케이션을 만드는 데에는 발목을 잡는 요인이 되곤 하였습니다.

이에 Angular에서는 데이터 바인딩 문법으로 보간(Interpolation), 프로퍼티 바인딩, 이벤트 바인딩, 양방향 바인딩(Two-way data binding)을 제공합니다. 단방향 바인딩과 양방향 바인딩을 모두 제공하며 기본적으로 단방향 바인딩을 자주 사용하게 됩니다. 지금부터 하나씩 알아보겠습니다.

<p>1 + 1 = {{ 1 + 1 }}</p> <!-- 결과 : 1 + 1 = 2 -->

보간은 중첩된 중괄호 {{<expression>}} 으로 사용합니다. 보간은 말 그대로 해당 위치를 표현식이 나타내는 값으로 채워줍니다. 가장 기초적인 바인딩 문법이라 할 수 있겠습니다. 보간은 컴포넌트의 데이터를 DOM 엘리먼트에 적용하는 방향의 단방향 바인딩 입니다. 위 예제에서는 표현식 1 + 1의 결과인 "2"가 해당 위치에 채워지는 것을 확인할 수 있습니다.

/* ... */
export class Component {
    currentUser = { firstName: '길동', lastName: '홍' };

    getName() {
        return this.currentUser.lastName + this.currentUser.firstName;
    }
}
<p>당신의 이름은 {{ currentUser.lastName + currentUser.firstName }} 입니다.</p> <!-- 결과 : 당신의 이름은 홍길동 입니다. -->
<p>당신의 이름은 {{ getName() }} 입니다.</p> <!-- 결과 : 당신의 이름은 홍길동 입니다. -->

뿐만 아니라 컴포넌트와 연결된 템플릿은 컴포넌트의 속성에 접근할 수 있습니다. 속성이 함수라면 실행하여 결과를 반영할 수도 있습니다.

/* ... */
export class Component {
    userImageUrl = '/img/profile.png';
}
<img src="{{userImageUrl}}" alt="프로필" /> <!-- 결과 : <img src="/img/profile.png" alt="프로필" /> -->

보간은 텍스트 노드를 채우기도 하지만 DOM 엘리먼트의 속성을 구성할 때 사용하기도 합니다. 하지만 이는 프로퍼티 바인딩 방식을 사용하는 것을 대부분의 상황에서 권장합니다.

<img [src]="userImageUrl" alt="프로필" />

이 코드는 보간을 사용한 코드와 동일하게 동작합니다. 이렇게 대괄호를 속성에 표시한 [<property>]="<expression>" 를 프로퍼티 바인딩이라 합니다. 표현식의 결과를 해당 DOM의 프로퍼티에 반영합니다. 프로퍼티 바인딩 역시 컴포넌트의 데이터를 DOM 엘리먼트에 적용하는 단방향 바인딩입니다. Angular는 프로퍼티 바인딩에 좀 더 세심한 문법을 제공합니다.

<span class="title" [class]="newClass">"new" 가 된다.</span> <!-- newClass = 'new' -->
<span class="title" [class.on]="isTarget">"title new" 가 된다.</span> <!-- isTarget = true -->
<span [style.font-size.em]="targetSize">2em 사이즈</span> <!-- targetSize = 2 -->

클래스와 스타일을 다룰 때 직관적인 표현을 발견할 수 있습니다. [class]="<expression>" 표현은 표현식의 결과로 클래스를 재할당 합니다. 기존의 클래스는 유지되지 않습니다. 반면에 [class.<className>]="<predicate>" 표현은 명제(predicate)가 참이면 해당 클래스를 적용하게 됩니다. 해당 클래스의 여부만을 고려할 뿐 다른 클래스는 무관하게 유지됩니다. [style.<property>]="<expression>" 표현은 해당 스타일 속성에 표현식의 결과를 적용합니다. 왠지 익숙하다면 그 느낌은 맞습니다. 우리는 이 부분을 기본 디렉티브인 ngClassngStyle 에서 다룬적이 있습니다. 여기에서 하나 더 짚어볼 내용은 어트리뷰트에 관한 내용입니다.

HTML 어트리뷰트와 DOM 프로퍼티는 구분하기 쉽지 않습니다. 하지만 명확히 다릅니다. DOM 은 Document Object Model의 약자로 HTML 요소에 대응하는 모델입니다. HTML은 어트리뷰트를 가지고 있고 DOM은 프로퍼티를 가지고 있습니다. Angular의 바인딩은 정확한 의미에서 DOM의 프로퍼티에 적용되는 기법입니다. 일반적으로 이 둘을 구분하는 것은 큰 의미는 없습니다. DOM이 HTML에 관한 모델인 만큼 대부분의 HTML 어트리뷰트가 DOM의 프로퍼티에 준비되어 있기 때문입니다. 다소 예외가 있기에 Angular는 어트리뷰트를 바인딩 할수 있는 프로퍼티를 준비해 두고 있습니다.

<table>
    <!-- 이 코드는 에러가 발생합니다 -->
    <!-- <tr><td [colspan]="numOfSmallRoom">넓은방</td></tr> -->
    <tr><td [attr.colspan]="numOfSmallRoom">넓은방</td></tr> <!-- numOfSmallRoom = 2 -->
    <tr><td>작은방</td><td>작은방</td></tr>
</table>

colspan 은 DOM 프로퍼티가 없는 HTML 어트리뷰트 중에 하나 입니다. [colspan]="<expression>"으로 직접 다루면 Angular는 에러를 표시합니다. 이런 경우에는 [attr.<attribute>]="<expression>"으로 다룰 수 있습니다. 참고로 Angular가 DOM 프로퍼티에 적용된다는 의미는 보간 방식에도 같습니다. 보간을 사용해서는 HTML 어트리뷰트를 다룰 방법이 없다는 것을 느끼게 됩니다. 이후 설명할 이벤트 바인딩과 양방향 바인딩에서도 마찬가지 입니다. 앞서 언급된 것 처럼 일반적으로는 구분없이 사용 가능하니 사실 크게 문제될 부분은 아닙니다.

/* ... */
export class Component {
    open() { /* ... */ }
}
<button (click)="open()">열기</button>

소괄호는 이벤트 바인딩 표현입니다. (<event>)="<expression>"는 해당 이벤트가 해당 DOM에서 발생했을 때 표현식을 수행합니다. 이벤트 바인딩은 사용자의 입력이 DOM에 발생했을 때 이를 컴포넌트에 알리는 단방향 바인딩입니다. 이로써 양 방향에 대한 길이 모두 갖춰졌습니다. 사용자의 반응은 불특정하게 발생합니다. 이벤트지요. 그래서 이벤트가 발생될 때 대응할 작업을 표현식으로 작성합니다. 위 예제는 'click' 이벤트가 해당 버튼에서 발생하면 open()을 실행합니다.

<input (keydown.enter)="value = $event.target.value" />
<p>{{value}}</p>

프로퍼티 바인딩에서 클래스와 스타일을 다루는 직관적인 표현이 있었습니다. 이벤트 바인딩에도 키 이벤트 필터링(Key event filtering)을 통해 간결한 표현을 사용할 수 있습니다. 위 예제는 "keydown" 이벤트 중에서 "Enter" 키에 대해서만 동작합니다. 우리는 이 작업을 이벤트를 처리하는 함수에서 이벤트 키를 통해 분류하는 분기문을 통해 다루곤 합니다. 그 대신 키 이벤트 필터링 문법을 사용하면 더 직관적이고 분류가 쉬운 코드를 작성하는데 도움을 받을 수 있습니다. keyupkeydown 이벤트에 대해서 키 이벤트 필터링을 사용할 수 있습니다. 키 이벤트 필터링은 키 입력값에 대부분 직관적으로 대응합니다.

<input
    (keyup.1)="press1()"
    (keyup.a)="pressA()"
    (keyup.space)="pressSpace()"
    (keydown.esc)="pressEscape()"
    (keydown.alt.dot)="pressAltDot()"
    (keydown.ctrl.f2)="pressCtrlF2()"/>

숫자, 문자, 기능키에 조합키까지 직관적으로 표현이 가능하고 필터들을 두 개 이상 나열하여 분류를 할 수도 있습니다. 프로퍼티 바인딩의 클래스와 스타일 문법과 키 이벤트 필터링 문법은 간결하고 직관적인 템플릿 문법을 통해 반복될 수 있는 코드를 줄여주고 가독성을 높혀주기에 활용해 보시길 권장합니다.

이전 예제에 $event가 잠시 등장합니다. 이 변수는 이벤트 바인딩에서 내부적으로 사용되는 변수입니다. 이벤트가 포함하는 정보, 즉 이벤트 객체를 이 변수를 통해서 참조할 수 있습니다. 이 예제에서 keydown 이벤트의 경우 DOM 이벤트 객체가 $event 변수에 전달됩니다. 따라서 $event.target은 이벤트가 일어난 DOM 엘리먼트이고 $event.target.value는 우리가 원하는 이벤트가 일어난 DOM 엘리먼트의 현재 값입니다.

<pizza (onSelect)="select($event)"></pizza>

DOM 이벤트외에 우리가 만든 커스텀 이벤트 역시 이벤트 바인딩을 통해서 사용됩니다. 이 때 $event 변수에는 우리가 전달한 이벤트 객체를 받게 됩니다.

Template 변수

results matching ""

    No results matching ""