Angular 템플릿에서는 템플릿 지역 변수와 템플릿 참조 변수를 선언해서 활용할 수 있습니다.
참고: 이 문서에서 "템플릿" 이라고 하는 것은 HTML 템플릿 파일을 의미하지 않습니다. 템플릿 구조나 파일 안에 있는 템플릿 조각도 언급하는 것입니다.
템플릿 지역 변수(Local template variables): @let
Angular가 제공하는 @let 문법을 사용하면 템플릿에서 지역 변수를 선언해서 재사용할 수 있습니다.
이 방식은 JavaScript let 문법과 비슷합니다.
@let 활용하기
템플릿 지역 변수를 선언하려면 @let을 사용하면 되고, 템플릿 지역 변수의 값은 표현식의 실행 결과로 할당됩니다.
이 값은 바인딩과 비슷하게 Angular가 값이 변경되는 것을 추적합니다.
@let name = user.name;
@let greeting = 'Hello, ' + name;
@let data = data$ | async;
@let pi = 3.14159;
@let coordinates = {x: 50, y: 100};
@let longExpression =
'Lorem ipsum dolor sit amet, consectetur adipiscing elit ' +
'sed do eiusmod tempor incididunt ut labore et dolore magna ' +
'Ut enim ad minim veniam...';
@let 블록은 각각 변수 하나를 선언합니다.
쉼표(,)를 사용하는 것처럼 블록 하나에서 변수 여러 개를 한 번에 선언할 수는 없습니다.
@let으로 값 참조하기
@let으로 변수를 선언하고 나면, 해당 템플릿 안에서는 지역 변수를 자유롭게 사용할 수 있습니다:
@let user = user$ | async;
@if (user) {
<h1>Hello, {{ user.name }}</h1>
<user-avatar [photo]="user.photo" />
<ul>
@for (snack of user.favoriteSnacks; track snack.id) {
<li>{{ snack.name }}</li>
}
</ul>
<button (click)="update(user)">Update profile</button>
}
재할당 불가
Angular 템플릿이 제공하는 @let은 JavaScript let과 다르게 한 번 선언한 뒤에 값을 재할당할 수 없습니다.
Angular에서는 값을 재할당하지 않아도 표현식의 결과에 따라 가장 최근에 계산된 값이 자동으로 갱신됩니다.
@let value = 1;
<!-- 잘못된 문법 - 동작하지 않습니다! -->
<button (click)="value = value + 1">Increment the value</button>
접근 범위
@let 으로 선언한 변수는 해당 뷰와 해당 뷰의 자식 뷰에서 접근할 수 있습니다.
Angular는 컴포넌트 경계와 흐름 제어 블록, @defer 블록, 구조 디렉티브 등 템플릿에 동적 컨텐츠가 위치할 수 있는 곳에 새 뷰를 생성합니다.
하지만 @let으로 선언한 변수는 호이스팅(hoisting)되지 않기 때문에 부모 뷰나 이웃 뷰에서는 참조할 수 없습니다:
@let topLevel = value;
<div>
@let insideDiv = value;
</div>
<!-- 유효함 -->
{{ topLevel }}
<!-- 유효함 -->
{{ insideDiv }}
@if (condition) {
<!-- 유효함 -->
{{ topLevel + insideDiv }}
@let nested = value;
@if (condition) {
<!-- Valid -->
{{ topLevel + insideDiv + nested }}
}
}
<!-- 오류, @if 밖으로 호이스팅되지 않습니다. -->
{{ nested }}
전체 문법
@let 문법을 자세하게 살펴보면 이렇습니다:
@let키워드- 뒤에 줄바꿈을 제외하고 하나 이상 공백 문자가 옵니다.
- 뒤에 JavaScript 문법으로 유효한 이름이 오고 공백 문자가 올 수 있습니다.
- 뒤에
=심볼이 오고, 공백 문자가 올 수 있습니다. - 뒤에 Angular 표현식이 오며, 이 표현식은 여러 줄일 수 있습니다.
;심볼로 종료됩니다.
템플릿 참조 변수(Template reference variables)
템플릿 참조 변수를 활용하면 템플릿에 있는 엘리먼트를 참조할 수 있습니다.
템플릿 참조 변수는 이런 것들을 가리킬 수 있습니다:
- 템플릿에 존재하는 DOM 엘리먼트 (커스텀 엘리먼트를 포함)
- Angular 컴포넌트나 디렉티브
- ng-template을 의미하는 TemplateRef 객체
그래서 템플릿 참조 변수를 활용하면 템플릿 안에 존재하는 어떤 부분을 다른 쪽에서 참조할 수 있습니다.
템플릿 참조 변수 선언하기
템플릿에 있는 엘리먼트에 변수를 선언하려면 변수 이름 앞에 해시 문자(#)를 붙이면 됩니다.
<!-- `taskInput` 이라는 템플릿 참조 변수를 선언합니다. 이 변수는 HTMLInputElement를 가리킵니다. -->
<input #taskInput placeholder="Enter task name" />
템플릿 참조 변수에 값 할당하기
Angular는 변수가 선언된 엘리먼트를 기준으로 템플릿 변수의 값을 할당합니다.
Angular 컴포넌트에 변수를 선언하면 이 변수는 컴포넌트 인스턴스를 가리킵니다.
<!-- 변수 `startDate`는 `MyDatepicker` 인스턴스를 가리킵니다. -->
<my-datepicker #startDate />
그리고 <ng-template> 엘리먼트에 템플릿 참조 변수를 선언하면, 이 변수는 템플릿을 의미하는 TemplateRef 인스턴스를 가리킵니다.
자세한 내용은 구조 디렉티브 문서의 Angular가 별표(asterisk, *)를 활용하는 방법 섹션을 참고하세요.
<!-- 변수 `myFragment`는 템플릿 조각을 의미하는 `TemplateRef` 인스턴스를 가리킵니다. -->
<ng-template #myFragment>
<p>This is a template fragment</p>
</ng-template>
마지막으로 일반 엘리먼트에 템플릿 참조 변수를 선언하면, 이 변수는 HTMLElement 인스턴스를 가리킵니다.
<!-- 변수 `taskInput`는 HTMLInputElement 인스턴스를 가리킵니다. -->
<input #taskInput placeholder="Enter task name" />
Angular 디렉티브 참조하기
Angular 디렉티브는 템플릿에서 참조할 수 있는 이름을 exportAs 프로퍼티로 지정할 수 있습니다:
@Directive({
selector: '[dropZone]',
exportAs: 'dropZone',
})
export class DropZone {
/* ... */
}
그러면 엘리먼트에 템플릿 참조 변수를 선언하면서 exportAs를 사용하면 디렉티브 인스턴스를 할당할 수 있습니다:
<!-- 변수 `firstZone`은 `DropZone` 디렉티브 인스턴스를 가리킵니다. -->
<section dropZone #firstZone="dropZone">...</section>
exportAs를 지정하지 않은 디렉티브는 참조할 수 없습니다.
템플릿 참조 변수로 탐색하기
템플릿 변수는 해당 템플릿 안에서 어떤 값을 참조하는 용도 외에도, 컴포넌트나 디렉티브를 탐색 하기 위해 "마킹" 하는 용도로도 사용됩니다.
템플릿에 있는 엘리먼트를 탐색하려면 해당 엘리먼트에 템플릿 변수를 선언하고, 컴포넌트 클래스 코드에서 템플릿 변수 이름으로 탐색하면 됩니다.
<input #description value="Original description" />
@Component({
/* ... */,
template: `<input #description value="Original description">`,
})
export class AppComponent {
// 템플릿 변수 이름으로 input 엘리먼트를 탐색합니다.
@ViewChild('description') input: ElementRef | undefined;
}
탐색에 대해 자세하게 알아보려면 쿼리 함수로 자식 컴포넌트 참조하기 문서를 참고하세요.
템플릿 변수의 범위
템플릿 변수의 범위는 JavaScript나 TypeScript 코드와 마찬가지로 선언된 범위 안에서만 유효합니다.
이와 비슷하게, @if와 @for 같은 제어 흐름 블록, 구조 디렉티브, <ng-template> 선언은 새로운 중첩 템플릿 범위를 만듭니다. JavaScript의 if와 for가 새로운 렉시컬 범위를 만드는 것과 같습니다.
이렇게 중첩된 템플릿 안에 선언된 변수는 해당 범위 밖에서 참조할 수 없습니다.
참고: 실행 오류를 방지하기 위해 템플릿 변수는 한 번만 선언하세요.
중첩 템플릿 안에 접근하기
내부 템플릿은 이 템플릿의 외부 템플릿 변수에 접근할 수 있습니다.
아래 코드에서 <input> 입력값을 변경하면 Angular가 템플릿 변수 ref1을 즉시 갱신하기 때문에 <span>에 표시되는 값이 변경됩니다.
<input #ref1 type="text" [(ngModel)]="firstExample" />
@if (true) {
<span>Value: {{ ref1.value }}</span>
}
이 경우 @if 블록은 새로운 템플릿 범위를 만들기 때문에, 이 범위에서 보면 ref1 변수는 부모 범위에 존재합니다.
하지만 부모 템플릿 범위에서 자식 템플릿 범위에 있는 변수에는 접근할 수 없습니다:
@if (true) {
<input #ref2 type="text" [(ngModel)]="secondExample" />
}
<span>Value: {{ ref2?.value }}</span>
위 코드에서 ref2는 @if에 의해 자식 템플릿 범위에 선언되었기 때문에, 부모 템플릿에서는 접근할 수 없습니다.