Skip to content

9. 템플릿 변수


템플릿 변수를 활용하면 템플릿 안에서 다른 영역에 있는 데이터를 참조할 수 있다. 그래서 템플릿 변수는 사용자가 입력한 내용에 반응하는 동작을 구현하거나, 애플리케이션 폼을 사용하기 좋게 튜닝할 때 주로 사용한다.


템플릿 변수는 이런 항목을 가리킬 수 있다:



Note


1. 문법

템플릿 안에서 해시 기호 #를 사용하면 템플릿 변수를 선언할 수 있다. 아래 코드처럼 #phone이라고 지정하면 <input> 엘리먼트에 phone 변수를 템플릿 변수로 선언한 것이다.


src/app/app.component.html
<input #phone placeholder="phone number" />


템플릿 변수는 컴포넌트 템플릿 안에서 자유롭게 참조할 수 있다. 아래 코드에서 <button>는 컴포넌트 메서드를 실행할 때 인자를 전달하기 위해 phone 변수를 참조했다.


src/app/app.component.html
<input #phone placeholder="phone number" />

<!-- 다른 엘리먼트들 -->

<!-- input 엘리먼트는 이벤트 핸들러에 phone 엘리먼트의 `value`를 전달합니다. -->
<button (click)="callPhone(phone.value)">Call</button>


2. 템플릿 변수에 할당되는 값은 어떻게 결정되나요?

Angular에서 템플릿 변수를 선언하면 이 템플릿 변수가 선언된 위치에 따라 참조하는 인스턴스의 타입이 결정된다.


  • 컴포넌트에 템플릿 변수를 선언하면 컴포넌트 인스턴스를 가리킨다.
  • 표준 HTML 태그에 템플릿 변수를 선언하면 엘리먼트를 가리킨다.
  • <ng-template> 엘리먼트에 템플릿 변수를 선언하면 TemplateRef 인스턴스를 가리킨다.
  • #var="ngModel"과 같이 템플릿 변수 오른쪽에 이름을 지정하면 디렉티브/컴포넌트에서 exportAs 이름에 해당하는 인스턴스를 가리킨다.


1) NgForm에 템플릿 변수 사용하기

보통은 템플릿 변수가 선언된 엘리먼트가 템플릿 변수의 값이 된다. 이전 예제에서도 템플릿 변수 phone은 전화번호가 입력되는 <input> 엘리먼트를 가리킨다. 그래서 버튼을 클릭하면 컴포넌트에 있는 callPhone() 메서드를 실행하면서 <input> 엘리먼트의 값을 인자로 전달한다.


아래 코드는 NgForm 디렉티브를 직접 참조하기 위해 디렉티브에서 exportAs로 지정된 값을 템플릿 변수 값으로 할당받는 예제 코드이다. 이 코드에서 템플릿 변수 itemForm은 템플릿 안에서 세 번 사용된다.


src/app/hero-form.component.html
<form #itemForm="ngForm" (ngSubmit)="onSubmit(itemForm)">
  <label for="name">Name</label>
  <input
    type="text"
    id="name"
    class="form-control"
    name="name"
    ngModel
    required
  />
  <button type="submit">Submit</button>
</form>

<div [hidden]="!itemForm.form.valid">
  <p>{{ submitMessage }}</p>
</div>


ngForm 어트리뷰트 값을 사용하지 않으면 itemForm이 참조하는 객체는 HTMLFormElement, <form> 엘리먼트가 된다.


NgFormitemForm은 모두 NgForm 디렉티브를 가리키키 때문에, 이 객체들을 활용하면 폼에 있는 폼 컨트롤의 값을 참조하거나 유효성을 검사할 수 있다.


표준 <form> 엘리먼트와 다르게 NgForm 디렉티브에는 form 프로퍼티가 존재한다. NgFormform 프로퍼티를 itemForm.form.valid와 같이 활용하면 폼이 유효하지 않은 상태일 때 제출 버튼을 비활성화시킬 수 있다.


3. 템플릿 변수를 참조할 수 있는 범위

템플릿 변수는 해당 템플릿 안에서 자유롭게 참조할 수 있다. *ngIf*ngFor, <ng-template>과 같은 구조 디렉티브를 템플릿 안에서 사용할 수 있는 것과 비슷하다. 템플릿을 넘어가는 범위에서는 참조할 수 없다.


Note

  • 애플리케이션이 제대로 동작하는 것을 보장하려면 템플릿 변수 하나를 여러 번 선언하지 않는다.


1) 중첩된 템플릿 안에서 접근하기

중첩된 템플릿에서 안쪽에 있는 템플릿에서는 바깥에 있는 템플릿 변수를 참조할 수 있다.


