# 2026년 Angular 제어 흐름 구문 완벽 가이드: @if, @for, @switch와 면접 질문 > Angular 17에서 도입된 새로운 제어 흐름 구문(@if, @for, @switch)에 대한 상세 가이드. 기존 구조 디렉티브에서의 마이그레이션 방법, 성능 최적화 전략, 기술 면접 빈출 질문과 답변을 다룹니다. - Published: 2026-09-03 - Updated: 2026-09-03 - Author: Anthony Fillion-Maillet - Tags: angular, control-flow, template-syntax, angular-17, 면접질문 - Reading time: 12 min --- 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 공식 문서](https://angular.dev/guide/templates/control-flow)에서는 모든 신규 프로젝트에서 블록 구문을 완전히 채택하고, 기존 코드베이스는 점진적으로 마이그레이션할 것을 권장합니다. ## @if: ng-template 없는 조건부 렌더링 `@if` 블록은 참/거짓 표현식을 기반으로 콘텐츠를 조건부로 렌더링합니다. `*ngIf`와 달리 `@else if`와 `@else` 분기를 직접 지원합니다: ```typescript // user-status.component.ts import { Component, input } from '@angular/core'; @Component({ selector: 'app-user-status', template: ` @if (user().role === 'admin') { } @else if (user().role === 'editor') { } @else { } ` }) export class UserStatusComponent { user = input.required<{ role: string }>(); } ``` `@if` 블록은 `as` 키워드를 사용한 변수 별칭도 지원하여 중첩된 표현식에서 값을 추출할 수 있습니다: ```html @if (user().profile?.settings?.theme; as theme) {

현재 테마: {{ theme }}

} ``` 이 패턴은 비동기 데이터나 깊게 중첩된 객체를 다룰 때 특히 유용합니다. 별칭된 변수는 조건이 참일 때만 정의되기 때문입니다. ## @for: 필수 track 표현식을 사용한 반복 `@for` 블록은 모든 JavaScript 이터러블을 순회하며, 배열에 대한 최적화를 제공합니다. `*ngFor`와 달리 `track` 표현식이 필수입니다: ```typescript // product-list.component.ts 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) { } @empty {

상품이 없습니다

} ` }) export class ProductListComponent { products = input.required(); } ``` `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) {
  • {{ i + 1 }}. {{ item.name }}
  • } ``` 사용 가능한 컨텍스트 변수: - `$index`: 0 기반 위치 - `$count`: 총 항목 수 - `$first`, `$last`: 첫 번째/마지막 항목에 대한 불리언 플래그 - `$even`, `$odd`: 인덱스 짝수/홀수에 기반한 불리언 플래그 ## @switch: 타입 안전한 조건 분기 `@switch` 블록은 엄격한 동등성(`===`) 비교를 사용하여 완전한 패턴 매칭을 제공합니다: ```typescript // status-badge.component.ts import { Component, input } from '@angular/core'; type OrderStatus = 'pending' | 'processing' | 'shipped' | 'delivered'; @Component({ selector: 'app-status-badge', template: ` @switch (status()) { @case ('pending') { 대기 중 } @case ('processing') { 처리 중 } @case ('shipped') { 발송됨 } @case ('delivered') { 배송 완료 } } ` }) export class StatusBadgeComponent { status = input.required(); } ``` JavaScript의 `switch`와 달리 Angular의 `@switch`는 폴스루(fall-through) 동작이 없습니다. 동일한 블록을 대상으로 하는 여러 조건에는 연속적인 `@case` 문이 필요합니다: ```html @switch (userRole()) { @case ('admin') @case ('superadmin') { } @case ('editor') @case ('reviewer') { } @default { } } ``` ### @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](/technologies/angular/interview-questions/angular-signals)와 원활하게 통합되어 세밀한 반응성을 제공합니다: ```typescript // dashboard.component.ts import { Component, computed, signal } from '@angular/core'; @Component({ selector: 'app-dashboard', template: ` @if (isLoading()) { } @else if (hasError()) { } @else { @for (item of filteredItems(); track item.id) { } @empty {

    필터 조건에 맞는 항목이 없습니다

    } } ` }) export class DashboardComponent { items = signal([]); 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의 존리스 변경 감지](/blog/angular/angular-19-zoneless-change-detection-performance)와 결합하면 고도로 최적화된 렌더링이 가능합니다. ## Angular 제어 흐름 관련 주요 면접 질문 기술 면접에서는 제어 흐름 구문에 대한 이해, 특히 구조 디렉티브와의 차이점이 자주 테스트됩니다. ### 질문 1: @for에서 track이 필수인 이유는? `track` 표현식은 각 항목에 대한 안정적인 신원 정보를 Angular에 제공합니다. 이것이 없으면 컬렉션이 변경될 때 어떤 DOM 노드를 생성, 업데이트 또는 삭제할지 Angular가 효율적으로 결정할 수 없습니다. `track`을 필수로 만들면 개발자가 신원에 대한 명시적인 결정을 내리도록 강제하여 암시적 참조 트래킹의 성능 함정을 피할 수 있습니다. ### 질문 2: @if는 async 파이프를 사용한 *ngIf와 어떻게 다른가? 둘 다 Observable을 처리할 수 있지만, Signal을 사용한 `@if`는 값에 대한 동기적 접근을 제공합니다: ```typescript // *ngIf와 async 파이프 사용 (레거시)
    {{ user.name }}
    // @if와 Signal 사용 (모던) @if (user(); as user) {
    {{ user.name }}
    } ``` Signal 접근 방식은 구독 관리를 피하고 Angular의 변경 감지와 더 잘 통합됩니다. ### 질문 3: @switch가 복잡한 @if/@else if 체인을 대체할 수 있는가? `@switch`는 단일 표현식을 여러 개별 값과 비교할 때 사용해야 합니다. 다른 표현식을 포함하는 복잡한 불리언 조건에는 `@if/@else if`가 더 적합합니다: ```html @switch (status()) { @case ('active') { ... } @case ('inactive') { ... } } @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 이후 권장되는 접근 방식이며 구조 디렉티브를 대체함 - `@for`의 `track` 표현식은 필수: 최적의 성능을 위해 `item.id`와 같은 고유 식별자 사용, 참조 기반 재렌더링을 유발하는 `track item`은 피해야 함 - `@if`는 `ng-template` 없이 네이티브 `@else if`와 `@else` 분기를 지원하며, `as` 키워드는 참인 값을 재사용을 위해 별칭화함 - `@switch`는 엄격한 동등성을 사용하며 폴스루 없음: 연속적인 `@case` 문이 동일한 블록을 대상으로 하고, `@default never;`로 완전성 타입 검사 가능 - 제어 흐름 블록은 [Signals](/technologies/angular/interview-questions/angular-signals)와 통합되어 세밀한 반응성 제공: Signal이 업데이트되면 영향받는 블록만 재렌더링 - `ng generate @angular/core:control-flow`를 실행하여 기존 템플릿을 자동으로 마이그레이션 --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/ko/blog/angular/angular-control-flow-syntax-if-for-switch-guide