<ng-template> 엘리먼트는 표준 <template> 엘리먼트와 비슷하게 템플릿 조각(template fragment) 를 선언하는 엘리먼트입니다.
이렇게 정의한 템플릿 조각은 동적으로 렌더링하거나 코드로 렌더링 할 수 있습니다.
템플릿 조각 만들기
템플릿 조각은 컴포넌트 템플릿에서 <ng-template> 엘리먼트를 사용하면 생성할 수 있습니다:
<p>This is a normal element</p><ng-template> <p>This is a template fragment</p></ng-template>
<ng-template> 엘리먼트는 실제로 렌더링되지 않습니다.
이 엘리먼트는 이후에 동적으로 렌더링할 템플릿 조각에 대한 참조로 사용됩니다.
템플릿 조각의 컨텍스트
템플릿 조각에는 바인딩 표현식이 존재할 수 있습니다:
@Component({ /* ... */, template: `<ng-template>You've selected {{count}} items.</ng-template>`,})export class ItemCounter { count: number = 0;}
템플릿 조각에 존재하는 표현식(expressions)이나 실행문(statements)은 템플릿 조각이 렌더링 되는 곳이 아니라, 템플릿 조각이 선언된 컴포넌트의 컨텍스트와 연결됩니다.
템플릿 조각 참조하기
템플릿 조각을 참조하는 방법은 세 가지 입니다:
<ng-template>엘리먼트에 템플릿 참조 변수를 선언하는 방법- 컴포넌트/디렉티브 쿼리로 쿼리하는 방법
<ng-template>엘리먼트를 의존성 관계로 참조하는 방법
위 세 방법 모두 템플릿 조각은 TemplateRef 객체 타입으로 참조합니다.
템플릿 참조 변수로 참조하기
<ng-template> 엘리먼트에 템플릿 참조 변수를 지정하면 템플릿의 다른 영역에서 템플릿 조각을 참조할 수 있습니다:
<p>This is a normal element</p><ng-template #myFragment> <p>This is a template fragment</p></ng-template>
이제 이 컴포넌트의 템플릿에서는 myFragment 변수로 템플릿 조각을 참조할 수 있습니다.
쿼리로 참조하기
컴포넌트/디렉티브 쿼리 API를 사용하면 템플릿 조각을 참조할 수 있습니다.
그래서 템플릿에 템플릿 조각이 하나만 있다면, @ViewChild 쿼리를 사용해서 TemplateRef 객체를 직접 쿼리하면 됩니다:
@Component({ /* ... */, template: ` <p>This is a normal element</p> <ng-template> <p>This is a template fragment</p> </ng-template> `,})export class ComponentWithFragment { @ViewChild(TemplateRef) myFragment: TemplateRef<unknown> | undefined;}
그러면 이제 다른 클래스 멤버처럼 템플릿 조각을 참조할 수 있습니다.
템플릿 조각이 여러 개라면, 각 <ng-template> 엘리먼트에 템플릿 참조 변수를 추가한 후에 쿼리하면서 이름을 지정하면 됩니다:
@Component({ /* ... */, template: ` <p>This is a normal element</p> <ng-template #fragmentOne> <p>This is one template fragment</p> </ng-template> <ng-template #fragmentTwo> <p>This is another template fragment</p> </ng-template> `,})export class ComponentWithFragment { // 이름으로 쿼리하면서 `read` 옵션을 지정해서 이 엘리먼트의 TemplateRef를 참조한다는 것을 지정합니다. @ViewChild('fragmentOne', {read: TemplateRef}) fragmentOne: TemplateRef<unknown> | undefined; @ViewChild('fragmentTwo', {read: TemplateRef}) fragmentTwo: TemplateRef<unknown> | undefined;}
이 경우에도 이전과 마찬가지로, 쿼리로 찾은 템플릿 조각은 다른 클래스 멤버처럼 참조할 수 있습니다.
의존성 관계로 참조하기
<ng-template> 엘리먼트에 디렉티브가 사용되었다면, 이 디렉티브를 활용해서 TemplateRef 객체를 참조할 수 있습니다:
@Directive({ selector: '[myDirective]'})export class MyDirective { private fragment = inject(TemplateRef);}
<ng-template myDirective> <p>This is one template fragment</p></ng-template>
이렇게 참조한 템플릿 조각은 클래스 멤버처럼 참조할 수 있습니다.
템플릿 조각 렌더링하기
템플릿 조각을 TemplateRef 객체 타입으로 참조하고 나면, 이 템플릿 조각은 템플릿에서 NgTemplateOutlet 디렉티브로 렌더링하거나, TypeScript 코드에서 ViewContainerRef로 렌더링 할 수 있습니다.
NgTemplateOutlet 로 렌더링하기
@angular/common 패키지로 제공되는 NgTemplateOutlet 디렉티브는 TemplateRef를 인자로 받아서 디렉티브가 지정된 엘리먼트의 이웃으로 템플릿 조각을 렌더링합니다.
보통은 <ng-container> 엘리먼트에 NgTemplateOutlet 디렉티브를 사용합니다.
먼저, NgTemplateOutlet 심볼을 로드합니다:
import { NgTemplateOutlet } from '@angular/common';
그리고 아래 예제 코드처럼 템플릿 조각을 선언한 후 <ng-container>에 NgTemplateOutlet를 지정해서 렌더링하면 됩니다:
<p>This is a normal element</p><ng-template #myFragment> <p>This is a fragment</p></ng-template><ng-container *ngTemplateOutlet="myFragment"></ng-container>
최종 결과는 이렇게 렌더링 됩니다:
<p>This is a normal element</p><p>This is a fragment</p>
ViewContainerRef 로 렌더링하기
Angular 컴포넌트 트리에서 내용물이 존재할 수 있는 노드를 뷰 컨테이너(view container) 라고 합니다.
그리고 컴포넌트나 디렉티브는 ViewContainerRef 를 의존성으로 주입 받아서 해당 컴포넌트나 디렉티브에 해당하는 뷰 컨테이너를 참조할 수 있습니다.
이렇게 참조한 ViewContainerRef 객체의 createEmbeddedView 메서드를 사용하면 템플릿 조각을 동적으로 렌더링할 수 있으며, 렌더링하는 템플릿 조각은 ViewContainerRef 객체의 이웃으로 DOM에 추가됩니다.
아래 코드는 사용자가 버튼을 클릭하면 템플릿 조각을 찾아서 DOM에 렌더링하는 예제 코드입니다.
@Component({ /* ... */, selector: 'component-with-fragment', template: ` <h2>Component with a fragment</h2> <ng-template #myFragment> <p>This is the fragment</p> </ng-template> <my-outlet [fragment]="myFragment" /> `,})export class ComponentWithFragment { }@Component({ /* ... */, selector: 'my-outlet', template: `<button (click)="showFragment()">Show</button>`,})export class MyOutlet { private viewContainer = inject(ViewContainerRef); fragment = input<TemplateRef<unknown> | undefined>(); showFragment() { if (this.fragment()) { this.viewContainer.createEmbeddedView(this.fragment()); } }}
이렇게 구현하고 사용자가 "Show" 버튼을 클릭하면 DOM은 이렇게 렌더링됩니다:
<component-with-fragment> <h2>Component with a fragment> <my-outlet> <button>Show</button> </my-outlet> <p>This is the fragment</p></component-with-fragment>
템플릿 조각을 렌더링하면서 인자 전달하기
<ng-template>으로 템플릿 조각을 선언할 때, 변수를 함께 선언할 수 있습니다.
그리고 템플릿 조각을 렌더링하면서 이 변수에 해당하는 context 객체를 전달할 수 있습니다.
그러면 컴포넌트 데이터를 컨텍스트 객체로 전달해서 바인딩 표현식이나 실행문에서 활용할 수 있습니다.
컨텍스트 객체에 선언하는 프로퍼티는 어트리뷰트 이름 앞에 let- 접두사를 붙여 선언합니다:
<ng-template let-pizzaTopping="topping"> <p>You selected: {{pizzaTopping}}</p></ng-template>
NgTemplateOutlet 활용하기
컨텍스트 객체는 ngTemplateOutletContext로 바인딩 할 수 있습니다:
<ng-template #myFragment let-pizzaTopping="topping"> <p>You selected: {{pizzaTopping}}</p></ng-template><ng-container [ngTemplateOutlet]="myFragment" [ngTemplateOutletContext]="{topping: 'onion'}"/>
ViewContainerRef 활용하기
그리고 createEmbeddedView 메서드의 두 번째 인자로 컨텍스트 객체를 전달할 수도 있습니다:
this.viewContainer.createEmbeddedView(this.myFragment, {topping: 'onion'});
구조 디렉티브
구조 디렉티브(structural directive) 는:
TemplateRef를 의존성으로 주입받거나,ViewContainerRef를 의존성으로 주입받아TemplateRef를 코드로 렌더링하는 디렉티브입니다.
Angular는 구조 디렉티브를 간편하게 사용할 수 있는 특별한 방법을 제공합니다.
디렉티브를 엘리먼트에 사용할 때 이 디렉티브 셀렉터 앞에 아스테리스크(*) 문자를 추가하면, Angular는 이 엘리먼트 전체를 템플릿 조각으로 다룹니다:
<section *myDirective> <p>This is a fragment</p></section>
이 코드는 아래 코드와 같습니다:
<ng-template myDirective> <section> <p>This is a fragment</p> </section></ng-template>
구조 디렉티브는 일반적으로 템플릿 조각을 조건에 따라 렌더링하거나, 여러번 렌더링하는 방식으로 활용됩니다.
더 자세한 내용은 구조 디렉티브 문서를 참고하세요.
추가 자료
다른 라이브러리에서 ng-template이 어떻게 사용되는지 알아보려면, 이런 내용을 확인해 보세요:
- Angular Material의 탭 - 탭이 활성화되어야 DOM이 렌더링됩니다.
- Angular Material의 표 - 데이터를 다양한 방식으로 렌더링 할 수 있습니다.