심화 가이드
디렉티브

기본 디렉티브

디렉티브(directive)는 Angular 애플리케이션 안에서 엘리먼트에 어떤 동작을 추가하는 클래스입니다.

Angular가 제공하는 기본 디렉티브는 폼, 목록, 스타일 등에 다양하게 활용됩니다.

디렉티브는 두 종류로 구분됩니다:

종류 설명
컴포넌트 템플릿에 사용합니다. 가장 많이 사용되는 디렉티브 종류입니다.
어트리뷰트 디렉티브 엘리먼트, 컴포넌트, 디렉티브의 모습이나 동작을 바꿉니다.

이 문서는 Angular가 제공하는 기분 어트리뷰트 디렉티브를 다룹니다.

기본 어트리뷰트 디렉티브

어트리뷰트는 HTML 엘리먼트, 어트리뷰트, 프로퍼티, 컴포넌트의 동작을 바꿉니다.

어트리뷰트 중에는 이런 것들을 자주 사용합니다:

디렉티브 설명
NgClass CSS 클래스를 추가하거나 제거합니다.
NgStyle HTML 스타일을 지정하거나 제거합니다.
NgModel HTML 폼 엘리먼트에 양방향 데이터 바인딩을 연결합니다.

참고: 기본 디렉티브는 public API만 사용합니다. 디렉티브가 제공하지 않는 private API는 사용할 수 없습니다.

CSS 클래스를 지정하거나 제거하기: NgClass

ngClass는 CSS 클래스를 지정하거나 제거할 수 있습니다.

참고: 클래스 하나 만 지정하거나 제거하려면 NgClass 대신 클래스 바인딩를 사용하세요.

컴포넌트에 NgClass 불러오기

NgClass를 사용하려면 먼저 컴포넌트 imports 배열에 이 심볼을 로드해야 합니다.

