Angular 19 Zoneless: ประสิทธิภาพและ Change Detection โดยไม่ต้องใช้ Zone.js
Angular zoneless change detection ลบ Zone.js ออกเพื่อให้ได้ bundle ที่เล็กลง rendering ที่เร็วขึ้น และ reactivity ที่ชัดเจนผ่าน signals คู่มือเชิงลึกเกี่ยวกับการย้ายจาก Zone.js ไปยัง zoneless Angular ตั้งแต่ API experimental ใน Angular 19 จนถึง API stable ใน Angular 20+

Angular 19 Zoneless เป็นหนึ่งในการเปลี่ยนแปลงที่สำคัญที่สุดของ Angular framework ในรอบหลายปี การเปลี่ยนผ่านจาก Zone.js ไปสู่ระบบ Change Detection แบบใหม่ที่ขับเคลื่อนด้วย Signals ถือเป็นก้าวกระโดดครั้งใหญ่ในด้านประสิทธิภาพและความเรียบง่ายของแอปพลิเคชัน บทความนี้จะอธิบายรายละเอียดทั้งหมดเกี่ยวกับ Zoneless Angular ตั้งแต่แนวคิดพื้นฐาน การตั้งค่า การย้ายโค้ดเดิม ไปจนถึงแนวทางปฏิบัติที่ดีที่สุดสำหรับการพัฒนาแอปพลิเคชันยุคใหม่
Zoneless Change Detection เปิดตัวครั้งแรกในรูปแบบ experimental API ตั้งแต่ Angular 18 และได้รับการพัฒนาต่อเนื่องใน Angular 19 โดยคาดว่าจะเป็น stable API อย่างเป็นทางการตั้งแต่ Angular 20 เป็นต้นไป นักพัฒนาสามารถเริ่มทดลองใช้งานได้ตั้งแต่วันนี้เพื่อเตรียมพร้อมสำหรับอนาคต
Zone.js คืออะไร และทำไมจึงต้องเปลี่ยน
Zone.js เป็นไลบรารีที่ทำหน้าที่เป็นหัวใจของระบบ Change Detection ใน Angular มาตั้งแต่ Angular 2 หลักการทำงานของ Zone.js คือการ "patch" หรือครอบคลุม Browser API กว่า 130 รายการ ไม่ว่าจะเป็น setTimeout, Promise, addEventListener, XMLHttpRequest และอื่น ๆ อีกมากมาย เมื่อใดก็ตามที่เกิด asynchronous callback ขึ้น Zone.js จะแจ้งให้ Angular ทราบว่าอาจมีการเปลี่ยนแปลงข้อมูล ส่งผลให้ Angular ต้องรัน Change Detection ทั่วทั้ง component tree
แม้ว่าแนวทางนี้จะทำให้การพัฒนาแอปพลิเคชันเป็นเรื่องสะดวก เนื่องจากนักพัฒนาไม่ต้องบอก Angular เองว่าข้อมูลเปลี่ยนเมื่อใด แต่ก็มาพร้อมกับต้นทุนที่สูง ทั้งขนาด bundle ที่เพิ่มขึ้น จำนวนรอบ Change Detection ที่มากเกินความจำเป็น และ stack trace ที่อ่านยาก
การตั้งค่าแบบดั้งเดิมที่ใช้ Zone.js มีลักษณะดังนี้:
import { ApplicationConfig } from '@angular/core';
import { provideZoneChangeDetection } from '@angular/core';
export const appConfig: ApplicationConfig = {
providers: [
provideZoneChangeDetection({ eventCoalescing: true }),
// Zone.js patches ~130+ browser APIs
// Every async callback triggers change detection
]
};ปัญหาหลักของ Zone.js สามารถสรุปได้ดังนี้ ประการแรก ขนาด bundle เพิ่มขึ้นประมาณ 33KB (raw) หรือ 10KB เมื่อ gzip แล้ว ประการที่สอง ทุก asynchronous operation จะ trigger Change Detection แม้ว่าจะไม่มีข้อมูลใดเปลี่ยนแปลงเลย ประการที่สาม stack trace จะมี Zone frames เพิ่มขึ้น 8-12 ชั้น ทำให้การ debug เป็นเรื่องยุ่งยาก และประการสุดท้าย การ patch Browser API อาจสร้างปัญหาความเข้ากันได้กับไลบรารีภายนอกบางตัว
การเปิดใช้งาน Zoneless Change Detection
การเปลี่ยนไปใช้ Zoneless นั้นเริ่มต้นได้ไม่ยาก สำหรับ Angular 19 ซึ่งยังอยู่ในขั้น experimental ให้ใช้ API ดังนี้:
import { ApplicationConfig } from '@angular/core';
import { provideExperimentalZonelessChangeDetection } from '@angular/core';
export const appConfig: ApplicationConfig = {
providers: [
provideExperimentalZonelessChangeDetection(),
// No more Zone.js patching
]
};สำหรับ Angular 20 ขึ้นไป API จะเป็น stable version โดยไม่ต้องมีคำว่า Experimental นำหน้า:
import { ApplicationConfig } from '@angular/core';
import { provideZonelessChangeDetection } from '@angular/core';
export const appConfig: ApplicationConfig = {
providers: [
provideZonelessChangeDetection(),
]
};หลังจากเปลี่ยน configuration แล้ว ขั้นตอนสำคัญถัดมาคือการลบ Zone.js ออกจากโปรเจกต์อย่างสมบูรณ์:
# Remove zone.js polyfill from angular.json build and test targets
# Then uninstall the package
npm uninstall zone.jsต้องไม่ลืมลบ zone.js ออกจาก polyfills ใน angular.json ด้วยทั้งในส่วน build และ test targets มิฉะนั้น Zone.js จะยังคงถูกโหลดแม้ว่าจะไม่ได้ใช้งานแล้ว
Signals: หัวใจของ Zoneless Architecture
ระบบ Zoneless ใช้ Angular Signals เป็นกลไกหลักในการแจ้ง framework ว่าข้อมูลมีการเปลี่ยนแปลง Signals เป็น reactive primitive ที่ถูกออกแบบมาโดยเฉพาะสำหรับ Angular ซึ่งทำงานแตกต่างจาก Zone.js อย่างสิ้นเชิง แทนที่จะตรวจสอบทุก asynchronous event แล้วรัน Change Detection ทั่วทั้ง tree Signals จะแจ้ง Angular เฉพาะเมื่อค่าที่ template ใช้งานจริงมีการเปลี่ยนแปลง ทำให้ Angular รู้ได้อย่างแม่นยำว่า component ใดต้องอัปเดต
ตัวอย่างการใช้ Signals ใน component อย่างง่ายมีดังนี้:
import { Component, signal, computed } from '@angular/core';
@Component({
selector: 'app-counter',
template: `
<div class="counter">
<button (click)="decrement()">-</button>
<span>{{ count() }}</span>
<button (click)="increment()">+</button>
<p>Double: {{ doubled() }}</p>
</div>
`
})
export class CounterComponent {
// Signal updates automatically notify the template
count = signal(0);
doubled = computed(() => this.count() * 2);
increment() {
this.count.update(v => v + 1);
// No markForCheck() needed - signal handles notification
}
decrement() {
this.count.update(v => v - 1);
}
}จุดที่น่าสนใจคือ computed() ซึ่งเป็น derived signal ที่คำนวณค่าใหม่โดยอัตโนมัติเมื่อ signal ต้นทางเปลี่ยนแปลง และที่สำคัญไม่จำเป็นต้องเรียก markForCheck() หรือ detectChanges() เลย เพราะ Signals จัดการทุกอย่างให้แล้ว
นักพัฒนาหลายคนอาจคุ้นเคยกับ OnPush Change Detection Strategy ที่ช่วยลดจำนวนรอบ Change Detection อยู่แล้ว ในโหมด Zoneless นั้น OnPush ยังคงทำงานได้ตามปกติ แต่จะยิ่งมีประสิทธิภาพมากขึ้นเมื่อใช้ร่วมกับ Signals เพราะ framework จะรู้ได้อย่างแม่นยำว่า component ใดต้องตรวจสอบ โดยไม่ต้องพึ่งพา Zone.js ในการ trigger อีกต่อไป ทำให้แม้แต่ component ที่ใช้ Default strategy ก็จะได้รับประโยชน์จาก Zoneless เช่นกัน
การย้าย Component เดิมมาใช้ Signals
ปัญหาที่พบบ่อยที่สุดเมื่อเปลี่ยนมาใช้ Zoneless คือ component เดิมที่พึ่งพา Zone.js ในการตรวจจับการเปลี่ยนแปลง ลองดูตัวอย่างโค้ดที่มีปัญหา:
@Component({
selector: 'app-user-status',
template: `<span>{{ statusMessage }}</span>`
})
export class UserStatusComponent {
statusMessage = 'Loading...';
ngOnInit() {
setTimeout(() => {
// Zone.js would trigger CD here - zoneless does NOT
this.statusMessage = 'Ready';
}, 2000);
}
}ในโค้ดด้านบน เมื่อ setTimeout callback ทำงาน ค่า statusMessage จะถูกเปลี่ยนเป็น 'Ready' ในโหมดที่ใช้ Zone.js Angular จะรู้โดยอัตโนมัติเพราะ Zone.js patch setTimeout ไว้ แต่ในโหมด Zoneless การเปลี่ยนค่า property ธรรมดาจะไม่ถูกตรวจจับ ส่งผลให้หน้าจอไม่อัปเดต
วิธีแก้ไขคือเปลี่ยนมาใช้ Signals:
import { Component, signal } from '@angular/core';
@Component({
selector: 'app-user-status',
template: `<span>{{ statusMessage() }}</span>`
})
export class UserStatusComponent {
statusMessage = signal('Loading...');
ngOnInit() {
setTimeout(() => {
// Signal update notifies Angular automatically
this.statusMessage.set('Ready');
}, 2000);
}
}ความแตกต่างหลักมีสองจุด ประการแรก statusMessage เปลี่ยนจาก string ธรรมดาเป็น signal('Loading...') ประการที่สอง ใน template ต้องเรียกเป็น function statusMessage() แทน statusMessage การเปลี่ยนแปลงเพียงเล็กน้อยนี้ทำให้ Angular รับรู้การเปลี่ยนแปลงได้ทันทีโดยไม่ต้องพึ่ง Zone.js
การใช้ Reactive Forms กับ Zoneless
Reactive Forms เป็นอีกส่วนหนึ่งที่ต้องปรับตัวเมื่อเปลี่ยนมาใช้ Zoneless เนื่องจาก FormControl ใช้ RxJS Observable ภายใน ซึ่งไม่ได้ notify Angular โดยตรงในโหมด Zoneless ทางออกคือการใช้ toSignal() จาก @angular/core/rxjs-interop เพื่อแปลง Observable เป็น Signal:
import { Component, inject, signal } from '@angular/core';
import { FormControl, ReactiveFormsModule } from '@angular/forms';
import { toSignal } from '@angular/core/rxjs-interop';
import { debounceTime, distinctUntilChanged } from 'rxjs';
@Component({
selector: 'app-search',
imports: [ReactiveFormsModule],
template: `
<input [formControl]="searchControl" placeholder="Search..." />
<p>Results for: {{ searchTerm() }}</p>
`
})
export class SearchComponent {
searchControl = new FormControl('');
// Convert observable to signal for automatic template updates
searchTerm = toSignal(
this.searchControl.valueChanges.pipe(
debounceTime(300),
distinctUntilChanged()
),
{ initialValue: '' }
);
}ฟังก์ชัน toSignal() เป็นสะพานเชื่อมระหว่าง RxJS และ Signals ที่ช่วยให้นักพัฒนาสามารถใช้ operator ที่คุ้นเคยอย่าง debounceTime และ distinctUntilChanged ได้ตามปกติ พร้อมกับได้ประโยชน์จาก Signal-based Change Detection
พร้อมที่จะพิชิตการสัมภาษณ์ Angular แล้วหรือยังครับ?
ฝึกฝนด้วยตัวจำลองแบบโต้ตอบ, flashcards และแบบทดสอบเทคนิคครับ
การจัดการ Async Data และ SSR ด้วย PendingTasks
สำหรับแอปพลิเคชันที่ใช้ Server-Side Rendering (SSR) การโหลดข้อมูลแบบ asynchronous ในโหมด Zoneless มีความท้าทายเพิ่มเติม เนื่องจากไม่มี Zone.js คอยติดตาม pending tasks อีกต่อไป Angular จึงจัดเตรียม PendingTasks service มาให้ใช้งาน ซึ่งทำหน้าที่บอก framework ว่ายังมีงานที่ต้องรอให้เสร็จก่อนที่จะ serialize HTML ส่งไปยัง client:
import { Component, inject, signal } from '@angular/core';
import { PendingTasks } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { firstValueFrom } from 'rxjs';
@Component({
selector: 'app-data-loader',
template: `
@if (data()) {
<div>{{ data()!.title }}</div>
} @else {
<div>Loading...</div>
}
`
})
export class DataLoaderComponent {
private http = inject(HttpClient);
private pendingTasks = inject(PendingTasks);
data = signal<{ title: string } | null>(null);
ngOnInit() {
// PendingTasks.run() prevents SSR serialization until complete
this.pendingTasks.run(async () => {
const result = await firstValueFrom(
this.http.get<{ title: string }>('/api/data')
);
this.data.set(result);
});
}
}PendingTasks.run() รับ async function แล้วติดตามว่างานนั้นเสร็จสิ้นหรือยัง ซึ่งมีความสำคัญอย่างยิ่งสำหรับ SSR เพราะหากไม่ใช้ PendingTasks เซิร์ฟเวอร์อาจส่ง HTML ที่ยังแสดง "Loading..." ออกไปแทนที่จะรอให้ข้อมูลโหลดเสร็จก่อน
ตารางเปรียบเทียบประสิทธิภาพ
ผลลัพธ์ด้านประสิทธิภาพจากการเปลี่ยนไปใช้ Zoneless นั้นน่าประทับใจอย่างมาก:
| Metric | Zone.js | Zoneless | Improvement | |--------|---------|----------|-------------| | Initial bundle size | +33KB raw / +10KB gzip | 0KB overhead | 100% reduction | | Change detection cycles (typical app) | 150-300 per interaction | 5-15 per interaction | 80-95% fewer | | Stack trace depth | 8-12 extra Zone frames | Clean native traces | Immediate clarity | | Time to Interactive (TTI) | Baseline | 15-25% faster | Zone bootstrap eliminated |
ตัวเลขเหล่านี้แสดงให้เห็นว่า Zoneless ไม่ได้เป็นเพียงการปรับปรุงเล็กน้อย แต่เป็นการเปลี่ยนแปลงเชิงโครงสร้างที่ส่งผลกระทบอย่างมีนัยสำคัญต่อประสิทธิภาพของแอปพลิเคชัน โดยเฉพาะอย่างยิ่งแอปพลิเคชันขนาดใหญ่ที่มี component จำนวนมาก การลดจำนวนรอบ Change Detection ลง 80-95% หมายความว่า CPU ทำงานน้อยลงอย่างมาก ส่งผลให้แอปพลิเคชันตอบสนองได้รวดเร็วขึ้นและใช้แบตเตอรี่น้อยลงบนอุปกรณ์เคลื่อนที่
ไลบรารีบางตัวอาจยังพึ่งพา Zone.js ในการทำงาน โดยเฉพาะไลบรารีที่ใช้ setTimeout หรือ setInterval ภายในเพื่อ trigger การอัปเดต UI ก่อนเปลี่ยนไปใช้ Zoneless อย่างสมบูรณ์ ควรตรวจสอบว่าไลบรารีที่ใช้งานอยู่ทั้งหมดรองรับ Signal-based Change Detection หรือมีแผนที่จะรองรับในอนาคตอันใกล้ ไลบรารียอดนิยมอย่าง Angular Material และ Angular CDK รองรับ Zoneless ได้อย่างเต็มที่แล้ว
แนวทางปฏิบัติที่ดีที่สุดในการย้ายไปใช้ Zoneless
การย้ายโปรเจกต์ขนาดใหญ่ไปใช้ Zoneless ไม่จำเป็นต้องทำทั้งหมดในครั้งเดียว Angular ออกแบบมาให้รองรับการย้ายแบบค่อยเป็นค่อยไป โดยมีแนวทางดังนี้
ขั้นตอนแรก ให้เริ่มเปิดใช้งาน provideExperimentalZonelessChangeDetection() ในโปรเจกต์โดยยังคง Zone.js ไว้ ซึ่ง Angular จะทำงานในโหมด hybrid ที่รองรับทั้ง Zone.js และ Signals พร้อมกัน จากนั้นค่อย ๆ แปลง component ทีละตัวให้ใช้ Signals แทน property binding แบบเดิม เมื่อมั่นใจว่า component ทั้งหมดทำงานได้ถูกต้องแล้ว จึงลบ Zone.js ออก
สำหรับ component ที่มี property assignment แบบตรง ๆ เช่น this.title = 'New Title' ให้เปลี่ยนเป็น this.title.set('New Title') สำหรับ component ที่ใช้ @Input() แบบเดิม ให้พิจารณาเปลี่ยนมาใช้ input() function ที่เป็น signal-based input ซึ่งเปิดตัวใน Angular 17.1 และสำหรับ component ที่ใช้ @Output() ให้พิจารณาใช้ output() function แทน
การเตรียมพร้อมสำหรับ Angular 20 และอนาคต
ทีมพัฒนา Angular ได้ประกาศชัดเจนว่า Zoneless จะเป็นค่าเริ่มต้นของโปรเจกต์ใหม่ตั้งแต่ Angular 20 เป็นต้นไป นั่นหมายความว่า Zone.js จะค่อย ๆ ลดบทบาทลงและอาจถูก deprecate ในเวอร์ชันอนาคต การเริ่มต้นย้ายตั้งแต่วันนี้จึงเป็นการลงทุนที่คุ้มค่า
ลิงก์ที่เกี่ยวข้องสำหรับการศึกษาเพิ่มเติมเกี่ยวกับ Angular Change Detection และการเตรียมตัวสัมภาษณ์:
สรุป
Angular 19 Zoneless เป็นจุดเปลี่ยนสำคัญที่นำ Angular เข้าสู่ยุคใหม่ของ reactive programming โดยสรุปประเด็นสำคัญได้ดังนี้:
- ลบ Zone.js ออกอย่างสมบูรณ์ ช่วยลดขนาด bundle ลง 33KB และกำจัดการ patch Browser API กว่า 130 รายการ
- Signal-based Change Detection ทำให้ Angular รู้ได้อย่างแม่นยำว่า component ใดต้องอัปเดต ลดจำนวนรอบ Change Detection ลง 80-95%
- ประสิทธิภาพที่ดีขึ้นอย่างชัดเจน ทั้ง Time to Interactive ที่เร็วขึ้น 15-25% และ stack trace ที่สะอาดขึ้น
- PendingTasks สำหรับ SSR เป็นกลไกทดแทน Zone.js ในการติดตาม async operations สำหรับ Server-Side Rendering
- การย้ายแบบค่อยเป็นค่อยไป สามารถเปิดใช้งาน Zoneless พร้อมกับ Zone.js ในช่วงเปลี่ยนผ่าน แล้วค่อย ๆ ย้าย component ทีละส่วน
- toSignal() เชื่อม RxJS กับ Signals ทำให้สามารถใช้ operator ของ RxJS ที่คุ้นเคยร่วมกับระบบ Signals ได้อย่างลงตัว
- อนาคตของ Angular Zoneless จะเป็นค่าเริ่มต้นตั้งแต่ Angular 20 การเริ่มย้ายตั้งแต่วันนี้ถือเป็นการเตรียมพร้อมที่ชาญฉลาด
การเปลี่ยนแปลงครั้งนี้ไม่ได้เป็นเพียงการปรับปรุงด้านเทคนิคเท่านั้น แต่ยังเป็นการปรับเปลี่ยนวิธีคิดในการพัฒนาแอปพลิเคชัน Angular โดยรวม จาก "ให้ Zone.js จัดการทุกอย่าง" ไปสู่ "ระบุอย่างชัดเจนว่าอะไรเปลี่ยน" ซึ่งส่งผลให้โค้ดมีความชัดเจน ทดสอบง่าย และมีประสิทธิภาพสูงขึ้นอย่างมีนัยสำคัญ
เริ่มฝึกซ้อมเลย!
ทดสอบความรู้ของคุณด้วยตัวจำลองสัมภาษณ์และแบบทดสอบเทคนิคครับ
แท็ก
แชร์
บทความที่เกี่ยวข้อง

Angular Standalone Components: คู่มือการย้ายระบบและแนวทางปฏิบัติที่ดีที่สุดในปี 2026
คู่มือการย้ายระบบ Angular standalone components อย่างละเอียด ขั้นตอนการลบ NgModules เปิดใช้งาน lazy loading และนำ standalone API มาใช้ใน Angular 21

Angular 18: Signals และฟีเจอร์ใหม่
เรียนรู้ Angular 18 Signals, การตรวจจับการเปลี่ยนแปลงแบบ zoneless และ API ใหม่ที่ใช้ signal สำหรับสร้างแอปพลิเคชันที่มีประสิทธิภาพสูงขึ้น

25 คำถามสัมภาษณ์ Angular ยอดนิยม: คู่มือฉบับสมบูรณ์สู่ความสำเร็จ
25 คำถามสัมภาษณ์ Angular ที่ถูกถามมากที่สุดในปี 2026 พร้อมคำตอบโดยละเอียด ตัวอย่างโค้ด และเคล็ดลับเพื่อคว้าตำแหน่งนักพัฒนา Angular