2026년 Angular 제어 흐름 구문 완벽 가이드: @if, @for, @switch와 면접 질문

Angular 17에서 도입된 새로운 제어 흐름 구문(@if, @for, @switch)에 대한 상세 가이드. 기존 구조 디렉티브에서의 마이그레이션 방법, 성능 최적화 전략, 기술 면접 빈출 질문과 답변을 다룹니다.

Angular 제어 흐름 구문 가이드

Angular의 제어 흐름 구문(@if, @for, @switch)은 오랫동안 Angular 템플릿을 지배했던 구조 디렉티브(*ngIf, *ngFor, *ngSwitch)를 대체합니다. Angular 17에서 도입되어 Angular 18에서 안정화되었으며, Angular 22에서는 기본 구문으로 채택되었습니다. 이 블록 기반 구문은 템플릿을 표준 프로그래밍 구조에 더 가깝게 만들면서 성능 최적화를 가능하게 합니다.

마이그레이션 도구 제공

ng generate @angular/core:control-flow 명령어를 실행하면 기존 템플릿을 구조 디렉티브에서 새로운 제어 흐름 구문으로 자동 변환할 수 있습니다. 마이그레이션 기간 동안 두 구문이 공존할 수 있지만, ngIf, ngFor, ngSwitch는 Angular 19부터 소프트 디프리케이션 상태입니다.

Angular가 구조 디렉티브를 블록 구문으로 대체한 이유

구조 디렉티브는 CommonModule 임포트가 필요했고, JavaScript와 다른 마이크로 구문을 사용했으며, 복잡한 조건에서는 콘텐츠를 ng-template으로 감싸야 했습니다. 새로운 제어 흐름 구문은 이러한 문제를 해결합니다:

  • 임포트 불필요: @if, @for, @switch는 프레임워크에 내장되어 있음
  • JavaScript와 유사한 구문: 조건문과 반복문이 표준 코드처럼 읽힘
  • 네이티브 @else 지원: else 분기를 위한 ng-template 참조 불필요
  • 향상된 트리 쉐이킹: 사용되지 않는 제어 흐름 블록은 번들 크기에 영향 없음

Angular 공식 문서에서는 모든 신규 프로젝트에서 블록 구문을 완전히 채택하고, 기존 코드베이스는 점진적으로 마이그레이션할 것을 권장합니다.

@if: ng-template 없는 조건부 렌더링

@if 블록은 참/거짓 표현식을 기반으로 콘텐츠를 조건부로 렌더링합니다. *ngIf와 달리 @else if@else 분기를 직접 지원합니다:

user-status.component.tstypescript
import { Component, input } from '@angular/core';

@Component({
  selector: 'app-user-status',
  template: `
    @if (user().role === 'admin') {
      <app-admin-panel />
    } @else if (user().role === 'editor') {
      <app-editor-panel />
    } @else {
      <app-viewer-panel />
    }
  `
})
export class UserStatusComponent {
  user = input.required<{ role: string }>();
}

@if 블록은 as 키워드를 사용한 변수 별칭도 지원하여 중첩된 표현식에서 값을 추출할 수 있습니다:

html
<!-- 반복적인 프로퍼티 접근 회피 -->
@if (user().profile?.settings?.theme; as theme) {
  <p>현재 테마: {{ theme }}</p>
}

이 패턴은 비동기 데이터나 깊게 중첩된 객체를 다룰 때 특히 유용합니다. 별칭된 변수는 조건이 참일 때만 정의되기 때문입니다.

@for: 필수 track 표현식을 사용한 반복

@for 블록은 모든 JavaScript 이터러블을 순회하며, 배열에 대한 최적화를 제공합니다. *ngFor와 달리 track 표현식이 필수입니다:

product-list.component.tstypescript
import { Component, input } from '@angular/core';

interface Product {
  id: string;
  name: string;
  price: number;
}

@Component({
  selector: 'app-product-list',
  template: `
    @for (product of products(); track product.id) {
      <app-product-card [product]="product" />
    } @empty {
      <p>상품이 없습니다</p>
    }
  `
})
export class ProductListComponent {
  products = input.required<Product[]>();
}

track 표현식은 재렌더링 시 각 항목을 식별하는 방법을 Angular에 알려줍니다. 적절한 트래킹 프로퍼티 선택은 성능에 직접적인 영향을 미칩니다:

Track 표현식사용 사례성능 영향
track item.id고유 식별자를 가진 항목최적: 최소한의 DOM 업데이트
track $index재정렬되지 않는 정적 리스트허용 가능: 재정렬 시 전체 재렌더링
track item참조 트래킹저조함: 새 참조 = 새 DOM 노드

