2. 문자열 바인딩
문자열 바인딩을 활용하면 HTML 템플릿에 문자열을 동적으로 반영할 수 있다. 그래서 사용자의 이름을 넣어서 환영 문구를 표시하는 방식으로 애플리케이션 화면의 내용을 동적으로 변경할 수 있다.
1. 문자열 값 표시하기
문자열 바인딩(interpolation)은 표현식 안에 문자열을 넣는 것을 의미한다. 기본적으로 문자열 바인딩은 이중 중괄호({{
, }}
)를 구분자로 사용한다.
문자열 바인딩이 동작하는 것을 확인하기 위해 Angular 컴포넌트 안에 currentCustomer
라는 프로퍼티가 있다고 하자:
이 프로퍼티 값을 템플릿에 표시하려면 이렇게 작성하면 된다:
그러면 Angular가 currentCustomer
라는 표현식을 프로퍼티의 값으로 대체한다. 이 예제에서는 Maria
로 대체된다.
같은 방식으로 이미지의 제목과 URL을 바인딩하려면 title
프로퍼티와 itemImageUrl
프로퍼티를 이렇게 바인딩하면 된다:
2. 템플릿 표현식(Template expressions)
템플릿 표현식은 이중 중괄호 {{ }}
를 사용해서 어떤 값을 만들어 내는 문법이다. Angular는 이런 표현식의 평가 결과를 바인딩 대상의 프로퍼티로 할당한다. 이 때 바인딩 대상은 HTML 엘리먼트나 컴포넌트, 디렉티브가 될 수 있다.
1) 표현식 계산 과정
좀 더 자세하게 설명하면, Angular는 이중 중괄호 안에 사용된 문자열을 가장 먼저 평가해서 문자열로 결정한다. 숫자 두 개를 더하는 과정을 자세하게 분석해 보자:
표현식에서는 getVal()
과 같은 호스트 컴포넌트의 메서드를 실행할 수도 있다:
<!-- "The sum of 1 + 1 is not 4" -->
<p>The sum of 1 + 1 is not {{1 + 1 + getVal()}}.</p>
Angular는 문자열 바인딩을 이렇게 처리한다:
1] 이중 중괄호 안에 있는 표현식을 모두 평가한다.
2] 표현식의 결과를 문자열로 변환한다.
3] 표현식 평가 결과를 주변 문자열과 결합한다.
4] 결과를 엘리먼트/디렉티브 프로퍼티로 할당한다.
Note
- 문자열 바인딩 문법의 구분기호는
@Component
메타데이터의 intepolation 옵션으로 변경할 수 있다.
2) 문법
템플릿 표현식은 JavaScript 문법과 비슷하다. 그래서 JavaScript 표현식은 대부분 그대로 템플릿 표현식으로 사용할 수 있지만, 일부 문법은 예외이다.
표현식 외부에 영향을 미치거나 미칠 가능성이 있는 이런 표현들은 사용할 수 없다:
- 값을 할당하는 표현 (
=
,+=
,=
,...
) new
,typeof
,instanceof
연산자;
나,
를 사용해서 표현식을 여러 번 사용하는 표현++
,-
와 같은 증감 연산자- ES2015 이상에서 지원하는 연산자 일부
그리고 JavaScript 문법에서 지원하는 것과 이런 점이 다르다:
|
,&
와 같은 비트 연산자는 사용할 수 없다.|
,?.
,!
와 같은 템플릿 표현식 연산자를 사용할 수 있다.
2. 표현식의 컨텍스트
문자열 바인딩 표현식에는 컨텍스트가 존재한다. 이 때 컨텍스트는 애플리케이션의 일부 영역을 의미하며, 일반적으로 컴포넌트 인스턴스 범위가 된다.
아래 예제 코드에서 표현식 안에 있는 recommended
와 itemImageUrl2
는 모두 AppComponent
에 있는 프로퍼티를 가리키는 것이다.
템플릿 입력 변수나 템플릿 참조 변수를 사용하면 템플릿 안에만 존재하는 프로퍼티를 참조할 수도 있다.
아래 예제 코드에 사용된 customer
는 템플릿 입력 변수를 의미한다.
<ul>
<li *ngFor="let customer of customers">{{customer.name}}</li>
</ul>
그리고 아래 예제 코드에서 #customerInput
은 템플릿 참조 변수를 의미한다.
<label>Type something: <input #customerInput />{{customerInput.value}} </label>
Note
- 템플릿 표현식은
undefined
외에는 전역 네임스페이스에 있는window
나document
와 같은 객체를 참조할 수 없다. - 그리고 표현식 컨텍스트 범위를 넘어가는
console.log()
나Math.max()
와 같은 함수도 실행할 수 없다.
1) 이름 충돌 방지하기
표현식이 평가되는 컨텍스트는 템플릿 변수, 디렉티브 컨텍스트 객체, 컴포넌트 멤버가 조합되어 구성된다. 그래서 템플릿 표현식에서 사용하는 이름이 네임스페이스에서 한 곳 이상 겹치면 이런 순서로 참조 객체를 결정한다:
1] 템플릿 변수 이름 중에서
2] 디렉티브 컨텍스트 안에서
3] 컴포넌트 멤버 중에서
그래서 다른 컨텍스트의 영향을 받아 변수가 가려지는 상황을 방지하려면 네임스페이스와 관계없이 변수의 이름을 겹치지 않게 지정해야 한다. 아래 예제에서 AppComponent
템플릿에 있는 customer
는 컴포넌트 클래스에 있는 Padma 값이 들어간다.
그리고 customers
배열을 순회할 때 사용하는 customer
는 ngFor
로 반복되는 개별 객체를 의미한다.
@Component({
template: `
<div>
<!-- Hello, Padma -->
<h1>Hello, {{ customer }}</h1>
<ul>
<!-- Ebony and Chiho in a list-->
<li *ngFor="let customer of customers">{{ customer.value }}</li>
</ul>
</div>
`,
})
class AppComponent {
customers = [{ value: "Ebony" }, { value: "Chiho" }];
customer = "Padma";
}
ngFor
안에 있는 customer
는 <ng-template>
컨텍스트 안에 존재하기 때문에 customers
배열의 개별 항목을 반환하며, 이 예제에서는 Ebony와 Chiho를 표시한다. 하지만 ngFor
가 순회하는 배열 안에는 Padma가 존재하지 않는다. Padma를 표시할 때 사용한 customer
는 ngFor
바깥에 있는 다른 컨텍스트이다. <h1>
에 사용된 customer
는 컴포넌트 클래스에서 Padma라고 선언된 customer
프로퍼티를 가리킨다.
3. 모범 사례
템플릿 표현식은 이렇게 작성하는 것이 좋다:
1] 짧게 작성한다.
- 프로퍼티 이름이나 메서드 실행만 하는 정도가 좋다. 애플리케이션 로직이나 비즈니스 로직은 컴포넌트 클래스에 작성한다. 그래야 개발하기 쉽고 테스트하기도 쉽다.
2] 간단하게 실행되도록 작성한다.
- Angular는 변화 감지 싸이클마다 템플릿 표현식을 실행한다. 그리고 Promise 처리, HTTP 이벤트, 타이머 이벤트, 키 입력 이벤트나 마우스 이동과 같은 비동기 작업들도 변화 감지 싸이클을 다시 실행시킨다. 그래서 표현식은 사용자가 불편함을 느끼지 않을 정도로 간단하게 실행되어야 한다. 이 내용은 디바이스 성능이 낮을수록 더 중요하다. 표현식이 실행되는 시간이 길다면 캐싱을 하는 방법도 고려해 본다.
3] 사이드 이펙트 최소화
- Angular의 단방향 데이터 흐름 모델에 따르면, 템플릿 표현식은 변경하려고 하는 대상 외에는 어떠한 것도 변경하지 않는 것이 좋다. 컴포넌트 프로퍼티의 값을 읽는 동작이 다른 값을 변경하면 안된다. 화면은 렌더링이 한 번 끝나고 난 후에 안정된 상태가 되어야 한다.
Note
- 멱등(idempotent) 표현식을 작성하면 사이드 이펙트를 최소화할 수 있으며 Angular의 변화 감지 성능도 향상시킬 수 있다.
- Angular의 관점에서 이야기하는 "멱등 표현식"은 관련 값이 변경되지 않는 한 언제나 같은 값을 반환하는 표현식을 의미한다.
- 그리고 표현식과 관련된 값들도 이벤트 루프가 한 번 실행되는 동안 변경되면 안된다.
- 멱등 표현식은 문자열이나 숫자를 반환하는데, 이 표현식이 정말 멱등이라면 이 표현식이 다시 한 번 실행되어도 같은 문자열이나 같은 숫자 값을 반환해야 한다.
- 표현식이 객체나 배열을 반환한다면, 다시 한 번 실행되어도 같은 객체 참조를 반환해야 한다.