ไวยากรณ์ Control Flow ใน Angular 2026: @if, @for, @switch และคำถามสัมภาษณ์
เรียนรู้ไวยากรณ์ control flow ของ Angular (@if, @for, @switch) ที่มาแทนที่ structural directive ครบถ้วนพร้อมตัวอย่างโค้ด เคล็ดลับการ migrate และคำถามสัมภาษณ์เทคนิค

ไวยากรณ์ control flow ของ Angular (@if, @for, @switch) มาแทนที่ structural directive อย่าง *ngIf, *ngFor และ *ngSwitch ที่ครอบงำ template ของ Angular มาหลายปี ถูกแนะนำใน Angular 17 เสถียรตั้งแต่ Angular 18 และกลายเป็นค่าเริ่มต้นใน Angular 22 ไวยากรณ์แบบ block นี้ทำให้ template ใกล้เคียงกับโครงสร้างการเขียนโปรแกรมมาตรฐานมากขึ้น พร้อมทั้งเปิดโอกาสให้มีการปรับแต่งประสิทธิภาพได้ดียิ่งขึ้น
รันคำสั่ง ng generate @angular/core:control-flow เพื่อ migrate จาก structural directive ไปยังไวยากรณ์ control flow ใหม่โดยอัตโนมัติ ทั้งสองไวยากรณ์สามารถอยู่ร่วมกันได้ในระหว่างการ migrate แต่ ngIf, ngFor และ ngSwitch ถูก deprecated ตั้งแต่ Angular 19
ทำไม Angular ถึงเปลี่ยนจาก Structural Directive มาเป็นไวยากรณ์ Block
Structural directive ต้องการ import CommonModule ใช้ microsyntax ที่แตกต่างจาก JavaScript และบังคับให้นักพัฒนาต้องครอบเนื้อหาด้วย ng-template สำหรับเงื่อนไขที่ซับซ้อน ไวยากรณ์ control flow ใหม่แก้ปัญหาเหล่านี้:
- ไม่ต้อง import:
@if,@forและ@switchถูก built-in เข้ากับ framework - ไวยากรณ์คล้าย JavaScript: เงื่อนไขและ loop อ่านได้เหมือนโค้ดมาตรฐาน
- รองรับ
@elseแบบ native: ไม่ต้องอ้างอิงng-templateสำหรับ branch else อีกต่อไป - Tree-shaking ดีขึ้น: block control flow ที่ไม่ได้ใช้ไม่มีผลกระทบต่อขนาด bundle
เอกสาร Angular แนะนำให้นำไวยากรณ์ block มาใช้อย่างเต็มที่ในทุกโปรเจกต์ใหม่ และ migrate codebase ที่มีอยู่ทีละขั้น
@if: Conditional Rendering โดยไม่ต้องใช้ ng-template
Block @if render เนื้อหาตามเงื่อนไขโดยอิงจากนิพจน์ที่เป็น truthy ต่างจาก *ngIf ตรงที่รองรับ branch @else if และ @else โดยตรง:
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 }>();
}Block @if ยังรองรับ aliasing ตัวแปรด้วย keyword as ซึ่งดึงค่าจากนิพจน์ที่ซ้อนกัน:
<!-- หลีกเลี่ยงการเข้าถึง property ซ้ำๆ -->
@if (user().profile?.settings?.theme; as theme) {
<p>ธีมปัจจุบัน: {{ theme }}</p>
}รูปแบบนี้มีประโยชน์โดยเฉพาะเมื่อทำงานกับข้อมูล async หรือ object ที่ซ้อนลึก เนื่องจากตัวแปรที่ถูก alias จะถูกกำหนดเฉพาะเมื่อเงื่อนไขเป็น truthy
@for: Iteration พร้อม Track Expression ที่บังคับ
Block @for iterate ผ่าน iterable ใดๆ ของ JavaScript พร้อมการปรับแต่งสำหรับ array ต่างจาก *ngFor ตรงที่ต้องมีนิพจน์ track:
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 ว่าจะระบุตัวตนของแต่ละ item ในแต่ละ re-render อย่างไร การเลือก property tracking ที่ถูกต้องส่งผลโดยตรงต่อประสิทธิภาพ:
| นิพจน์ Track | กรณีใช้งาน | ผลกระทบต่อประสิทธิภาพ |
|---|---|---|
track item.id | Item ที่มี identifier เฉพาะ | ดีที่สุด: อัปเดต DOM น้อยที่สุด |
track $index | รายการคงที่ที่ไม่เคยเรียงลำดับใหม่ | ยอมรับได้: re-render ทั้งหมดเมื่อเรียงลำดับใหม่ |
track item | Tracking แบบ reference | แย่: reference ใหม่ = node DOM ใหม่ |
ตัวแปร Contextual ใน Block @for
Angular จัดเตรียมตัวแปร implicit ภายใน block @for ที่แสดง metadata ของ iteration:
@for (item of items(); track item.id; let i = $index, isLast = $last) {
<li class="item" [class.last]="isLast">
{{ i + 1 }}. {{ item.name }}
</li>
}ตัวแปร contextual ที่มีให้:
$index: ตำแหน่งเริ่มจากศูนย์$count: จำนวน item ทั้งหมด$first,$last: flag boolean สำหรับ item แรก/สุดท้าย$even,$odd: flag boolean ตามความเป็นคู่หรือคี่ของ index
พร้อมที่จะพิชิตการสัมภาษณ์ Angular แล้วหรือยังครับ?
ฝึกฝนด้วยตัวจำลองแบบโต้ตอบ, flashcards และแบบทดสอบเทคนิคครับ
@switch: Conditional Branching แบบ Type-Safe
Block @switch ให้ pattern matching แบบ exhaustive ด้วยการเปรียบเทียบ strict equality (===):
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>();
}ต่างจาก switch ของ JavaScript ตรงที่ @switch ของ Angular ไม่มีพฤติกรรม fallthrough หลายเงื่อนไขที่เล็งไปยัง block เดียวกันต้องใช้คำสั่ง @case ติดต่อกัน:
@switch (userRole()) {
@case ('admin')
@case ('superadmin') {
<app-admin-dashboard />
}
@case ('editor')
@case ('reviewer') {
<app-editor-dashboard />
}
@default {
<app-viewer-dashboard />
}
}การตรวจสอบ Type แบบ Exhaustive ด้วย @default never
Angular 22 รองรับการตรวจสอบ exhaustiveness ในเวลา compile การใช้ @default never; ประกาศว่าไม่ควรมี case ที่เหลืออยู่:
type Theme = 'light' | 'dark' | 'system';
// ข้อผิดพลาด compile ถ้าค่า Theme ขาดหายไปจาก block @case
@switch (theme()) {
@case ('light') { /* ... */ }
@case ('dark') { /* ... */ }
@case ('system') { /* ... */ }
@default never;
}การเพิ่มค่าใหม่เข้า union Theme จะทำให้เกิดข้อผิดพลาด compile บังคับให้นักพัฒนาต้องจัดการทุก case
ไวยากรณ์ Control Flow และการผสานรวมกับ Angular Signals
Block control flow ผสานรวมอย่างราบรื่นกับ Angular Signals ทำให้เกิด reactivity แบบ fine-grained:
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>ไม่มี item ที่ตรงกับตัวกรอง</p>
}
}
`
})
export class DashboardComponent {
items = signal<Item[]>([]);
filter = signal('');
isLoading = signal(true);
hasError = signal(false);
errorMessage = signal('');
// Computed signal คำนวณใหม่เฉพาะเมื่อ dependencies เปลี่ยน
filteredItems = computed(() =>
this.items().filter(item =>
item.name.toLowerCase().includes(this.filter().toLowerCase())
)
);
}เมื่อ signal อัปเดต Angular จะประเมินเฉพาะ block control flow ที่ได้รับผลกระทบใหม่ เมื่อรวมกับ การตรวจจับการเปลี่ยนแปลงแบบ zoneless ของ Angular จะทำให้เกิดการ render ที่มีประสิทธิภาพสูง
คำถามสัมภาษณ์ทั่วไปเกี่ยวกับ Control Flow ของ Angular
การสัมภาษณ์เทคนิคมักทดสอบความเข้าใจเกี่ยวกับไวยากรณ์ control flow โดยเฉพาะความแตกต่างจาก structural directive
คำถามที่ 1: ทำไม track ถึงบังคับใน @for?
นิพจน์ track ให้ identity ที่เสถียรสำหรับแต่ละ item แก่ Angular หากไม่มี Angular จะไม่สามารถระบุได้อย่างมีประสิทธิภาพว่า node DOM ใดที่ต้องสร้าง อัปเดต หรือทำลายเมื่อ collection เปลี่ยน การบังคับ track ทำให้นักพัฒนาต้องตัดสินใจอย่างชัดเจนเกี่ยวกับ identity หลีกเลี่ยงปัญหาประสิทธิภาพของ reference tracking แบบ implicit
คำถามที่ 2: @if แตกต่างจาก *ngIf กับ async pipe อย่างไร?
ทั้งสองสามารถจัดการ observable ได้ แต่ @if กับ signals ให้การเข้าถึงค่าแบบ synchronous:
// กับ *ngIf และ async pipe (legacy)
<div *ngIf="user$ | async as user">{{ user.name }}</div>
// กับ @if และ signals (สมัยใหม่)
@if (user(); as user) {
<div>{{ user.name }}</div>
}แนวทาง signal หลีกเลี่ยงการจัดการ subscription และผสานรวมกับการตรวจจับการเปลี่ยนแปลงของ Angular ได้ดีกว่า
คำถามที่ 3: @switch สามารถแทนที่ chain @if/@else if ที่ซับซ้อนได้หรือไม่?
@switch ควรใช้เมื่อเปรียบเทียบนิพจน์เดียวกับหลายค่าแบบแยกกัน สำหรับเงื่อนไข boolean ที่ซับซ้อนที่เกี่ยวข้องกับนิพจน์หลายตัว @if/@else if ยังคงเหมาะสมกว่า:
<!-- ใช้ @switch สำหรับนิพจน์เดียว หลายค่า -->
@switch (status()) {
@case ('active') { ... }
@case ('inactive') { ... }
}
<!-- ใช้ @if สำหรับหลายนิพจน์ -->
@if (isAdmin() && hasPermission('write')) {
...
} @else if (isEditor()) {
...
}คำถามที่ 4: เกิดอะไรขึ้นกับ @for เมื่อ collection ว่าง?
เมื่อ iterable ไม่มี item Angular จะข้ามเนื้อหา @for ทั้งหมด block @empty ที่เป็นทางเลือกจะ render เนื้อหา fallback ในกรณีนี้ หากไม่มี @empty จะไม่มีอะไร render ซึ่งแตกต่างจากบาง framework ที่ render container ว่าง
การ Migrate จาก Structural Directive ไปยัง Control Flow
Angular CLI จัดเตรียม schematic สำหรับ migration อัตโนมัติ:
# Migrate ทั้งโปรเจกต์
ng generate @angular/core:control-flow
# Migrate directory ที่ระบุ
ng generate @angular/core:control-flow --path=src/app/featuresSchematic จัดการ transformation ส่วนใหญ่โดยอัตโนมัติ แต่ตรวจสอบ output สำหรับกรณีพิเศษ:
| Structural Directive | Control Flow ที่เทียบเท่า |
|---|---|
*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 |
หลังจาก migration ลบ import CommonModule จาก standalone components ที่ไม่ได้ใช้ structural directive อีกต่อไป
เริ่มฝึกซ้อมเลย!
ทดสอบความรู้ของคุณด้วยตัวจำลองสัมภาษณ์และแบบทดสอบเทคนิคครับ
ประเด็นสำคัญสำหรับไวยากรณ์ Control Flow ของ Angular
- ไวยากรณ์ control flow ของ Angular (
@if,@for,@switch) เป็นแนวทางที่แนะนำตั้งแต่ Angular 19 แทนที่ structural directive - นิพจน์
trackใน@forเป็นสิ่งบังคับ: ใช้ identifier เฉพาะเช่นitem.idสำหรับประสิทธิภาพที่ดีที่สุด หลีกเลี่ยงtrack itemที่ทำให้เกิดการ re-render แบบ reference-based @ifรองรับ branch native@else ifและ@elseโดยไม่ต้องใช้ng-templateและ keywordasสร้าง alias ค่า truthy สำหรับใช้ซ้ำ@switchใช้ strict equality และไม่มี fallthrough: คำสั่ง@caseติดต่อกันเล็งไปยัง block เดียวกัน และ@default never;เปิดใช้งานการตรวจสอบ type แบบ exhaustive- Block control flow ผสานรวมกับ Signals สำหรับ reactivity แบบ fine-grained: เฉพาะ block ที่ได้รับผลกระทบเท่านั้นที่ re-render เมื่อ signals อัปเดต
- รัน
ng generate @angular/core:control-flowเพื่อ migrate template ที่มีอยู่โดยอัตโนมัติ
คุณหาบั๊กใน Angular เจอไหม
โค้ดจริงหนึ่งชิ้น บั๊กที่ซ่อนอยู่หนึ่งจุด วันละหนึ่งครั้ง ลองได้โดยไม่ต้องมีบัญชี

เขียนโดย
Anthony Fillion-Mailletผู้ก่อตั้ง SharpSkill
เป็นนักพัฒนาฟูลสแตกมากว่า 10 ปี ดูแล SharpSkill และรับผิดชอบทุกสิ่งที่เผยแพร่ที่นี่
อัปเดตเมื่อ 3 กันยายน 2569
แท็ก
แชร์
บทความที่เกี่ยวข้อง

Dependency Injection ขั้นสูงใน Angular 2026: Providers, Tokens และคำถามสัมภาษณ์
เรียนรู้ระบบ dependency injection ของ Angular อย่างลึกซึ้ง รวมถึงกลยุทธ์ provider, InjectionToken, injector แบบลำดับชั้น และคำถามสัมภาษณ์ที่พบบ่อยสำหรับนักพัฒนา Angular ที่มีประสบการณ์

Angular Signals และ Computed ในปี 2026: Fine-Grained Reactivity และคำถามสัมภาษณ์
เรียนรู้ Angular Signals และ computed signals สำหรับ fine-grained reactivity ใน Angular 20+ คู่มือครบถ้วนครอบคลุม signal primitives, linkedSignal, การทำงานร่วมกับ RxJS และคำถามสัมภาษณ์ทางเทคนิค

คำถามสัมภาษณ์ Angular 19: Signals, SSR และแนวคิดสำคัญที่ต้องรู้
คำถามสัมภาษณ์ Angular 19 ที่พบบ่อยที่สุด: Signals, incremental hydration, zoneless change detection และ API แบบ reactive ใหม่พร้อมตัวอย่างโค้ดและคำตอบที่คาดหวัง