@for 블록 내의 컨텍스트 변수

Angular는 @for 블록 내부에서 반복 메타데이터를 노출하는 암시적 변수를 제공합니다:

html
@for (item of items(); track item.id; let i = $index, isLast = $last) {
  <li class="item" [class.last]="isLast">
    {{ i + 1 }}. {{ item.name }}
  </li>
}

사용 가능한 컨텍스트 변수:

  • $index: 0 기반 위치
  • $count: 총 항목 수
  • $first, $last: 첫 번째/마지막 항목에 대한 불리언 플래그
  • $even, $odd: 인덱스 짝수/홀수에 기반한 불리언 플래그

Angular 면접 준비가 되셨나요?

인터랙티브 시뮬레이터, flashcards, 기술 테스트로 연습하세요.

@switch: 타입 안전한 조건 분기

@switch 블록은 엄격한 동등성(===) 비교를 사용하여 완전한 패턴 매칭을 제공합니다:

status-badge.component.tstypescript
import { Component, input } from '@angular/core';

type OrderStatus = 'pending' | 'processing' | 'shipped' | 'delivered';

@Component({
  selector: 'app-status-badge',
  template: `
    @switch (status()) {
      @case ('pending') {
        <span class="badge badge-gray">대기 중</span>
      }
      @case ('processing') {
        <span class="badge badge-blue">처리 중</span>
      }
      @case ('shipped') {
        <span class="badge badge-yellow">발송됨</span>
      }
      @case ('delivered') {
        <span class="badge badge-green">배송 완료</span>
      }
    }
  `
})
export class StatusBadgeComponent {
  status = input.required<OrderStatus>();
}

JavaScript의 switch와 달리 Angular의 @switch는 폴스루(fall-through) 동작이 없습니다. 동일한 블록을 대상으로 하는 여러 조건에는 연속적인 @case 문이 필요합니다:

html
@switch (userRole()) {
  @case ('admin')
  @case ('superadmin') {
    <app-admin-dashboard />
  }
  @case ('editor')
  @case ('reviewer') {
    <app-editor-dashboard />
  }
  @default {
    <app-viewer-dashboard />
  }
}

@default never를 사용한 완전성 타입 검사

Angular 22는 컴파일 타임 완전성 검사를 지원합니다. @default never;를 사용하면 남은 케이스가 없어야 함을 선언합니다:

typescript
type Theme = 'light' | 'dark' | 'system';

// @case 블록에서 Theme 값이 누락되면 컴파일 에러 발생
@switch (theme()) {
  @case ('light') { /* ... */ }
  @case ('dark') { /* ... */ }
  @case ('system') { /* ... */ }
  @default never;
}

Theme 유니온에 새 값을 추가하면 컴파일 에러가 발생하여 개발자가 모든 케이스를 처리하도록 강제합니다.

제어 흐름 구문과 Angular Signals 통합

제어 흐름 블록은 Angular Signals와 원활하게 통합되어 세밀한 반응성을 제공합니다:

dashboard.component.tstypescript
import { Component, computed, signal } from '@angular/core';

@Component({
  selector: 'app-dashboard',
  template: `
    @if (isLoading()) {
      <app-skeleton />
    } @else if (hasError()) {
      <app-error [message]="errorMessage()" />
    } @else {
      @for (item of filteredItems(); track item.id) {
        <app-item-card [item]="item" />
      } @empty {
        <p>필터 조건에 맞는 항목이 없습니다</p>
      }
    }
  `
})
export class DashboardComponent {
  items = signal<Item[]>([]);
  filter = signal('');
  isLoading = signal(true);
  hasError = signal(false);
  errorMessage = signal('');

  // computed signal은 의존성이 변경될 때만 재계산
  filteredItems = computed(() =>
    this.items().filter(item =>
      item.name.toLowerCase().includes(this.filter().toLowerCase())
    )
  );
}

Signal이 업데이트되면 Angular는 영향을 받는 제어 흐름 블록만 재평가합니다. Angular의 존리스 변경 감지와 결합하면 고도로 최적화된 렌더링이 가능합니다.

Angular 제어 흐름 관련 주요 면접 질문

기술 면접에서는 제어 흐름 구문에 대한 이해, 특히 구조 디렉티브와의 차이점이 자주 테스트됩니다.

질문 1: @for에서 track이 필수인 이유는?

track 표현식은 각 항목에 대한 안정적인 신원 정보를 Angular에 제공합니다. 이것이 없으면 컬렉션이 변경될 때 어떤 DOM 노드를 생성, 업데이트 또는 삭제할지 Angular가 효율적으로 결정할 수 없습니다. track을 필수로 만들면 개발자가 신원에 대한 명시적인 결정을 내리도록 강제하여 암시적 참조 트래킹의 성능 함정을 피할 수 있습니다.