아래 코드에서 <input> 엘리먼트의 값을 변경하면 <span>의 내용이 변경되는데, 두 엘리먼트가 템플릿 변수 ref1로 연결되어 있기 때문이다.


src/app/app.component.html
<input #ref1 type="text" [(ngModel)]="firstExample" />
<span *ngIf="true">Value: {{ ref1.value }}</span>


<span><ng-template>으로 감싸 보자. 템플릿 변수 ref1<ng-template> 바깥쪽에 선언되었다. 이런 경우라면 자식 템플릿은 부모 템플릿의 컨텍스트를 상속받기 때문에 이전처럼 동작한다.


<ng-template> 부분을 이해하기 쉽게 수정해 보자.


<input #ref1 type="text" [(ngModel)]="firstExample" />

<!-- 새 템플릿 -->
<ng-template [ngIf]="true">
  <!-- 부모 컨텍스트를 상속받기 때문에 새 템플릿에서도 템플릿 변수를 참조할 수 있습니다. -->
  <span>Value: {{ ref1.value }}</span>
</ng-template>


하지만 부모 템플릿에서 자식 템플릿에 선언된 템플릿 변수를 참조하는 로직은 동작하지 않는다.


<input *ngIf="true" #ref2 type="text" [(ngModel)]="secondExample" />
<span>Value: {{ ref2?.value }}</span>
<!-- 동작하지 않습니다. -->


ref 부분을 이해하기 쉽게 수정해 보자.


<ng-template [ngIf]="true">
  <!-- ref2는 자식 템플릿 안쪽에 선언되어 있습니다. -->
  <input #ref2 type="text" [(ngModel)]="secondExample" />
</ng-template>
<!-- 자식 템플릿 밖에서 ref2를 참조하는 로직은 동작하지 않습니다. -->
<span>Value: {{ ref2?.value }}</span>


*ngFor의 경우도 생각해 보자.


<ng-container *ngFor="let i of [1,2]">
  <input #ref type="text" [value]="i" />
</ng-container>
{{ ref.value }}


이 경우에도 ref.value는 동작하지 않는다. 이 코드에서 배열에는 항목이 2개 있기 때문에 구조 디렉티브 *ngFor도 템플릿을 두 벌 생성한다. 그래서 ref.value가 어떤 값을 가리키는지 결정할 수 없다.


그리고 *ngFor*ngIf와 같은 구조 디렉티브는 초기화되지 않은 템플릿 안쪽을 참조할 수 없다.


결국 Angular는 참조하는 객체의 값을 결정할 수 없기 때문에 에러가 발생한다.


2) <ng-template> 안에서 템플릿 변수에 접근하기

<ng-template>에 템플릿 변수를 선언하면 이 변수는 템플릿을 표현하는 TemplateRef 인스턴스를 가리킨다.


src/app/app.component.html
<ng-template #ref3></ng-template>
<button (click)="log(ref3)">Log type of #ref</button>


이 예제에서 버튼을 클릭하면 log() 함수가 실행되면서 #ref3가 참조하는 값을 콘솔에 출력한다. 이 때 #ref3 변수는 <ng-template>에 선언되었기 때문에 ref3가 가리키는 값은 TemplateRef가 된다.


콘솔에 출력된 결과를 확인해보면 이렇다:


▼ ƒ TemplateRef()
name: "TemplateRef"
__proto__: Function


4. 템플릿 입력 변수(Template input variable)

템플릿 입력 변수는 템플릿 안에서 한 번만 선언하는 변수이다. 템플릿 입력 변수는 let 키워드를 사용해서 let hero와 같이 선언한다.


Angular 가이드 문서에서도 템플릿 입력 변수는 heri, i, odd와 같이 자주 사용된다.


<ng-template #hero let-hero let-i="index" let-odd="isOdd">
  <div [class]="{'odd-row': odd}">{{i}}:{{hero.name}}</div>
</ng-template>


템플릿 입력 변수의 참조 범위는 반복되는 템플릿 인스턴스 안쪽으로 제한된다. 그래서 구조 디렉티브가 다르다면 같은 변수 이름을 여러 번 사용할 수도 있다.


템플릿 변수는 템플릿 입력 변수와 다르게 # 접두사를 붙여서 #var와 같이 선언한다. 그리고 템플릿 변수는 템플릿 변수가 지정된 엘리먼트나 컴포넌트, 디렉티브를 가리킨다.


템플릿 입력 변수와 템플릿 변수는 서로 다른 네임스페이스에 선언된다. 그래서 let hero로 선언한 템플릿 입력 변수 hero#hero로 선언한 템플릿 변수 hero와 겹치지 않는다.


References