src/app/app.component.ts (NgClass 불러오기)

      
import {Component, OnInit} from '@angular/core';import {JsonPipe} from '@angular/common';import {NgIf} from '@angular/common';import {NgFor} from '@angular/common';import {NgSwitch, NgSwitchCase, NgSwitchDefault} from '@angular/common';import {NgStyle} from '@angular/common';import {NgClass} from '@angular/common';import {FormsModule} from '@angular/forms';import {Item} from './item';import {ItemDetailComponent} from './item-detail/item-detail.component';import {ItemSwitchComponents} from './item-switch.component';import {StoutItemComponent} from './item-switch.component';@Component({  selector: 'app-root',  templateUrl: './app.component.html',  styleUrls: ['./app.component.css'],  imports: [    NgIf, // <-- import into the component    NgFor, // <-- import into the component    NgStyle, // <-- 컴포넌트에 심볼을 로드합니다    NgSwitch, // <-- import into the component    NgSwitchCase,    NgSwitchDefault,    NgClass, // <-- 컴포넌트에 심볼을 로드합니다    FormsModule, // <--- 컴포넌트에 심볼을 로드합니다    JsonPipe,    ItemDetailComponent,    ItemSwitchComponents,    StoutItemComponent,  ],})export class AppComponent implements OnInit {  canSave = true;  isSpecial = true;  isUnchanged = true;  isActive = true;  nullCustomer: string | null = null;  currentCustomer = {    name: 'Laura',  };  item!: Item; // defined to demonstrate template context precedence  items: Item[] = [];  currentItem!: Item;  // trackBy change counting  itemsNoTrackByCount = 0;  itemsWithTrackByCount = 0;  itemsWithTrackByCountReset = 0;  itemIdIncrement = 1;  currentClasses: Record<string, boolean> = {};  currentStyles: Record<string, string> = {};  ngOnInit() {    this.resetItems();    this.setCurrentClasses();    this.setCurrentStyles();    this.itemsNoTrackByCount = 0;  }  setUppercaseName(name: string) {    this.currentItem.name = name.toUpperCase();  }  setCurrentClasses() {    // CSS 클래스: 컴포넌트 프로퍼티 값에 따라 지정되거나 제거됩니다.    this.currentClasses = {      saveable: this.canSave,      modified: !this.isUnchanged,      special: this.isSpecial,    };  }  setCurrentStyles() {    // CSS 스타일: 컴포넌트 프로퍼티 값에 따라 지정됩니다.    this.currentStyles = {      'font-style': this.canSave ? 'italic' : 'normal',      'font-weight': !this.isUnchanged ? 'bold' : 'normal',      'font-size': this.isSpecial ? '24px' : '12px',    };  }  isActiveToggle() {    this.isActive = !this.isActive;  }  giveNullCustomerValue() {    this.nullCustomer = 'Kelly';  }  resetItems() {    this.items = Item.items.map((item) => item.clone());    this.currentItem = this.items[0];    this.item = this.currentItem;  }  resetList() {    this.resetItems();    this.itemsWithTrackByCountReset = 0;    this.itemsNoTrackByCount = ++this.itemsNoTrackByCount;  }  changeIds() {    this.items.forEach((i) => (i.id += 1 * this.itemIdIncrement));    this.itemsWithTrackByCountReset = -1;    this.itemsNoTrackByCount = ++this.itemsNoTrackByCount;    this.itemsWithTrackByCount = ++this.itemsWithTrackByCount;  }  clearTrackByCounts() {    this.resetItems();    this.itemsNoTrackByCount = 0;    this.itemsWithTrackByCount = 0;    this.itemIdIncrement = 1;  }  trackByItems(index: number, item: Item): number {    return item.id;  }  trackById(index: number, item: any): number {    return item.id;  }  getValue(event: Event): string {    return (event.target as HTMLInputElement).value;  }}

NgClass에 표현식 연결하기

스타일을 지정하려는 엘리먼트에 [ngClass]를 추가하고 등호(=)를 붙인 후 표현식을 작성합니다. 아래 코드처럼 작성하면 app.component.ts 파일의 isSpecial 값이 true로 평가되면 <div>special 클래스가 지정됩니다.

src/app/app.component.html

      
<h1>Built-in Directives</h1><h2>Built-in attribute directives</h2><h3 id="ngModel">NgModel (two-way) Binding</h3><fieldset><h4>NgModel examples</h4>  <p>Current item name: {{ currentItem.name }}</p>  <p>    <label for="without">without NgModel:</label>    <input [value]="currentItem.name" (input)="currentItem.name=getValue($event)" id="without">  </p>  <p>    <label for="example-ngModel">[(ngModel)]:</label>    <input [(ngModel)]="currentItem.name" id="example-ngModel">  </p>  <p>    <label for="example-change">(ngModelChange)="...name=$event":</label>    <input [ngModel]="currentItem.name" (ngModelChange)="currentItem.name=$event" id="example-change">  </p>  <p>    <label for="example-uppercase">(ngModelChange)="setUppercaseName($event)"      <input [ngModel]="currentItem.name" (ngModelChange)="setUppercaseName($event)" id="example-uppercase">    </label>  </p></fieldset><hr><h2 id="ngClass">NgClass Binding</h2><p>currentClasses is {{ currentClasses | json }}</p><div [ngClass]="currentClasses">This div is initially saveable, unchanged, and special.</div><ul>  <li>    <label for="saveable">saveable</label>    <input type="checkbox" [(ngModel)]="canSave" id="saveable">  </li>  <li>    <label for="modified">modified:</label>    <input type="checkbox" [value]="!isUnchanged" (change)="isUnchanged=!isUnchanged" id="modified"></li>  <li>    <label for="special">special: <input type="checkbox" [(ngModel)]="isSpecial" id="special"></label></li></ul><button type="button" (click)="setCurrentClasses()">Refresh currentClasses</button><div [ngClass]="currentClasses">  This div should be {{ canSave ? "": "not"}} saveable,                  {{ isUnchanged ? "unchanged" : "modified" }} and                  {{ isSpecial ? "": "not"}} special after clicking "Refresh".</div><br><br><!-- 프로퍼티 값에 따라 "special" 클래스를 지정하거나 해제합니다. --><div [ngClass]="isSpecial ? 'special' : ''">This div is special</div><div class="helpful study course">Helpful study course</div><div [ngClass]="{'helpful':false, 'study':true, 'course':true}">Study course</div><!-- NgStyle binding --><hr><h3>NgStyle Binding</h3><div [style.font-size]="isSpecial ? 'x-large' : 'smaller'">  This div is x-large or smaller.</div><h4>[ngStyle] binding to currentStyles - CSS property names</h4><p>currentStyles is {{ currentStyles | json }}</p><div [ngStyle]="currentStyles">  This div is initially italic, normal weight, and extra large (24px).</div><br><label for="canSave">italic: <input id="canSave" type="checkbox" [(ngModel)]="canSave"></label> |<label for="isUnchanged">normal: <input id="isUnchanged" type="checkbox" [(ngModel)]="isUnchanged"></label> |<label for="isSpecial">xlarge: <input id="isSpecial" type="checkbox" [(ngModel)]="isSpecial"></label><button type="button" (click)="setCurrentStyles()">Refresh currentStyles</button><br><br><div [ngStyle]="currentStyles">  This div should be {{ canSave ? "italic": "plain"}},                  {{ isUnchanged ? "normal weight" : "bold" }} and,                  {{ isSpecial ? "extra large": "normal size"}} after clicking "Refresh".</div><hr><h2>Built-in structural directives</h2><h3 id="ngIf">NgIf Binding</h3><div>  <p>If isActive is true, app-item-detail will render: </p>  <app-item-detail *ngIf="isActive" [item]="item"></app-item-detail>  <button type="button" (click)="isActiveToggle()">Toggle app-item-detail</button></div><p>If currentCustomer isn't null, say hello to Laura:</p><div *ngIf="currentCustomer">Hello, {{ currentCustomer.name }}</div><p>nullCustomer is null by default. NgIf guards against null. Give it a value to show it:</p><div *ngIf="nullCustomer">Hello, <span>{{ nullCustomer }}</span></div><button type="button" (click)="giveNullCustomerValue()">Give nullCustomer a value</button><h4>NgIf binding with template (no *)</h4><ng-template [ngIf]="currentItem">Add {{ currentItem.name }} with template</ng-template><hr><h4>Show/hide vs. NgIf</h4><!-- isSpecial is true --><div [class.hidden]="!isSpecial">Show with class</div><div [class.hidden]="isSpecial">Hide with class</div><p>ItemDetail is in the DOM but hidden</p><app-item-detail [class.hidden]="isSpecial"></app-item-detail><div [style.display]="isSpecial ? 'block' : 'none'">Show with style</div><div [style.display]="isSpecial ? 'none'  : 'block'">Hide with style</div><hr><h2 id="ngFor">NgFor Binding</h2><div class="box">  <div *ngFor="let item of items">{{ item.name }}</div></div><p>*ngFor with ItemDetailComponent element</p><div class="box">  <app-item-detail *ngFor="let item of items" [item]="item"></app-item-detail></div><h4 id="ngFor-index">*ngFor with index</h4><p>with <em>semi-colon</em> separator</p><div class="box">  <div *ngFor="let item of items; let i=index">{{ i + 1 }} - {{ item.name }}</div></div><p>with <em>comma</em> separator</p><div class="box"> <div *ngFor="let item of items, let i=index">{{ i + 1 }} - {{ item.name }}</div></div><h4 id="ngFor-trackBy">*ngFor trackBy</h4><button type="button" (click)="resetList()">Reset items</button><button type="button" (click)="changeIds()">Change ids</button><button type="button" (click)="clearTrackByCounts()">Clear counts</button><p><em>without</em> trackBy</p><div class="box">  <div #noTrackBy *ngFor="let item of items">({{ item.id }}) {{ item.name }}</div>  <div id="noTrackByCnt" *ngIf="itemsNoTrackByCount" >    Item DOM elements change #{{ itemsNoTrackByCount }} without trackBy  </div></div><p>with trackBy</p><div class="box">  <div #withTrackBy *ngFor="let item of items; trackBy: trackByItems">({{ item.id }}) {{ item.name }}</div>  <div id="withTrackByCnt" *ngIf="itemsWithTrackByCount">    Item DOM elements change #{{ itemsWithTrackByCount }} with trackBy  </div></div><br><br><br><p>with trackBy and <em>semi-colon</em> separator</p><div class="box">  <div *ngFor="let item of items; trackBy: trackByItems">    ({{ item.id }}) {{ item.name }}  </div></div><p>with trackBy and <em>comma</em> separator</p><div class="box">  <div *ngFor="let item of items, trackBy: trackByItems">({{ item.id }}) {{ item.name }}</div></div><p>with trackBy and <em>space</em> separator</p><div class="box">  <div *ngFor="let item of items trackBy: trackByItems">({{ item.id }}) {{ item.name }}</div></div><p>with <em>generic</em> trackById function</p><div class="box">  <div *ngFor="let item of items, trackBy: trackById">({{ item.id }}) {{ item.name }}</div></div><hr><h2>NgSwitch Binding</h2><p>Pick your favorite item</p><div>  <label for="item-{{i}}" *ngFor="let i of items">    <div><input id="item-{{i}}"type="radio" name="items" [(ngModel)]="currentItem" [value]="i">{{ i.name }}    </div>  </label></div><div [ngSwitch]="currentItem.feature">  <app-stout-item    *ngSwitchCase="'stout'"    [item]="currentItem"></app-stout-item>  <app-device-item   *ngSwitchCase="'slim'"     [item]="currentItem"></app-device-item>  <app-lost-item     *ngSwitchCase="'vintage'"  [item]="currentItem"></app-lost-item>  <app-best-item     *ngSwitchCase="'bright'"   [item]="currentItem"></app-best-item>  <div *ngSwitchCase="'bright'">Are you as bright as {{ currentItem.name }}?</div>  <app-unknown-item  *ngSwitchDefault           [item]="currentItem"></app-unknown-item></div>

NgClass와 메서드 사용하기

  1. NgClass를 메서드와 함께 사용하려면 컴포넌트 클래스에 메서드를 정의해야 합니다. 아래 코드를 보면, setCurrentClasses()currentClasses 프로퍼티에 객체를 할당하며, 이 객체는 다른 컴포넌트 프로퍼티 값에 따라 CSS 클래스를 지정하거나 제거합니다.

    객체의 키는 CSS 클래스 이름을 의미합니다. 값이 true이면, ngClass 가 CSS 클래스를 지정합니다. 값이 false이면, ngClass 가 CSS 클래스를 제거합니다.

    src/app/app.component.ts

          
    import {Component, OnInit} from '@angular/core';import {JsonPipe} from '@angular/common';import {NgIf} from '@angular/common';import {NgFor} from '@angular/common';import {NgSwitch, NgSwitchCase, NgSwitchDefault} from '@angular/common';import {NgStyle} from '@angular/common';import {NgClass} from '@angular/common';import {FormsModule} from '@angular/forms';import {Item} from './item';import {ItemDetailComponent} from './item-detail/item-detail.component';import {ItemSwitchComponents} from './item-switch.component';import {StoutItemComponent} from './item-switch.component';@Component({  selector: 'app-root',  templateUrl: './app.component.html',  styleUrls: ['./app.component.css'],  imports: [    NgIf, // <-- import into the component    NgFor, // <-- import into the component    NgStyle, // <-- 컴포넌트에 심볼을 로드합니다    NgSwitch, // <-- import into the component    NgSwitchCase,    NgSwitchDefault,    NgClass, // <-- 컴포넌트에 심볼을 로드합니다    FormsModule, // <--- 컴포넌트에 심볼을 로드합니다    JsonPipe,    ItemDetailComponent,    ItemSwitchComponents,    StoutItemComponent,  ],})export class AppComponent implements OnInit {  canSave = true;  isSpecial = true;  isUnchanged = true;  isActive = true;  nullCustomer: string | null = null;  currentCustomer = {    name: 'Laura',  };  item!: Item; // defined to demonstrate template context precedence  items: Item[] = [];  currentItem!: Item;  // trackBy change counting  itemsNoTrackByCount = 0;  itemsWithTrackByCount = 0;  itemsWithTrackByCountReset = 0;  itemIdIncrement = 1;  currentClasses: Record<string, boolean> = {};  currentStyles: Record<string, string> = {};  ngOnInit() {    this.resetItems();    this.setCurrentClasses();    this.setCurrentStyles();    this.itemsNoTrackByCount = 0;  }  setUppercaseName(name: string) {    this.currentItem.name = name.toUpperCase();  }  setCurrentClasses() {    // CSS 클래스: 컴포넌트 프로퍼티 값에 따라 지정되거나 제거됩니다.    this.currentClasses = {      saveable: this.canSave,      modified: !this.isUnchanged,      special: this.isSpecial,    };  }  setCurrentStyles() {    // CSS 스타일: 컴포넌트 프로퍼티 값에 따라 지정됩니다.    this.currentStyles = {      'font-style': this.canSave ? 'italic' : 'normal',      'font-weight': !this.isUnchanged ? 'bold' : 'normal',      'font-size': this.isSpecial ? '24px' : '12px',    };  }  isActiveToggle() {    this.isActive = !this.isActive;  }  giveNullCustomerValue() {    this.nullCustomer = 'Kelly';  }  resetItems() {    this.items = Item.items.map((item) => item.clone());    this.currentItem = this.items[0];    this.item = this.currentItem;  }  resetList() {    this.resetItems();    this.itemsWithTrackByCountReset = 0;    this.itemsNoTrackByCount = ++this.itemsNoTrackByCount;  }  changeIds() {    this.items.forEach((i) => (i.id += 1 * this.itemIdIncrement));    this.itemsWithTrackByCountReset = -1;    this.itemsNoTrackByCount = ++this.itemsNoTrackByCount;    this.itemsWithTrackByCount = ++this.itemsWithTrackByCount;  }  clearTrackByCounts() {    this.resetItems();    this.itemsNoTrackByCount = 0;    this.itemsWithTrackByCount = 0;    this.itemIdIncrement = 1;  }  trackByItems(index: number, item: Item): number {    return item.id;  }  trackById(index: number, item: any): number {    return item.id;  }  getValue(event: Event): string {    return (event.target as HTMLInputElement).value;  }}
  2. 템플릿에서 CSS 클래스를 지정하려는 엘리먼트에 ngClasscurrentClasses 프로퍼티와 바인딩 합니다:

    src/app/app.component.html

          
    <h1>Built-in Directives</h1><h2>Built-in attribute directives</h2><h3 id="ngModel">NgModel (two-way) Binding</h3><fieldset><h4>NgModel examples</h4>  <p>Current item name: {{ currentItem.name }}</p>  <p>    <label for="without">without NgModel:</label>    <input [value]="currentItem.name" (input)="currentItem.name=getValue($event)" id="without">  </p>  <p>    <label for="example-ngModel">[(ngModel)]:</label>    <input [(ngModel)]="currentItem.name" id="example-ngModel">  </p>  <p>    <label for="example-change">(ngModelChange)="...name=$event":</label>    <input [ngModel]="currentItem.name" (ngModelChange)="currentItem.name=$event" id="example-change">  </p>  <p>    <label for="example-uppercase">(ngModelChange)="setUppercaseName($event)"      <input [ngModel]="currentItem.name" (ngModelChange)="setUppercaseName($event)" id="example-uppercase">    </label>  </p></fieldset><hr><h2 id="ngClass">NgClass Binding</h2><p>currentClasses is {{ currentClasses | json }}</p><div [ngClass]="currentClasses">This div is initially saveable, unchanged, and special.</div><ul>  <li>    <label for="saveable">saveable</label>    <input type="checkbox" [(ngModel)]="canSave" id="saveable">  </li>  <li>    <label for="modified">modified:</label>    <input type="checkbox" [value]="!isUnchanged" (change)="isUnchanged=!isUnchanged" id="modified"></li>  <li>    <label for="special">special: <input type="checkbox" [(ngModel)]="isSpecial" id="special"></label></li></ul><button type="button" (click)="setCurrentClasses()">Refresh currentClasses</button><div [ngClass]="currentClasses">  This div should be {{ canSave ? "": "not"}} saveable,                  {{ isUnchanged ? "unchanged" : "modified" }} and                  {{ isSpecial ? "": "not"}} special after clicking "Refresh".</div><br><br><!-- 프로퍼티 값에 따라 "special" 클래스를 지정하거나 해제합니다. --><div [ngClass]="isSpecial ? 'special' : ''">This div is special</div><div class="helpful study course">Helpful study course</div><div [ngClass]="{'helpful':false, 'study':true, 'course':true}">Study course</div><!-- NgStyle binding --><hr><h3>NgStyle Binding</h3><div [style.font-size]="isSpecial ? 'x-large' : 'smaller'">  This div is x-large or smaller.</div><h4>[ngStyle] binding to currentStyles - CSS property names</h4><p>currentStyles is {{ currentStyles | json }}</p><div [ngStyle]="currentStyles">  This div is initially italic, normal weight, and extra large (24px).</div><br><label for="canSave">italic: <input id="canSave" type="checkbox" [(ngModel)]="canSave"></label> |<label for="isUnchanged">normal: <input id="isUnchanged" type="checkbox" [(ngModel)]="isUnchanged"></label> |<label for="isSpecial">xlarge: <input id="isSpecial" type="checkbox" [(ngModel)]="isSpecial"></label><button type="button" (click)="setCurrentStyles()">Refresh currentStyles</button><br><br><div [ngStyle]="currentStyles">  This div should be {{ canSave ? "italic": "plain"}},                  {{ isUnchanged ? "normal weight" : "bold" }} and,                  {{ isSpecial ? "extra large": "normal size"}} after clicking "Refresh".</div><hr><h2>Built-in structural directives</h2><h3 id="ngIf">NgIf Binding</h3><div>  <p>If isActive is true, app-item-detail will render: </p>  <app-item-detail *ngIf="isActive" [item]="item"></app-item-detail>  <button type="button" (click)="isActiveToggle()">Toggle app-item-detail</button></div><p>If currentCustomer isn't null, say hello to Laura:</p><div *ngIf="currentCustomer">Hello, {{ currentCustomer.name }}</div><p>nullCustomer is null by default. NgIf guards against null. Give it a value to show it:</p><div *ngIf="nullCustomer">Hello, <span>{{ nullCustomer }}</span></div><button type="button" (click)="giveNullCustomerValue()">Give nullCustomer a value</button><h4>NgIf binding with template (no *)</h4><ng-template [ngIf]="currentItem">Add {{ currentItem.name }} with template</ng-template><hr><h4>Show/hide vs. NgIf</h4><!-- isSpecial is true --><div [class.hidden]="!isSpecial">Show with class</div><div [class.hidden]="isSpecial">Hide with class</div><p>ItemDetail is in the DOM but hidden</p><app-item-detail [class.hidden]="isSpecial"></app-item-detail><div [style.display]="isSpecial ? 'block' : 'none'">Show with style</div><div [style.display]="isSpecial ? 'none'  : 'block'">Hide with style</div><hr><h2 id="ngFor">NgFor Binding</h2><div class="box">  <div *ngFor="let item of items">{{ item.name }}</div></div><p>*ngFor with ItemDetailComponent element</p><div class="box">  <app-item-detail *ngFor="let item of items" [item]="item"></app-item-detail></div><h4 id="ngFor-index">*ngFor with index</h4><p>with <em>semi-colon</em> separator</p><div class="box">  <div *ngFor="let item of items; let i=index">{{ i + 1 }} - {{ item.name }}</div></div><p>with <em>comma</em> separator</p><div class="box"> <div *ngFor="let item of items, let i=index">{{ i + 1 }} - {{ item.name }}</div></div><h4 id="ngFor-trackBy">*ngFor trackBy</h4><button type="button" (click)="resetList()">Reset items</button><button type="button" (click)="changeIds()">Change ids</button><button type="button" (click)="clearTrackByCounts()">Clear counts</button><p><em>without</em> trackBy</p><div class="box">  <div #noTrackBy *ngFor="let item of items">({{ item.id }}) {{ item.name }}</div>  <div id="noTrackByCnt" *ngIf="itemsNoTrackByCount" >    Item DOM elements change #{{ itemsNoTrackByCount }} without trackBy  </div></div><p>with trackBy</p><div class="box">  <div #withTrackBy *ngFor="let item of items; trackBy: trackByItems">({{ item.id }}) {{ item.name }}</div>  <div id="withTrackByCnt" *ngIf="itemsWithTrackByCount">    Item DOM elements change #{{ itemsWithTrackByCount }} with trackBy  </div></div><br><br><br><p>with trackBy and <em>semi-colon</em> separator</p><div class="box">  <div *ngFor="let item of items; trackBy: trackByItems">    ({{ item.id }}) {{ item.name }}  </div></div><p>with trackBy and <em>comma</em> separator</p><div class="box">  <div *ngFor="let item of items, trackBy: trackByItems">({{ item.id }}) {{ item.name }}</div></div><p>with trackBy and <em>space</em> separator</p><div class="box">  <div *ngFor="let item of items trackBy: trackByItems">({{ item.id }}) {{ item.name }}</div></div><p>with <em>generic</em> trackById function</p><div class="box">  <div *ngFor="let item of items, trackBy: trackById">({{ item.id }}) {{ item.name }}</div></div><hr><h2>NgSwitch Binding</h2><p>Pick your favorite item</p><div>  <label for="item-{{i}}" *ngFor="let i of items">    <div><input id="item-{{i}}"type="radio" name="items" [(ngModel)]="currentItem" [value]="i">{{ i.name }}    </div>  </label></div><div [ngSwitch]="currentItem.feature">  <app-stout-item    *ngSwitchCase="'stout'"    [item]="currentItem"></app-stout-item>  <app-device-item   *ngSwitchCase="'slim'"     [item]="currentItem"></app-device-item>  <app-lost-item     *ngSwitchCase="'vintage'"  [item]="currentItem"></app-lost-item>  <app-best-item     *ngSwitchCase="'bright'"   [item]="currentItem"></app-best-item>  <div *ngSwitchCase="'bright'">Are you as bright as {{ currentItem.name }}?</div>  <app-unknown-item  *ngSwitchDefault           [item]="currentItem"></app-unknown-item></div>

이렇게 구현하면 Angular는 currentClasses 객체를 처음 생성할 때부터 이 객체 내부의 값이 변할 때마다 새로운 객체를 재할당합니다. 그리고 전체 예제 코드에서는 사용자가 Refresh currentClasses 버튼을 클릭하면 ngOnInit()가 실행될 때와 동일하게 setCurrentClasses()를 실행합니다.

인라인 스타일 지정하기: NgStyle

참고: 스타일 하나 를 지정하거나 제거하려면 NgStyle을 사용하지 말고 스타일 바인딩을 사용하세요.

컴포넌트에 NgStyle 불러오기

NgStyle을 사용하려면 먼저 컴포넌트 imports 배열에 이 심볼을 로드해야 합니다.

src/app/app.component.ts (NgStyle 불러오기)

      
import {Component, OnInit} from '@angular/core';import {JsonPipe} from '@angular/common';import {NgIf} from '@angular/common';import {NgFor} from '@angular/common';import {NgSwitch, NgSwitchCase, NgSwitchDefault} from '@angular/common';import {NgStyle} from '@angular/common';import {NgClass} from '@angular/common';import {FormsModule} from '@angular/forms';import {Item} from './item';import {ItemDetailComponent} from './item-detail/item-detail.component';import {ItemSwitchComponents} from './item-switch.component';import {StoutItemComponent} from './item-switch.component';@Component({  selector: 'app-root',  templateUrl: './app.component.html',  styleUrls: ['./app.component.css'],  imports: [    NgIf, // <-- import into the component    NgFor, // <-- import into the component    NgStyle, // <-- 컴포넌트에 심볼을 로드합니다    NgSwitch, // <-- import into the component    NgSwitchCase,    NgSwitchDefault,    NgClass, // <-- 컴포넌트에 심볼을 로드합니다    FormsModule, // <--- 컴포넌트에 심볼을 로드합니다    JsonPipe,    ItemDetailComponent,    ItemSwitchComponents,    StoutItemComponent,  ],})export class AppComponent implements OnInit {  canSave = true;  isSpecial = true;  isUnchanged = true;  isActive = true;  nullCustomer: string | null = null;  currentCustomer = {    name: 'Laura',  };  item!: Item; // defined to demonstrate template context precedence  items: Item[] = [];  currentItem!: Item;  // trackBy change counting  itemsNoTrackByCount = 0;  itemsWithTrackByCount = 0;  itemsWithTrackByCountReset = 0;  itemIdIncrement = 1;  currentClasses: Record<string, boolean> = {};  currentStyles: Record<string, string> = {};  ngOnInit() {    this.resetItems();    this.setCurrentClasses();    this.setCurrentStyles();    this.itemsNoTrackByCount = 0;  }  setUppercaseName(name: string) {    this.currentItem.name = name.toUpperCase();  }  setCurrentClasses() {    // CSS 클래스: 컴포넌트 프로퍼티 값에 따라 지정되거나 제거됩니다.    this.currentClasses = {      saveable: this.canSave,      modified: !this.isUnchanged,      special: this.isSpecial,    };  }  setCurrentStyles() {    // CSS 스타일: 컴포넌트 프로퍼티 값에 따라 지정됩니다.    this.currentStyles = {      'font-style': this.canSave ? 'italic' : 'normal',      'font-weight': !this.isUnchanged ? 'bold' : 'normal',      'font-size': this.isSpecial ? '24px' : '12px',    };  }  isActiveToggle() {    this.isActive = !this.isActive;  }  giveNullCustomerValue() {    this.nullCustomer = 'Kelly';  }  resetItems() {    this.items = Item.items.map((item) => item.clone());    this.currentItem = this.items[0];    this.item = this.currentItem;  }  resetList() {    this.resetItems();    this.itemsWithTrackByCountReset = 0;    this.itemsNoTrackByCount = ++this.itemsNoTrackByCount;  }  changeIds() {    this.items.forEach((i) => (i.id += 1 * this.itemIdIncrement));    this.itemsWithTrackByCountReset = -1;    this.itemsNoTrackByCount = ++this.itemsNoTrackByCount;    this.itemsWithTrackByCount = ++this.itemsWithTrackByCount;  }  clearTrackByCounts() {    this.resetItems();    this.itemsNoTrackByCount = 0;    this.itemsWithTrackByCount = 0;    this.itemIdIncrement = 1;  }  trackByItems(index: number, item: Item): number {    return item.id;  }  trackById(index: number, item: any): number {    return item.id;  }  getValue(event: Event): string {    return (event.target as HTMLInputElement).value;  }}

NgStyle을 사용하면 컴포넌트 상태에 따라 인라인 스타일 여러개를 동시에 지정할 수 있습니다.

  1. NgStyle을 사용하려면, 컴포넌트 클래스에 메서드를 추가합니다.

    아래 예제를 보면 setCurrentStyles()는 컴포넌트 프로퍼티 상태에 따라 스타일을 지정하는 객체를 선언해서 currentStyles 프로퍼티에 할당합니다.

    src/app/app.component.ts

          
    import {Component, OnInit} from '@angular/core';import {JsonPipe} from '@angular/common';import {NgIf} from '@angular/common';import {NgFor} from '@angular/common';import {NgSwitch, NgSwitchCase, NgSwitchDefault} from '@angular/common';import {NgStyle} from '@angular/common';import {NgClass} from '@angular/common';import {FormsModule} from '@angular/forms';import {Item} from './item';import {ItemDetailComponent} from './item-detail/item-detail.component';import {ItemSwitchComponents} from './item-switch.component';import {StoutItemComponent} from './item-switch.component';@Component({  selector: 'app-root',  templateUrl: './app.component.html',  styleUrls: ['./app.component.css'],  imports: [    NgIf, // <-- import into the component    NgFor, // <-- import into the component    NgStyle, // <-- 컴포넌트에 심볼을 로드합니다    NgSwitch, // <-- import into the component    NgSwitchCase,    NgSwitchDefault,    NgClass, // <-- 컴포넌트에 심볼을 로드합니다    FormsModule, // <--- 컴포넌트에 심볼을 로드합니다    JsonPipe,    ItemDetailComponent,    ItemSwitchComponents,    StoutItemComponent,  ],})export class AppComponent implements OnInit {  canSave = true;  isSpecial = true;  isUnchanged = true;  isActive = true;  nullCustomer: string | null = null;  currentCustomer = {    name: 'Laura',  };  item!: Item; // defined to demonstrate template context precedence  items: Item[] = [];  currentItem!: Item;  // trackBy change counting  itemsNoTrackByCount = 0;  itemsWithTrackByCount = 0;  itemsWithTrackByCountReset = 0;  itemIdIncrement = 1;  currentClasses: Record<string, boolean> = {};  currentStyles: Record<string, string> = {};  ngOnInit() {    this.resetItems();    this.setCurrentClasses();    this.setCurrentStyles();    this.itemsNoTrackByCount = 0;  }  setUppercaseName(name: string) {    this.currentItem.name = name.toUpperCase();  }  setCurrentClasses() {    // CSS 클래스: 컴포넌트 프로퍼티 값에 따라 지정되거나 제거됩니다.    this.currentClasses = {      saveable: this.canSave,      modified: !this.isUnchanged,      special: this.isSpecial,    };  }  setCurrentStyles() {    // CSS 스타일: 컴포넌트 프로퍼티 값에 따라 지정됩니다.    this.currentStyles = {      'font-style': this.canSave ? 'italic' : 'normal',      'font-weight': !this.isUnchanged ? 'bold' : 'normal',      'font-size': this.isSpecial ? '24px' : '12px',    };  }  isActiveToggle() {    this.isActive = !this.isActive;  }  giveNullCustomerValue() {    this.nullCustomer = 'Kelly';  }  resetItems() {    this.items = Item.items.map((item) => item.clone());    this.currentItem = this.items[0];    this.item = this.currentItem;  }  resetList() {    this.resetItems();    this.itemsWithTrackByCountReset = 0;    this.itemsNoTrackByCount = ++this.itemsNoTrackByCount;  }  changeIds() {    this.items.forEach((i) => (i.id += 1 * this.itemIdIncrement));    this.itemsWithTrackByCountReset = -1;    this.itemsNoTrackByCount = ++this.itemsNoTrackByCount;    this.itemsWithTrackByCount = ++this.itemsWithTrackByCount;  }  clearTrackByCounts() {    this.resetItems();    this.itemsNoTrackByCount = 0;    this.itemsWithTrackByCount = 0;    this.itemIdIncrement = 1;  }  trackByItems(index: number, item: Item): number {    return item.id;  }  trackById(index: number, item: any): number {    return item.id;  }  getValue(event: Event): string {    return (event.target as HTMLInputElement).value;  }}
  2. 엘리먼트에 스타일을 지정하려면 ngStyle을 사용해서 currentStyles와 프로퍼티 바인딩하면 됩니다.

    src/app/app.component.html

          
    <h1>Built-in Directives</h1><h2>Built-in attribute directives</h2><h3 id="ngModel">NgModel (two-way) Binding</h3><fieldset><h4>NgModel examples</h4>  <p>Current item name: {{ currentItem.name }}</p>  <p>    <label for="without">without NgModel:</label>    <input [value]="currentItem.name" (input)="currentItem.name=getValue($event)" id="without">  </p>  <p>    <label for="example-ngModel">[(ngModel)]:</label>    <input [(ngModel)]="currentItem.name" id="example-ngModel">  </p>  <p>    <label for="example-change">(ngModelChange)="...name=$event":</label>    <input [ngModel]="currentItem.name" (ngModelChange)="currentItem.name=$event" id="example-change">  </p>  <p>    <label for="example-uppercase">(ngModelChange)="setUppercaseName($event)"      <input [ngModel]="currentItem.name" (ngModelChange)="setUppercaseName($event)" id="example-uppercase">    </label>  </p></fieldset><hr><h2 id="ngClass">NgClass Binding</h2><p>currentClasses is {{ currentClasses | json }}</p><div [ngClass]="currentClasses">This div is initially saveable, unchanged, and special.</div><ul>  <li>    <label for="saveable">saveable</label>    <input type="checkbox" [(ngModel)]="canSave" id="saveable">  </li>  <li>    <label for="modified">modified:</label>    <input type="checkbox" [value]="!isUnchanged" (change)="isUnchanged=!isUnchanged" id="modified"></li>  <li>    <label for="special">special: <input type="checkbox" [(ngModel)]="isSpecial" id="special"></label></li></ul><button type="button" (click)="setCurrentClasses()">Refresh currentClasses</button><div [ngClass]="currentClasses">  This div should be {{ canSave ? "": "not"}} saveable,                  {{ isUnchanged ? "unchanged" : "modified" }} and                  {{ isSpecial ? "": "not"}} special after clicking "Refresh".</div><br><br><!-- 프로퍼티 값에 따라 "special" 클래스를 지정하거나 해제합니다. --><div [ngClass]="isSpecial ? 'special' : ''">This div is special</div><div class="helpful study course">Helpful study course</div><div [ngClass]="{'helpful':false, 'study':true, 'course':true}">Study course</div><!-- NgStyle binding --><hr><h3>NgStyle Binding</h3><div [style.font-size]="isSpecial ? 'x-large' : 'smaller'">  This div is x-large or smaller.</div><h4>[ngStyle] binding to currentStyles - CSS property names</h4><p>currentStyles is {{ currentStyles | json }}</p><div [ngStyle]="currentStyles">  This div is initially italic, normal weight, and extra large (24px).</div><br><label for="canSave">italic: <input id="canSave" type="checkbox" [(ngModel)]="canSave"></label> |<label for="isUnchanged">normal: <input id="isUnchanged" type="checkbox" [(ngModel)]="isUnchanged"></label> |<label for="isSpecial">xlarge: <input id="isSpecial" type="checkbox" [(ngModel)]="isSpecial"></label><button type="button" (click)="setCurrentStyles()">Refresh currentStyles</button><br><br><div [ngStyle]="currentStyles">  This div should be {{ canSave ? "italic": "plain"}},                  {{ isUnchanged ? "normal weight" : "bold" }} and,                  {{ isSpecial ? "extra large": "normal size"}} after clicking "Refresh".</div><hr><h2>Built-in structural directives</h2><h3 id="ngIf">NgIf Binding</h3><div>  <p>If isActive is true, app-item-detail will render: </p>  <app-item-detail *ngIf="isActive" [item]="item"></app-item-detail>  <button type="button" (click)="isActiveToggle()">Toggle app-item-detail</button></div><p>If currentCustomer isn't null, say hello to Laura:</p><div *ngIf="currentCustomer">Hello, {{ currentCustomer.name }}</div><p>nullCustomer is null by default. NgIf guards against null. Give it a value to show it:</p><div *ngIf="nullCustomer">Hello, <span>{{ nullCustomer }}</span></div><button type="button" (click)="giveNullCustomerValue()">Give nullCustomer a value</button><h4>NgIf binding with template (no *)</h4><ng-template [ngIf]="currentItem">Add {{ currentItem.name }} with template</ng-template><hr><h4>Show/hide vs. NgIf</h4><!-- isSpecial is true --><div [class.hidden]="!isSpecial">Show with class</div><div [class.hidden]="isSpecial">Hide with class</div><p>ItemDetail is in the DOM but hidden</p><app-item-detail [class.hidden]="isSpecial"></app-item-detail><div [style.display]="isSpecial ? 'block' : 'none'">Show with style</div><div [style.display]="isSpecial ? 'none'  : 'block'">Hide with style</div><hr><h2 id="ngFor">NgFor Binding</h2><div class="box">  <div *ngFor="let item of items">{{ item.name }}</div></div><p>*ngFor with ItemDetailComponent element</p><div class="box">  <app-item-detail *ngFor="let item of items" [item]="item"></app-item-detail></div><h4 id="ngFor-index">*ngFor with index</h4><p>with <em>semi-colon</em> separator</p><div class="box">  <div *ngFor="let item of items; let i=index">{{ i + 1 }} - {{ item.name }}</div></div><p>with <em>comma</em> separator</p><div class="box"> <div *ngFor="let item of items, let i=index">{{ i + 1 }} - {{ item.name }}</div></div><h4 id="ngFor-trackBy">*ngFor trackBy</h4><button type="button" (click)="resetList()">Reset items</button><button type="button" (click)="changeIds()">Change ids</button><button type="button" (click)="clearTrackByCounts()">Clear counts</button><p><em>without</em> trackBy</p><div class="box">  <div #noTrackBy *ngFor="let item of items">({{ item.id }}) {{ item.name }}</div>  <div id="noTrackByCnt" *ngIf="itemsNoTrackByCount" >    Item DOM elements change #{{ itemsNoTrackByCount }} without trackBy  </div></div><p>with trackBy</p><div class="box">  <div #withTrackBy *ngFor="let item of items; trackBy: trackByItems">({{ item.id }}) {{ item.name }}</div>  <div id="withTrackByCnt" *ngIf="itemsWithTrackByCount">    Item DOM elements change #{{ itemsWithTrackByCount }} with trackBy  </div></div><br><br><br><p>with trackBy and <em>semi-colon</em> separator</p><div class="box">  <div *ngFor="let item of items; trackBy: trackByItems">    ({{ item.id }}) {{ item.name }}  </div></div><p>with trackBy and <em>comma</em> separator</p><div class="box">  <div *ngFor="let item of items, trackBy: trackByItems">({{ item.id }}) {{ item.name }}</div></div><p>with trackBy and <em>space</em> separator</p><div class="box">  <div *ngFor="let item of items trackBy: trackByItems">({{ item.id }}) {{ item.name }}</div></div><p>with <em>generic</em> trackById function</p><div class="box">  <div *ngFor="let item of items, trackBy: trackById">({{ item.id }}) {{ item.name }}</div></div><hr><h2>NgSwitch Binding</h2><p>Pick your favorite item</p><div>  <label for="item-{{i}}" *ngFor="let i of items">    <div><input id="item-{{i}}"type="radio" name="items" [(ngModel)]="currentItem" [value]="i">{{ i.name }}    </div>  </label></div><div [ngSwitch]="currentItem.feature">  <app-stout-item    *ngSwitchCase="'stout'"    [item]="currentItem"></app-stout-item>  <app-device-item   *ngSwitchCase="'slim'"     [item]="currentItem"></app-device-item>  <app-lost-item     *ngSwitchCase="'vintage'"  [item]="currentItem"></app-lost-item>  <app-best-item     *ngSwitchCase="'bright'"   [item]="currentItem"></app-best-item>  <div *ngSwitchCase="'bright'">Are you as bright as {{ currentItem.name }}?</div>  <app-unknown-item  *ngSwitchDefault           [item]="currentItem"></app-unknown-item></div>

이렇게 구현하면 Angular는 프로퍼티 값이 변경될 때마다 엘리먼트에 변경된 스타일을 지정합니다. 그리고 전체 예제 코드에서는 사용자가 버튼을 클릭하면 ngOnInit()가 실행될 때와 동일하게 setCurrentStyles()를 실행합니다.

프로퍼티 화면에 표시하기, 값 반영하기: ngModel

NgModel 디렉티브를 사용하면 데이터 프로퍼티를 화면에 표시하거나, 사용자가 변경한 값을 프로퍼티에 반영할 수 있습니다.

  1. FormsModule을 불러와서 AppComponent imports 목록에 추가합니다.

    src/app/app.component.ts (FormsModule 불러오기)

          
    import {Component, OnInit} from '@angular/core';import {JsonPipe} from '@angular/common';import {NgIf} from '@angular/common';import {NgFor} from '@angular/common';import {NgSwitch, NgSwitchCase, NgSwitchDefault} from '@angular/common';import {NgStyle} from '@angular/common';import {NgClass} from '@angular/common';import {FormsModule} from '@angular/forms';import {Item} from './item';import {ItemDetailComponent} from './item-detail/item-detail.component';import {ItemSwitchComponents} from './item-switch.component';import {StoutItemComponent} from './item-switch.component';@Component({  selector: 'app-root',  templateUrl: './app.component.html',  styleUrls: ['./app.component.css'],  imports: [    NgIf, // <-- import into the component    NgFor, // <-- import into the component    NgStyle, // <-- 컴포넌트에 심볼을 로드합니다    NgSwitch, // <-- import into the component    NgSwitchCase,    NgSwitchDefault,    NgClass, // <-- 컴포넌트에 심볼을 로드합니다    FormsModule, // <--- 컴포넌트에 심볼을 로드합니다    JsonPipe,    ItemDetailComponent,    ItemSwitchComponents,    StoutItemComponent,  ],})export class AppComponent implements OnInit {  canSave = true;  isSpecial = true;  isUnchanged = true;  isActive = true;  nullCustomer: string | null = null;  currentCustomer = {    name: 'Laura',  };  item!: Item; // defined to demonstrate template context precedence  items: Item[] = [];  currentItem!: Item;  // trackBy change counting  itemsNoTrackByCount = 0;  itemsWithTrackByCount = 0;  itemsWithTrackByCountReset = 0;  itemIdIncrement = 1;  currentClasses: Record<string, boolean> = {};  currentStyles: Record<string, string> = {};  ngOnInit() {    this.resetItems();    this.setCurrentClasses();    this.setCurrentStyles();    this.itemsNoTrackByCount = 0;  }  setUppercaseName(name: string) {    this.currentItem.name = name.toUpperCase();  }  setCurrentClasses() {    // CSS 클래스: 컴포넌트 프로퍼티 값에 따라 지정되거나 제거됩니다.    this.currentClasses = {      saveable: this.canSave,      modified: !this.isUnchanged,      special: this.isSpecial,    };  }  setCurrentStyles() {    // CSS 스타일: 컴포넌트 프로퍼티 값에 따라 지정됩니다.    this.currentStyles = {      'font-style': this.canSave ? 'italic' : 'normal',      'font-weight': !this.isUnchanged ? 'bold' : 'normal',      'font-size': this.isSpecial ? '24px' : '12px',    };  }  isActiveToggle() {    this.isActive = !this.isActive;  }  giveNullCustomerValue() {    this.nullCustomer = 'Kelly';  }  resetItems() {    this.items = Item.items.map((item) => item.clone());    this.currentItem = this.items[0];    this.item = this.currentItem;  }  resetList() {    this.resetItems();    this.itemsWithTrackByCountReset = 0;    this.itemsNoTrackByCount = ++this.itemsNoTrackByCount;  }  changeIds() {    this.items.forEach((i) => (i.id += 1 * this.itemIdIncrement));    this.itemsWithTrackByCountReset = -1;    this.itemsNoTrackByCount = ++this.itemsNoTrackByCount;    this.itemsWithTrackByCount = ++this.itemsWithTrackByCount;  }  clearTrackByCounts() {    this.resetItems();    this.itemsNoTrackByCount = 0;    this.itemsWithTrackByCount = 0;    this.itemIdIncrement = 1;  }  trackByItems(index: number, item: Item): number {    return item.id;  }  trackById(index: number, item: any): number {    return item.id;  }  getValue(event: Event): string {    return (event.target as HTMLInputElement).value;  }}
  2. HTML <form> 엘리먼트에 [(ngModel)] 을 추가하고 name 프로퍼티와 바인딩합니다.

    src/app/app.component.html (NgModel 예제)

          
    <h1>Built-in Directives</h1><h2>Built-in attribute directives</h2><h3 id="ngModel">NgModel (two-way) Binding</h3><fieldset><h4>NgModel examples</h4>  <p>Current item name: {{ currentItem.name }}</p>  <p>    <label for="without">without NgModel:</label>    <input [value]="currentItem.name" (input)="currentItem.name=getValue($event)" id="without">  </p>  <p>    <label for="example-ngModel">[(ngModel)]:</label>    <input [(ngModel)]="currentItem.name" id="example-ngModel">  </p>  <p>    <label for="example-change">(ngModelChange)="...name=$event":</label>    <input [ngModel]="currentItem.name" (ngModelChange)="currentItem.name=$event" id="example-change">  </p>  <p>    <label for="example-uppercase">(ngModelChange)="setUppercaseName($event)"      <input [ngModel]="currentItem.name" (ngModelChange)="setUppercaseName($event)" id="example-uppercase">    </label>  </p></fieldset><hr><h2 id="ngClass">NgClass Binding</h2><p>currentClasses is {{ currentClasses | json }}</p><div [ngClass]="currentClasses">This div is initially saveable, unchanged, and special.</div><ul>  <li>    <label for="saveable">saveable</label>    <input type="checkbox" [(ngModel)]="canSave" id="saveable">  </li>  <li>    <label for="modified">modified:</label>    <input type="checkbox" [value]="!isUnchanged" (change)="isUnchanged=!isUnchanged" id="modified"></li>  <li>    <label for="special">special: <input type="checkbox" [(ngModel)]="isSpecial" id="special"></label></li></ul><button type="button" (click)="setCurrentClasses()">Refresh currentClasses</button><div [ngClass]="currentClasses">  This div should be {{ canSave ? "": "not"}} saveable,                  {{ isUnchanged ? "unchanged" : "modified" }} and                  {{ isSpecial ? "": "not"}} special after clicking "Refresh".</div><br><br><!-- 프로퍼티 값에 따라 "special" 클래스를 지정하거나 해제합니다. --><div [ngClass]="isSpecial ? 'special' : ''">This div is special</div><div class="helpful study course">Helpful study course</div><div [ngClass]="{'helpful':false, 'study':true, 'course':true}">Study course</div><!-- NgStyle binding --><hr><h3>NgStyle Binding</h3><div [style.font-size]="isSpecial ? 'x-large' : 'smaller'">  This div is x-large or smaller.</div><h4>[ngStyle] binding to currentStyles - CSS property names</h4><p>currentStyles is {{ currentStyles | json }}</p><div [ngStyle]="currentStyles">  This div is initially italic, normal weight, and extra large (24px).</div><br><label for="canSave">italic: <input id="canSave" type="checkbox" [(ngModel)]="canSave"></label> |<label for="isUnchanged">normal: <input id="isUnchanged" type="checkbox" [(ngModel)]="isUnchanged"></label> |<label for="isSpecial">xlarge: <input id="isSpecial" type="checkbox" [(ngModel)]="isSpecial"></label><button type="button" (click)="setCurrentStyles()">Refresh currentStyles</button><br><br><div [ngStyle]="currentStyles">  This div should be {{ canSave ? "italic": "plain"}},                  {{ isUnchanged ? "normal weight" : "bold" }} and,                  {{ isSpecial ? "extra large": "normal size"}} after clicking "Refresh".</div><hr><h2>Built-in structural directives</h2><h3 id="ngIf">NgIf Binding</h3><div>  <p>If isActive is true, app-item-detail will render: </p>  <app-item-detail *ngIf="isActive" [item]="item"></app-item-detail>  <button type="button" (click)="isActiveToggle()">Toggle app-item-detail</button></div><p>If currentCustomer isn't null, say hello to Laura:</p><div *ngIf="currentCustomer">Hello, {{ currentCustomer.name }}</div><p>nullCustomer is null by default. NgIf guards against null. Give it a value to show it:</p><div *ngIf="nullCustomer">Hello, <span>{{ nullCustomer }}</span></div><button type="button" (click)="giveNullCustomerValue()">Give nullCustomer a value</button><h4>NgIf binding with template (no *)</h4><ng-template [ngIf]="currentItem">Add {{ currentItem.name }} with template</ng-template><hr><h4>Show/hide vs. NgIf</h4><!-- isSpecial is true --><div [class.hidden]="!isSpecial">Show with class</div><div [class.hidden]="isSpecial">Hide with class</div><p>ItemDetail is in the DOM but hidden</p><app-item-detail [class.hidden]="isSpecial"></app-item-detail><div [style.display]="isSpecial ? 'block' : 'none'">Show with style</div><div [style.display]="isSpecial ? 'none'  : 'block'">Hide with style</div><hr><h2 id="ngFor">NgFor Binding</h2><div class="box">  <div *ngFor="let item of items">{{ item.name }}</div></div><p>*ngFor with ItemDetailComponent element</p><div class="box">  <app-item-detail *ngFor="let item of items" [item]="item"></app-item-detail></div><h4 id="ngFor-index">*ngFor with index</h4><p>with <em>semi-colon</em> separator</p><div class="box">  <div *ngFor="let item of items; let i=index">{{ i + 1 }} - {{ item.name }}</div></div><p>with <em>comma</em> separator</p><div class="box"> <div *ngFor="let item of items, let i=index">{{ i + 1 }} - {{ item.name }}</div></div><h4 id="ngFor-trackBy">*ngFor trackBy</h4><button type="button" (click)="resetList()">Reset items</button><button type="button" (click)="changeIds()">Change ids</button><button type="button" (click)="clearTrackByCounts()">Clear counts</button><p><em>without</em> trackBy</p><div class="box">  <div #noTrackBy *ngFor="let item of items">({{ item.id }}) {{ item.name }}</div>  <div id="noTrackByCnt" *ngIf="itemsNoTrackByCount" >    Item DOM elements change #{{ itemsNoTrackByCount }} without trackBy  </div></div><p>with trackBy</p><div class="box">  <div #withTrackBy *ngFor="let item of items; trackBy: trackByItems">({{ item.id }}) {{ item.name }}</div>  <div id="withTrackByCnt" *ngIf="itemsWithTrackByCount">    Item DOM elements change #{{ itemsWithTrackByCount }} with trackBy  </div></div><br><br><br><p>with trackBy and <em>semi-colon</em> separator</p><div class="box">  <div *ngFor="let item of items; trackBy: trackByItems">    ({{ item.id }}) {{ item.name }}  </div></div><p>with trackBy and <em>comma</em> separator</p><div class="box">  <div *ngFor="let item of items, trackBy: trackByItems">({{ item.id }}) {{ item.name }}</div></div><p>with trackBy and <em>space</em> separator</p><div class="box">  <div *ngFor="let item of items trackBy: trackByItems">({{ item.id }}) {{ item.name }}</div></div><p>with <em>generic</em> trackById function</p><div class="box">  <div *ngFor="let item of items, trackBy: trackById">({{ item.id }}) {{ item.name }}</div></div><hr><h2>NgSwitch Binding</h2><p>Pick your favorite item</p><div>  <label for="item-{{i}}" *ngFor="let i of items">    <div><input id="item-{{i}}"type="radio" name="items" [(ngModel)]="currentItem" [value]="i">{{ i.name }}    </div>  </label></div><div [ngSwitch]="currentItem.feature">  <app-stout-item    *ngSwitchCase="'stout'"    [item]="currentItem"></app-stout-item>  <app-device-item   *ngSwitchCase="'slim'"     [item]="currentItem"></app-device-item>  <app-lost-item     *ngSwitchCase="'vintage'"  [item]="currentItem"></app-lost-item>  <app-best-item     *ngSwitchCase="'bright'"   [item]="currentItem"></app-best-item>  <div *ngSwitchCase="'bright'">Are you as bright as {{ currentItem.name }}?</div>  <app-unknown-item  *ngSwitchDefault           [item]="currentItem"></app-unknown-item></div>

    [(ngModel)] 문법은 프로퍼티를 바인딩하는 문법입니다.

폼을 확장하는 경우라면 프로퍼티 바인딩과 이벤트 바인딩은 별도로 연결하세요. 프로퍼티에 값을 할당할 때는 프로퍼티 바인딩을 사용하고, 변화를 감지하는 것은 이벤트 바인딩으로 연결하는 것이 좋습니다. 아래 코드는 <input> 에 들어오는 값을 대문자로 바꾸는 예제 코드입니다:

src/app/app.component.html

      
<h1>Built-in Directives</h1><h2>Built-in attribute directives</h2><h3 id="ngModel">NgModel (two-way) Binding</h3><fieldset><h4>NgModel examples</h4>  <p>Current item name: {{ currentItem.name }}</p>  <p>    <label for="without">without NgModel:</label>    <input [value]="currentItem.name" (input)="currentItem.name=getValue($event)" id="without">  </p>  <p>    <label for="example-ngModel">[(ngModel)]:</label>    <input [(ngModel)]="currentItem.name" id="example-ngModel">  </p>  <p>    <label for="example-change">(ngModelChange)="...name=$event":</label>    <input [ngModel]="currentItem.name" (ngModelChange)="currentItem.name=$event" id="example-change">  </p>  <p>    <label for="example-uppercase">(ngModelChange)="setUppercaseName($event)"      <input [ngModel]="currentItem.name" (ngModelChange)="setUppercaseName($event)" id="example-uppercase">    </label>  </p></fieldset><hr><h2 id="ngClass">NgClass Binding</h2><p>currentClasses is {{ currentClasses | json }}</p><div [ngClass]="currentClasses">This div is initially saveable, unchanged, and special.</div><ul>  <li>    <label for="saveable">saveable</label>    <input type="checkbox" [(ngModel)]="canSave" id="saveable">  </li>  <li>    <label for="modified">modified:</label>    <input type="checkbox" [value]="!isUnchanged" (change)="isUnchanged=!isUnchanged" id="modified"></li>  <li>    <label for="special">special: <input type="checkbox" [(ngModel)]="isSpecial" id="special"></label></li></ul><button type="button" (click)="setCurrentClasses()">Refresh currentClasses</button><div [ngClass]="currentClasses">  This div should be {{ canSave ? "": "not"}} saveable,                  {{ isUnchanged ? "unchanged" : "modified" }} and                  {{ isSpecial ? "": "not"}} special after clicking "Refresh".</div><br><br><!-- 프로퍼티 값에 따라 "special" 클래스를 지정하거나 해제합니다. --><div [ngClass]="isSpecial ? 'special' : ''">This div is special</div><div class="helpful study course">Helpful study course</div><div [ngClass]="{'helpful':false, 'study':true, 'course':true}">Study course</div><!-- NgStyle binding --><hr><h3>NgStyle Binding</h3><div [style.font-size]="isSpecial ? 'x-large' : 'smaller'">  This div is x-large or smaller.</div><h4>[ngStyle] binding to currentStyles - CSS property names</h4><p>currentStyles is {{ currentStyles | json }}</p><div [ngStyle]="currentStyles">  This div is initially italic, normal weight, and extra large (24px).</div><br><label for="canSave">italic: <input id="canSave" type="checkbox" [(ngModel)]="canSave"></label> |<label for="isUnchanged">normal: <input id="isUnchanged" type="checkbox" [(ngModel)]="isUnchanged"></label> |<label for="isSpecial">xlarge: <input id="isSpecial" type="checkbox" [(ngModel)]="isSpecial"></label><button type="button" (click)="setCurrentStyles()">Refresh currentStyles</button><br><br><div [ngStyle]="currentStyles">  This div should be {{ canSave ? "italic": "plain"}},                  {{ isUnchanged ? "normal weight" : "bold" }} and,                  {{ isSpecial ? "extra large": "normal size"}} after clicking "Refresh".</div><hr><h2>Built-in structural directives</h2><h3 id="ngIf">NgIf Binding</h3><div>  <p>If isActive is true, app-item-detail will render: </p>  <app-item-detail *ngIf="isActive" [item]="item"></app-item-detail>  <button type="button" (click)="isActiveToggle()">Toggle app-item-detail</button></div><p>If currentCustomer isn't null, say hello to Laura:</p><div *ngIf="currentCustomer">Hello, {{ currentCustomer.name }}</div><p>nullCustomer is null by default. NgIf guards against null. Give it a value to show it:</p><div *ngIf="nullCustomer">Hello, <span>{{ nullCustomer }}</span></div><button type="button" (click)="giveNullCustomerValue()">Give nullCustomer a value</button><h4>NgIf binding with template (no *)</h4><ng-template [ngIf]="currentItem">Add {{ currentItem.name }} with template</ng-template><hr><h4>Show/hide vs. NgIf</h4><!-- isSpecial is true --><div [class.hidden]="!isSpecial">Show with class</div><div [class.hidden]="isSpecial">Hide with class</div><p>ItemDetail is in the DOM but hidden</p><app-item-detail [class.hidden]="isSpecial"></app-item-detail><div [style.display]="isSpecial ? 'block' : 'none'">Show with style</div><div [style.display]="isSpecial ? 'none'  : 'block'">Hide with style</div><hr><h2 id="ngFor">NgFor Binding</h2><div class="box">  <div *ngFor="let item of items">{{ item.name }}</div></div><p>*ngFor with ItemDetailComponent element</p><div class="box">  <app-item-detail *ngFor="let item of items" [item]="item"></app-item-detail></div><h4 id="ngFor-index">*ngFor with index</h4><p>with <em>semi-colon</em> separator</p><div class="box">  <div *ngFor="let item of items; let i=index">{{ i + 1 }} - {{ item.name }}</div></div><p>with <em>comma</em> separator</p><div class="box"> <div *ngFor="let item of items, let i=index">{{ i + 1 }} - {{ item.name }}</div></div><h4 id="ngFor-trackBy">*ngFor trackBy</h4><button type="button" (click)="resetList()">Reset items</button><button type="button" (click)="changeIds()">Change ids</button><button type="button" (click)="clearTrackByCounts()">Clear counts</button><p><em>without</em> trackBy</p><div class="box">  <div #noTrackBy *ngFor="let item of items">({{ item.id }}) {{ item.name }}</div>  <div id="noTrackByCnt" *ngIf="itemsNoTrackByCount" >    Item DOM elements change #{{ itemsNoTrackByCount }} without trackBy  </div></div><p>with trackBy</p><div class="box">  <div #withTrackBy *ngFor="let item of items; trackBy: trackByItems">({{ item.id }}) {{ item.name }}</div>  <div id="withTrackByCnt" *ngIf="itemsWithTrackByCount">    Item DOM elements change #{{ itemsWithTrackByCount }} with trackBy  </div></div><br><br><br><p>with trackBy and <em>semi-colon</em> separator</p><div class="box">  <div *ngFor="let item of items; trackBy: trackByItems">    ({{ item.id }}) {{ item.name }}  </div></div><p>with trackBy and <em>comma</em> separator</p><div class="box">  <div *ngFor="let item of items, trackBy: trackByItems">({{ item.id }}) {{ item.name }}</div></div><p>with trackBy and <em>space</em> separator</p><div class="box">  <div *ngFor="let item of items trackBy: trackByItems">({{ item.id }}) {{ item.name }}</div></div><p>with <em>generic</em> trackById function</p><div class="box">  <div *ngFor="let item of items, trackBy: trackById">({{ item.id }}) {{ item.name }}</div></div><hr><h2>NgSwitch Binding</h2><p>Pick your favorite item</p><div>  <label for="item-{{i}}" *ngFor="let i of items">    <div><input id="item-{{i}}"type="radio" name="items" [(ngModel)]="currentItem" [value]="i">{{ i.name }}    </div>  </label></div><div [ngSwitch]="currentItem.feature">  <app-stout-item    *ngSwitchCase="'stout'"    [item]="currentItem"></app-stout-item>  <app-device-item   *ngSwitchCase="'slim'"     [item]="currentItem"></app-device-item>  <app-lost-item     *ngSwitchCase="'vintage'"  [item]="currentItem"></app-lost-item>  <app-best-item     *ngSwitchCase="'bright'"   [item]="currentItem"></app-best-item>  <div *ngSwitchCase="'bright'">Are you as bright as {{ currentItem.name }}?</div>  <app-unknown-item  *ngSwitchDefault           [item]="currentItem"></app-unknown-item></div>

그리고 이렇게 다양하게 활용할 수도 있습니다:

NgModel variations

NgModel, 값 접근자

NgModel 디렉티브는 ControlValueAccessor를 지원하는 엘리먼트에 동작합니다. 그래서 Angular는 모든 HTML 폼 엘리먼트에 값 접근자(value accessors) 를 지원합니다. 자세한 내용은 문서를 참고하세요.

기본 폼 엘리먼트가 아닌 엘리먼트나 서드 파티 커스텀 컴포넌트에 [(ngModel)]을 적용하려면 값 접근자를 직접 정의해야 합니다. 자세한 내용은 DefaultValueAccessor 문서의 API 명세를 참고하세요.

참고: Angular 컴포넌트를 정의했다면 이 컴포넌트에는 값 접근자를 정의하거나 NgModel을 사용할 필요 없이 양방향 바인딩 문법을 활용하면 됩니다.

DOM 엘리먼트 없이 디렉티브 적용하기

Angular 엘리먼트 <ng-container>는 스타일이나 레이앙수 변경 없이 엘리먼트 여러 개를 하나로 묶는 엘리먼트입니다. 이 엘리먼트는 DOM에 직접 추가되지 않습니다.

그런데 <ng-container>는 엘리먼트 없이 디렉티브를 적용하는 용도로도 사용할 수 있습니다.

이렇게 사용하면 됩니다:

src/app/app.component.html (ngif-ngcontainer)

      
<h1>Structural Directives</h1><p>Conditional display of hero</p><blockquote><div *ngIf="hero" class="name">{{hero.name}}</div></blockquote><p>List of heroes</p><ul>  <li *ngFor="let hero of heroes">{{hero.name}}</li></ul><hr><h2 id="ngIf">NgIf</h2><p *ngIf="true">  Expression is true and ngIf is true.  This paragraph is in the DOM.</p><p *ngIf="false">  Expression is false and ngIf is false.  This paragraph is not in the DOM.</p><p [style.display]="'block'">  Expression sets display to "block".  This paragraph is visible.</p><p [style.display]="'none'">  Expression sets display to "none".  This paragraph is hidden but still in the DOM.</p><h4>NgIf with template</h4><p><ng-template> element</p><ng-template [ngIf]="hero">  <div class="name">{{hero.name}}</div></ng-template><hr><h2 id="ng-container"><ng-container></h2><h4>*ngIf with a <ng-container></h4><button type="button" (click)="hero = hero ? null : heroes[0]">Toggle hero</button><p>  I turned the corner  <ng-container *ngIf="hero">    and saw {{hero.name}}. I waved  </ng-container>  and continued on my way.</p><p>  I turned the corner  <span *ngIf="hero">    and saw {{hero.name}}. I waved  </span>  and continued on my way.</p><p><em><select> with <span></em></p><div>  Pick your favorite hero  (<label for="show-sad"><input id="show-sad" type="checkbox" checked (change)="showSad = !showSad">show sad</label>)</div><select [(ngModel)]="hero">  <span *ngFor="let h of heroes">    <span *ngIf="showSad || h.emotion !== 'sad'">      <option [ngValue]="h">{{h.name}} ({{h.emotion}})</option>    </span>  </span></select><p><em><select> with <ng-container></em></p><div>  Pick your favorite hero  (<label for="showSad"><input id="showSad" type="checkbox" checked (change)="showSad = !showSad">show sad</label>)</div><select [(ngModel)]="hero">  <ng-container *ngFor="let h of heroes">    <ng-container *ngIf="showSad || h.emotion !== 'sad'">      <option [ngValue]="h">{{h.name}} ({{h.emotion}})</option>    </ng-container>  </ng-container></select><br><br><hr><h2 id="ngFor">NgFor</h2><div class="box"><p class="code"><div *ngFor="let hero of heroes; let i=index; let odd=odd; trackBy: trackById" [class.odd]="odd"></p><div  *ngFor="let hero of heroes; let i=index; let odd=odd; trackBy: trackById"  [class.odd]="odd">  ({{i}}) {{hero.name}}</div><p class="code"><ng-template ngFor let-hero [ngForOf]="heroes" let-i="index" let-odd="odd" [ngForTrackBy]="trackById"/></p><ng-template ngFor let-hero [ngForOf]="heroes"  let-i="index" let-odd="odd" [ngForTrackBy]="trackById">  <div [class.odd]="odd">    ({{i}}) {{hero.name}}  </div></ng-template></div><hr><h2 id="ngSwitch">NgSwitch</h2><div>Pick your favorite hero</div><p>  <label for="hero-{{h}}" *ngFor="let h of heroes">    <input id="hero-{{h}}" type="radio" name="heroes" [(ngModel)]="hero" [value]="h">{{h.name}}  </label>  <label for="none-of-the-above"><input id="none-of-the-above" type="radio" name="heroes" (click)="hero = null">None of the above</label></p><h4>NgSwitch</h4><div [ngSwitch]="hero?.emotion">  <app-happy-hero    *ngSwitchCase="'happy'"    [hero]="hero!"></app-happy-hero>  <app-sad-hero      *ngSwitchCase="'sad'"      [hero]="hero!"></app-sad-hero>  <app-confused-hero *ngSwitchCase="'confused'" [hero]="hero!"></app-confused-hero>  <app-unknown-hero  *ngSwitchDefault           [hero]="hero!"></app-unknown-hero></div><h4>NgSwitch with <ng-template></h4><div [ngSwitch]="hero?.emotion">  <ng-template ngSwitchCase="happy">    <app-happy-hero [hero]="hero!"></app-happy-hero>  </ng-template>  <ng-template ngSwitchCase="sad">    <app-sad-hero [hero]="hero!"></app-sad-hero>  </ng-template>  <ng-template ngSwitchCase="confused">    <app-confused-hero [hero]="hero!"></app-confused-hero>  </ng-template >  <ng-template ngSwitchDefault>    <app-unknown-hero [hero]="hero!"></app-unknown-hero>  </ng-template></div><hr><hr><h2 id="appIfLoaded">IfLoadedDirective</h2><app-hero></app-hero><hr><h2 id="appTrigonometry">TrigonometryDirective</h2><ul *appTrigonometry="30; sin as s; cos as c; tan as t">  <li>sin(30°): {{ s }}</li>  <li>cos(30°): {{ c }}</li>  <li>tan(30°): {{ t }}</li></ul>
ngcontainer paragraph with proper style
  1. FormsModule 로 제공되는 ngModel 디렉티브를 불러옵니다.

  2. FormsModule 을 적절한 Angular 모듈의 imports 배열에 추가합니다.

  3. 이제 <option> 엘리먼트는 <option> 엘리먼트를 둘러싼 <ng-container>에 지정된 조건에 따라 DOM에 추가되거나 제거됩니다.

    src/app/app.component.html (select-ngcontainer)

          
    <h1>Structural Directives</h1><p>Conditional display of hero</p><blockquote><div *ngIf="hero" class="name">{{hero.name}}</div></blockquote><p>List of heroes</p><ul>  <li *ngFor="let hero of heroes">{{hero.name}}</li></ul><hr><h2 id="ngIf">NgIf</h2><p *ngIf="true">  Expression is true and ngIf is true.  This paragraph is in the DOM.</p><p *ngIf="false">  Expression is false and ngIf is false.  This paragraph is not in the DOM.</p><p [style.display]="'block'">  Expression sets display to "block".  This paragraph is visible.</p><p [style.display]="'none'">  Expression sets display to "none".  This paragraph is hidden but still in the DOM.</p><h4>NgIf with template</h4><p><ng-template> element</p><ng-template [ngIf]="hero">  <div class="name">{{hero.name}}</div></ng-template><hr><h2 id="ng-container"><ng-container></h2><h4>*ngIf with a <ng-container></h4><button type="button" (click)="hero = hero ? null : heroes[0]">Toggle hero</button><p>  I turned the corner  <ng-container *ngIf="hero">    and saw {{hero.name}}. I waved  </ng-container>  and continued on my way.</p><p>  I turned the corner  <span *ngIf="hero">    and saw {{hero.name}}. I waved  </span>  and continued on my way.</p><p><em><select> with <span></em></p><div>  Pick your favorite hero  (<label for="show-sad"><input id="show-sad" type="checkbox" checked (change)="showSad = !showSad">show sad</label>)</div><select [(ngModel)]="hero">  <span *ngFor="let h of heroes">    <span *ngIf="showSad || h.emotion !== 'sad'">      <option [ngValue]="h">{{h.name}} ({{h.emotion}})</option>    </span>  </span></select><p><em><select> with <ng-container></em></p><div>  Pick your favorite hero  (<label for="showSad"><input id="showSad" type="checkbox" checked (change)="showSad = !showSad">show sad</label>)</div><select [(ngModel)]="hero">  <ng-container *ngFor="let h of heroes">    <ng-container *ngIf="showSad || h.emotion !== 'sad'">      <option [ngValue]="h">{{h.name}} ({{h.emotion}})</option>    </ng-container>  </ng-container></select><br><br><hr><h2 id="ngFor">NgFor</h2><div class="box"><p class="code"><div *ngFor="let hero of heroes; let i=index; let odd=odd; trackBy: trackById" [class.odd]="odd"></p><div  *ngFor="let hero of heroes; let i=index; let odd=odd; trackBy: trackById"  [class.odd]="odd">  ({{i}}) {{hero.name}}</div><p class="code"><ng-template ngFor let-hero [ngForOf]="heroes" let-i="index" let-odd="odd" [ngForTrackBy]="trackById"/></p><ng-template ngFor let-hero [ngForOf]="heroes"  let-i="index" let-odd="odd" [ngForTrackBy]="trackById">  <div [class.odd]="odd">    ({{i}}) {{hero.name}}  </div></ng-template></div><hr><h2 id="ngSwitch">NgSwitch</h2><div>Pick your favorite hero</div><p>  <label for="hero-{{h}}" *ngFor="let h of heroes">    <input id="hero-{{h}}" type="radio" name="heroes" [(ngModel)]="hero" [value]="h">{{h.name}}  </label>  <label for="none-of-the-above"><input id="none-of-the-above" type="radio" name="heroes" (click)="hero = null">None of the above</label></p><h4>NgSwitch</h4><div [ngSwitch]="hero?.emotion">  <app-happy-hero    *ngSwitchCase="'happy'"    [hero]="hero!"></app-happy-hero>  <app-sad-hero      *ngSwitchCase="'sad'"      [hero]="hero!"></app-sad-hero>  <app-confused-hero *ngSwitchCase="'confused'" [hero]="hero!"></app-confused-hero>  <app-unknown-hero  *ngSwitchDefault           [hero]="hero!"></app-unknown-hero></div><h4>NgSwitch with <ng-template></h4><div [ngSwitch]="hero?.emotion">  <ng-template ngSwitchCase="happy">    <app-happy-hero [hero]="hero!"></app-happy-hero>  </ng-template>  <ng-template ngSwitchCase="sad">    <app-sad-hero [hero]="hero!"></app-sad-hero>  </ng-template>  <ng-template ngSwitchCase="confused">    <app-confused-hero [hero]="hero!"></app-confused-hero>  </ng-template >  <ng-template ngSwitchDefault>    <app-unknown-hero [hero]="hero!"></app-unknown-hero>  </ng-template></div><hr><hr><h2 id="appIfLoaded">IfLoadedDirective</h2><app-hero></app-hero><hr><h2 id="appTrigonometry">TrigonometryDirective</h2><ul *appTrigonometry="30; sin as s; cos as c; tan as t">  <li>sin(30°): {{ s }}</li>  <li>cos(30°): {{ c }}</li>  <li>tan(30°): {{ t }}</li></ul>
    ngcontainer options work properly

다음 단계