질문 2: @if는 async 파이프를 사용한 *ngIf와 어떻게 다른가?

둘 다 Observable을 처리할 수 있지만, Signal을 사용한 @if는 값에 대한 동기적 접근을 제공합니다:

typescript
// *ngIf와 async 파이프 사용 (레거시)
<div *ngIf="user$ | async as user">{{ user.name }}</div>

// @if와 Signal 사용 (모던)
@if (user(); as user) {
  <div>{{ user.name }}</div>
}

Signal 접근 방식은 구독 관리를 피하고 Angular의 변경 감지와 더 잘 통합됩니다.

질문 3: @switch가 복잡한 @if/@else if 체인을 대체할 수 있는가?

@switch는 단일 표현식을 여러 개별 값과 비교할 때 사용해야 합니다. 다른 표현식을 포함하는 복잡한 불리언 조건에는 @if/@else if가 더 적합합니다:

html
<!-- 단일 표현식, 여러 값에는 @switch 사용 -->
@switch (status()) {
  @case ('active') { ... }
  @case ('inactive') { ... }
}

<!-- 여러 표현식에는 @if 사용 -->
@if (isAdmin() && hasPermission('write')) {
  ...
} @else if (isEditor()) {
  ...
}

질문 4: 컬렉션이 비어 있을 때 @for는 어떻게 되는가?

이터러블에 항목이 없으면 Angular는 @for 콘텐츠를 완전히 건너뜁니다. 선택적 @empty 블록이 이 경우 대체 콘텐츠를 렌더링합니다. @empty가 없으면 아무것도 렌더링되지 않으며, 이는 빈 컨테이너를 렌더링하는 일부 프레임워크와 다릅니다.

구조 디렉티브에서 제어 흐름으로 마이그레이션

Angular CLI는 자동화된 마이그레이션 스키매틱을 제공합니다:

bash
# 전체 프로젝트 마이그레이션
ng generate @angular/core:control-flow

# 특정 디렉토리 마이그레이션
ng generate @angular/core:control-flow --path=src/app/features

스키매틱은 대부분의 변환을 자동으로 처리하지만, 엣지 케이스에 대해서는 출력을 검토하세요:

구조 디렉티브제어 흐름 대응
*ngIf="condition"@if (condition) { }
*ngIf="condition; else elseBlock"@if (condition) { } @else { }
*ngFor="let item of items"@for (item of items; track item) { }
*ngFor="let item of items; index as i"@for (item of items; track item; let i = $index) { }
[ngSwitch] + *ngSwitchCase@switch + @case

마이그레이션 후, 더 이상 구조 디렉티브를 사용하지 않는 스탠드얼론 컴포넌트에서 CommonModule 임포트를 제거하세요.

연습을 시작하세요!

면접 시뮬레이터와 기술 테스트로 지식을 테스트하세요.

Angular 제어 흐름 구문 핵심 정리

  • Angular의 제어 흐름 구문(@if, @for, @switch)은 Angular 19 이후 권장되는 접근 방식이며 구조 디렉티브를 대체함
  • @fortrack 표현식은 필수: 최적의 성능을 위해 item.id와 같은 고유 식별자 사용, 참조 기반 재렌더링을 유발하는 track item은 피해야 함
  • @ifng-template 없이 네이티브 @else if@else 분기를 지원하며, as 키워드는 참인 값을 재사용을 위해 별칭화함
  • @switch는 엄격한 동등성을 사용하며 폴스루 없음: 연속적인 @case 문이 동일한 블록을 대상으로 하고, @default never;로 완전성 타입 검사 가능
  • 제어 흐름 블록은 Signals와 통합되어 세밀한 반응성 제공: Signal이 업데이트되면 영향받는 블록만 재렌더링
  • ng generate @angular/core:control-flow를 실행하여 기존 템플릿을 자동으로 마이그레이션
오늘의 챌린지

Angular 코드의 버그를 찾을 수 있나요

실제 코드 한 조각, 숨은 버그 하나, 하루 한 번. 계정 없이 바로 도전할 수 있습니다.

Anthony Fillion-Maillet

작성자

Anthony Fillion-Maillet

SharpSkill 창업자

10년 이상 풀스택 개발을 해왔습니다. SharpSkill을 운영하며 이곳에 게시되는 모든 내용에 책임을 집니다.

2026년 9월 3일 업데이트

태그

#angular
#control-flow
#template-syntax
#angular-17
#면접질문

공유

관련 기사