심화 가이드
템플릿

템플릿 조각 만들기: <ng-template>

<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)은 템플릿 조각이 렌더링 되는 곳이 아니라, 템플릿 조각이 선언된 컴포넌트의 컨텍스트와 연결됩니다.

템플릿 조각 참조하기

템플릿 조각을 참조하는 방법은 세 가지 입니다:

위 세 방법 모두 템플릿 조각은 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이 어떻게 사용되는지 알아보려면, 이런 내용을 확인해 보세요: