Cú Pháp Control Flow trong Angular 2026: @if, @for, @switch và Câu Hỏi Phỏng Vấn
Tìm hiểu cú pháp control flow Angular (@if, @for, @switch) thay thế các structural directive. Hướng dẫn đầy đủ với ví dụ code, tips migration và câu hỏi phỏng vấn kỹ thuật.

Cú pháp control flow của Angular (@if, @for, @switch) thay thế các structural directive *ngIf, *ngFor, và *ngSwitch đã thống trị template Angular trong nhiều năm. Được giới thiệu trong Angular 17, ổn định từ Angular 18, và trở thành mặc định trong Angular 22, cú pháp dựa trên block này đưa template gần hơn với các cấu trúc lập trình tiêu chuẩn đồng thời cho phép tối ưu hóa hiệu năng tốt hơn.
Chạy ng generate @angular/core:control-flow để tự động migrate từ structural directive sang cú pháp control flow mới. Cả hai cú pháp có thể tồn tại song song trong quá trình migration, nhưng ngIf, ngFor, và ngSwitch đã deprecated từ Angular 19.
Tại Sao Angular Thay Thế Structural Directive Bằng Cú Pháp Block
Structural directive yêu cầu import CommonModule, sử dụng microsyntax khác với JavaScript, và buộc developer phải bọc nội dung trong ng-template cho các điều kiện phức tạp. Cú pháp control flow mới giải quyết những vấn đề này:
- Không cần import:
@if,@for, và@switchđược tích hợp sẵn trong framework - Cú pháp giống JavaScript: điều kiện và vòng lặp đọc như code tiêu chuẩn
- Hỗ trợ native
@else: không cần tham chiếung-templatecho nhánh else - Tree-shaking tốt hơn: các block control flow không sử dụng không ảnh hưởng đến kích thước bundle
Tài liệu Angular khuyến nghị áp dụng hoàn toàn cú pháp block trong tất cả dự án mới, và migrate codebase hiện có dần dần.
@if: Conditional Rendering Không Cần ng-template
Block @if render nội dung có điều kiện dựa trên biểu thức truthy. Khác với *ngIf, nó hỗ trợ trực tiếp các nhánh @else if và @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 cũng hỗ trợ aliasing biến với keyword as, trích xuất giá trị từ các biểu thức lồng nhau:
<!-- Tránh truy cập property lặp lại -->
@if (user().profile?.settings?.theme; as theme) {
<p>Theme hiện tại: {{ theme }}</p>
}Pattern này đặc biệt hữu ích khi làm việc với dữ liệu async hoặc object lồng sâu, vì biến được alias chỉ được định nghĩa khi điều kiện là truthy.
@for: Iteration Với Track Expression Bắt Buộc
Block @for iterate qua bất kỳ iterable JavaScript nào, với tối ưu hóa cho array. Khác với *ngFor, nó yêu cầu biểu thức 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>Không có sản phẩm nào</p>
}
`
})
export class ProductListComponent {
products = input.required<Product[]>();
}Biểu thức track cho Angular biết cách xác định từng item qua các lần re-render. Chọn property tracking đúng ảnh hưởng trực tiếp đến hiệu năng:
| Biểu Thức Track | Trường Hợp Sử Dụng | Ảnh Hưởng Hiệu Năng |
|---|---|---|
track item.id | Item có identifier duy nhất | Tối ưu: cập nhật DOM tối thiểu |
track $index | Danh sách tĩnh không bao giờ sắp xếp lại | Chấp nhận được: re-render hoàn toàn khi sắp xếp lại |
track item | Tracking tham chiếu | Kém: tham chiếu mới = node DOM mới |
Biến Ngữ Cảnh Trong Block @for
Angular cung cấp các biến implicit bên trong block @for để expose 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>
}Các biến ngữ cảnh có sẵn:
$index: vị trí bắt đầu từ 0$count: tổng số item$first,$last: flag boolean cho item đầu/cuối$even,$odd: flag boolean dựa trên tính chẵn lẻ của index
Sẵn sàng chinh phục phỏng vấn Angular?
Luyện tập với mô phỏng tương tác, flashcards và bài kiểm tra kỹ thuật.
@switch: Conditional Branching Type-Safe
Block @switch cung cấp pattern matching exhaustive với so sánh 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">Đang chờ</span>
}
@case ('processing') {
<span class="badge badge-blue">Đang xử lý</span>
}
@case ('shipped') {
<span class="badge badge-yellow">Đã gửi</span>
}
@case ('delivered') {
<span class="badge badge-green">Đã giao</span>
}
}
`
})
export class StatusBadgeComponent {
status = input.required<OrderStatus>();
}Khác với switch của JavaScript, @switch của Angular không có hành vi fallthrough. Nhiều điều kiện nhắm vào cùng block yêu cầu các câu lệnh @case liên tiếp:
@switch (userRole()) {
@case ('admin')
@case ('superadmin') {
<app-admin-dashboard />
}
@case ('editor')
@case ('reviewer') {
<app-editor-dashboard />
}
@default {
<app-viewer-dashboard />
}
}Kiểm Tra Type Exhaustive Với @default never
Angular 22 hỗ trợ kiểm tra exhaustiveness tại thời điểm compile. Sử dụng @default never; khai báo rằng không nên có case còn lại nào:
type Theme = 'light' | 'dark' | 'system';
// Lỗi compile nếu thiếu giá trị Theme từ các block @case
@switch (theme()) {
@case ('light') { /* ... */ }
@case ('dark') { /* ... */ }
@case ('system') { /* ... */ }
@default never;
}Thêm giá trị mới vào union Theme sẽ trigger lỗi compile, buộc developer phải xử lý tất cả các case.
Cú Pháp Control Flow và Tích Hợp Angular Signals
Các block control flow tích hợp liền mạch với Angular Signals, cho phép 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>Không có item nào khớp với bộ lọc</p>
}
}
`
})
export class DashboardComponent {
items = signal<Item[]>([]);
filter = signal('');
isLoading = signal(true);
hasError = signal(false);
errorMessage = signal('');
// Computed signal chỉ tính lại khi dependencies thay đổi
filteredItems = computed(() =>
this.items().filter(item =>
item.name.toLowerCase().includes(this.filter().toLowerCase())
)
);
}Khi signal cập nhật, Angular chỉ đánh giá lại các block control flow bị ảnh hưởng. Kết hợp với change detection zoneless của Angular, điều này cho phép rendering được tối ưu hóa cao.
Câu Hỏi Phỏng Vấn Phổ Biến Về Control Flow Angular
Các cuộc phỏng vấn kỹ thuật thường kiểm tra hiểu biết về cú pháp control flow, đặc biệt là sự khác biệt với structural directive.
Câu Hỏi 1: Tại sao track bắt buộc trong @for?
Biểu thức track cung cấp cho Angular một identity ổn định cho mỗi item. Không có nó, Angular không thể xác định hiệu quả node DOM nào cần tạo, cập nhật, hoặc hủy khi collection thay đổi. Bắt buộc track buộc developer phải đưa ra quyết định rõ ràng về identity, tránh các vấn đề hiệu năng của reference tracking implicit.
Câu Hỏi 2: @if khác *ngIf với async pipe như thế nào?
Cả hai đều có thể xử lý observable, nhưng @if với signals cung cấp truy cập đồng bộ vào giá trị:
// Với *ngIf và async pipe (legacy)
<div *ngIf="user$ | async as user">{{ user.name }}</div>
// Với @if và signals (hiện đại)
@if (user(); as user) {
<div>{{ user.name }}</div>
}Cách tiếp cận signal tránh quản lý subscription và tích hợp tốt hơn với change detection của Angular.
Câu Hỏi 3: @switch có thể thay thế chuỗi @if/@else if phức tạp không?
@switch nên được sử dụng khi so sánh một biểu thức duy nhất với nhiều giá trị rời rạc. Cho các điều kiện boolean phức tạp liên quan đến nhiều biểu thức khác nhau, @if/@else if vẫn phù hợp hơn:
<!-- Sử dụng @switch cho biểu thức đơn, nhiều giá trị -->
@switch (status()) {
@case ('active') { ... }
@case ('inactive') { ... }
}
<!-- Sử dụng @if cho nhiều biểu thức -->
@if (isAdmin() && hasPermission('write')) {
...
} @else if (isEditor()) {
...
}Câu Hỏi 4: Điều gì xảy ra với @for khi collection rỗng?
Khi iterable không chứa item nào, Angular bỏ qua hoàn toàn nội dung @for. Block @empty tùy chọn render nội dung fallback trong trường hợp này. Không có @empty, không có gì được render, điều này khác với một số framework render container rỗng.
Migration Từ Structural Directive Sang Control Flow
Angular CLI cung cấp schematic migration tự động:
# Migrate toàn bộ dự án
ng generate @angular/core:control-flow
# Migrate thư mục cụ thể
ng generate @angular/core:control-flow --path=src/app/featuresSchematic xử lý hầu hết các transformation tự động, nhưng kiểm tra output cho các trường hợp đặc biệt:
| Structural Directive | Control Flow Tương Đương |
|---|---|
*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 |
Sau khi migration, xóa import CommonModule khỏi standalone components không còn sử dụng structural directive.
Bắt đầu luyện tập!
Kiểm tra kiến thức với mô phỏng phỏng vấn và bài kiểm tra kỹ thuật.
Điểm Chính Về Cú Pháp Control Flow Angular
- Cú pháp control flow Angular (
@if,@for,@switch) là cách tiếp cận được khuyến nghị từ Angular 19, thay thế structural directive - Biểu thức
tracktrong@forlà bắt buộc: sử dụng identifier duy nhất nhưitem.idđể có hiệu năng tối ưu, tránhtrack itemgây ra re-render dựa trên reference @ifhỗ trợ native nhánh@else ifvà@elsekhông cầnng-template, và keywordastạo alias giá trị truthy để tái sử dụng@switchsử dụng strict equality và không có fallthrough: các câu lệnh@caseliên tiếp nhắm vào cùng block, và@default never;cho phép kiểm tra type exhaustive- Các block control flow tích hợp với Signals cho reactivity fine-grained: chỉ các block bị ảnh hưởng được re-render khi signals cập nhật
- Chạy
ng generate @angular/core:control-flowđể tự động migrate template hiện có
Bạn có tìm ra lỗi trong Angular không?
Một đoạn mã thật, một lỗi ẩn, mỗi ngày một lượt. Không cần tài khoản để thử.

Viết bởi
Anthony Fillion-MailletNgười sáng lập SharpSkill
Lập trình viên fullstack hơn 10 năm. Anh điều hành SharpSkill và chịu trách nhiệm về mọi nội dung đăng tại đây.
Cập nhật ngày 3 tháng 9, 2026
Thẻ
Chia sẻ
Bài viết liên quan

Dependency Injection Nang Cao trong Angular 2026: Providers, Tokens va Cau Hoi Phong Van
Tim hieu sau ve he thong dependency injection cua Angular bao gom chien luoc provider, InjectionToken, injector phan cap va cac cau hoi phong van thuong gap cho developer Angular co kinh nghiem.

Angular Signals và Computed năm 2026: Reactivity Chi Tiết và Câu Hỏi Phỏng Vấn
Tìm hiểu Angular Signals và computed signals để có reactivity chi tiết trong Angular 20+. Hướng dẫn toàn diện bao gồm signal primitives, linkedSignal, tương tác RxJS và các câu hỏi phỏng vấn kỹ thuật.

Câu hỏi phỏng vấn Angular 19: Signals, SSR và những khái niệm bắt buộc phải biết
Những câu hỏi phỏng vấn Angular 19 phổ biến nhất: Signals, hydration tăng dần, zoneless change detection và các API reactive mới kèm ví dụ code và câu trả lời mong đợi.