# 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