# Angular @defer ในปี 2026: Declarative Lazy Loading สำหรับแอปพลิเคชันที่เร็วขึ้น
> เจาะลึก Angular @defer blocks สำหรับ declarative lazy loading พร้อม triggers ทุกประเภท prefetching, incremental hydration, พฤติกรรมบน SSR และรูปแบบการใช้งานจริงเพื่อเพิ่มประสิทธิภาพ
- Published: 2026-06-14
- Updated: 2026-06-14
- Author: SharpSkill
- Tags: angular, performance, lazy-loading
- Reading time: 8 min
---
Angular `@defer` blocks แก้ปัญหาที่ router-based lazy loading ไม่เคยทำได้มาก่อน นั่นคือการโหลด components, directives และ pipes แต่ละตัวตามความต้องการ โดยไม่จำเป็นต้องปรับโครงสร้างแอปพลิเคชันใหม่ให้แยกเป็น routes ต่างหาก ฟีเจอร์นี้เปิดตัวใน Angular 17 และผ่านการพัฒนาจนเสถียรใน Angular 22 ทำให้สิ่งที่เคยต้องใช้ dynamic imports, `*ngIf` flags และการตั้งค่า webpack configuration กลายเป็น declarative template block เพียงบล็อกเดียว
> **@defer ทำอะไรได้บ้าง**
>
> `@defer` สั่งให้ Angular compiler แยก components ที่ถูกครอบไว้ออกเป็น JavaScript chunks แยกต่างหาก โดยจะโหลดเฉพาะเมื่อเงื่อนไข trigger ถูกกระตุ้นเท่านั้น เบราว์เซอร์ดาวน์โหลด JavaScript น้อยลงตั้งแต่เริ่มต้น ช่วยปรับปรุงค่า Largest Contentful Paint (LCP) และ Time to First Byte (TTFB) โดยไม่ต้องตั้งค่า code-splitting ด้วยตนเอง
## กลไกการทำงานภายในของ @defer
เมื่อ Angular compiler พบ `@defer` block จะทำการแยก standalone component, directive และ pipe ทุกตัวที่อยู่ภายในออกเป็น chunk แยกต่างหาก main bundle จะถูกส่งไปยังเบราว์เซอร์โดยไม่รวม dependencies เหล่านี้ เมื่อถึง runtime Angular จะประเมินเงื่อนไข trigger และดึง chunk ผ่าน dynamic `import()` call
บล็อกนี้รองรับ sub-blocks สี่ประเภทที่ควบคุมสิ่งที่ผู้ใช้เห็นตลอดวงจรชีวิตการโหลด:
```typescript
// app.component.html
@defer (on viewport) {
} @placeholder {
} @loading (minimum 200ms) {
} @error {
Failed to load the dashboard. Please refresh.
}
```
`@placeholder` แสดงผลก่อนที่ trigger จะทำงาน `@loading` ปรากฏขึ้นขณะที่ chunk กำลังดาวน์โหลด โดยมีพารามิเตอร์ `minimum` เพื่อกำหนดระยะเวลาขั้นต่ำในการแสดงผลและป้องกันการกะพริบ `@error` รับมือกับความล้มเหลวของเครือข่ายหรือข้อผิดพลาดของ chunk พารามิเตอร์ `minimum` บน `@loading` ป้องกัน spinner กะพริบสั้นเกินไปเมื่อ chunk โหลดจาก cache ได้อย่างรวดเร็ว
ข้อจำกัดที่สำคัญคือ dependency ทุกตัวภายใน `@defer` ต้องเป็น standalone เท่านั้น components ที่ไม่ใช่ standalone ซึ่งประกาศใน `NgModule` ไม่สามารถ defer ได้และจะถูกโหลดแบบ eagerly โดยไม่คำนึงถึง `@defer` wrapper ที่ครอบอยู่
## ประเภทของ Trigger: การควบคุมจังหวะการโหลด Component
Angular จัดเตรียม trigger ในตัวไว้เจ็ดประเภท แต่ละประเภทมุ่งเป้าไปที่กลยุทธ์การโหลดที่แตกต่างกัน triggers หลายตัวสามารถรวมกันด้วย semicolons โดยประเมินเป็นเงื่อนไข OR
```typescript
// product-page.component.html
// Loads when the browser goes idle (default)
@defer {
}
// Loads when the element enters the viewport
@defer (on viewport) {
}
// Loads on click or keydown
@defer (on interaction) {
} @placeholder {
}
// Loads on mouseenter or focusin
@defer (on hover) {
}
// Loads after 3 seconds
@defer (on timer(3s)) {
}
// Loads immediately after initial render
@defer (on immediate) {
}
```
Trigger `on viewport` ใช้ Intersection Observer API ภายในเพื่อตรวจจับว่า element เลื่อนเข้าสู่พื้นที่ที่มองเห็นบนหน้าจอ `on idle` อาศัย `requestIdleCallback` ทำให้เป็นค่าเริ่มต้นที่ปลอดภัยที่สุดสำหรับเนื้อหาที่อยู่ด้านล่างของหน้าจอ `on timer` รับค่าระยะเวลาในหน่วยมิลลิวินาที (`500ms`) หรือวินาที (`3s`)
### Trigger `when` สำหรับเงื่อนไขแบบ Reactive
นอกเหนือจาก trigger ที่อิงกับ event แล้ว `when` ยังรับ boolean expression ใดก็ได้ รวมถึงการอ่านค่า signal:
```typescript
// dashboard.component.ts
export class DashboardComponent {
showAdvanced = signal(false);
}
// dashboard.component.html
@defer (when showAdvanced()) {
} @placeholder {
}
```
การรวม `on` กับ `when` เข้าด้วยกันถูกต้องตามไวยากรณ์ Angular ถือว่าเป็นเงื่อนไข OR กล่าวคือบล็อกจะโหลดเมื่อเงื่อนไขใดเงื่อนไขหนึ่งเป็นจริง
## Prefetching: การแยกการดาวน์โหลดออกจากการแสดงผล
Prefetching แยกจังหวะที่ chunk ถูกดาวน์โหลดออกจากจังหวะที่ component ถูก render ช่วยขจัดความรู้สึกล่าช้าของผู้ใช้โดยการดึง JavaScript ล่วงหน้าก่อนที่ผู้ใช้จะกระตุ้น trigger จริง
```typescript
// settings.component.html
@defer (on interaction; prefetch on idle) {
} @placeholder {
}
```
ในรูปแบบนี้เบราว์เซอร์จะดาวน์โหลด chunk ของ `account-settings` ในช่วง idle time เมื่อผู้ใช้คลิก component จะ render ทันทีเพราะโค้ดพร้อมใช้งานแล้ว Prefetching รองรับ trigger types เดียวกันกับ `on` clause หลัก
รูปแบบที่นิยมใช้สำหรับ dashboards คือ prefetch components ที่มีน้ำหนักมากในช่วง idle ขณะที่เลื่อนการ render ไปจนกว่าจะเข้าสู่ viewport:
```typescript
// analytics.component.html
@defer (on viewport; prefetch on idle) {
} @placeholder (minimum 100ms) {
}
```
## @defer กับ Server-Side Rendering
บนฝั่ง server `@defer` blocks จะ render เนื้อหาของ `@placeholder` เป็นค่าเริ่มต้น deferred component จะไม่ถูก render บน server เลย พฤติกรรมนี้เป็นไปตามเจตนา เนื่องจากการดาวน์โหลด chunk มีความหมายเฉพาะในบริบทของเบราว์เซอร์เท่านั้น
สำหรับเนื้อหาที่สำคัญต่อ SEO หมายความว่า `@placeholder` ต้องมี HTML ที่มีความหมาย ไม่ใช่เพียงแค่ spinner:
```typescript
// blog-post.component.html
@defer (on viewport) {
} @placeholder {
Comments
Loading comments...
}
```
Search engines จะ index เนื้อหาของ placeholder ระหว่าง SSR การใช้ placeholder ที่มีโครงสร้าง semantic ชัดเจนช่วยรักษาคุณภาพของหน้าเว็บให้สมบูรณ์ทั้งในแง่ของ SEO และ accessibility
## Incremental Hydration ร่วมกับ @defer
Angular 19 เปิดตัว incremental hydration ในสถานะ developer preview และ Angular 22 ทำให้ฟีเจอร์นี้เสถียรพร้อมสำหรับ production Incremental hydration ขยายความสามารถของ `@defer` ด้วย `hydrate` trigger ที่ควบคุมช่วงเวลาที่ component ซึ่ง render บน server จะกลายเป็น interactive บน client
```typescript
// product-page.component.html
@defer (hydrate on viewport) {
}
@defer (hydrate on interaction) {
}
```
สิ่งที่แตกต่างจาก `@defer` แบบมาตรฐานคือ incremental hydration จะ render HTML ของ component อย่างสมบูรณ์บน server ฝั่ง client ได้รับ markup ที่ครบถ้วนแต่ข้ามการ hydrate component ไปจนกว่า trigger จะทำงาน ผลลัพธ์คือเบราว์เซอร์ paint หน้าจอทั้งหมดได้ทันที แต่การ execute JavaScript ถูกเลื่อนออกไป
## รูปแบบการใช้งานจริงและกลยุทธ์ด้าน Performance
### รูปแบบที่ 1: Dashboard ขนาดใหญ่ที่มีหลาย Deferred Panels
```typescript
// dashboard.component.html
@defer (on viewport; prefetch on idle) {
} @placeholder {
}
@defer (on viewport; prefetch on idle) {
} @placeholder {
}
@defer (on interaction) {
} @placeholder {
}
```
แต่ละ panel โหลดอย่างอิสระจากกัน export panel จะไม่ถูกโหลดจนกว่าผู้ใช้จะร้องขออย่างชัดเจน ช่วยประหยัดการดาวน์โหลด chunk ทั้ง chunk สำหรับผู้ใช้ที่ไม่เคยใช้ฟังก์ชันส่งออกข้อมูล
### รูปแบบที่ 2: การโหลดฟีเจอร์ตามเงื่อนไขด้วย Signals
```typescript
// editor.component.ts
export class EditorComponent {
user = inject(UserService).currentUser;
isPremium = computed(() => this.user()?.plan === 'premium');
}
// editor.component.html
@defer (when isPremium()) {
} @placeholder {
}
```
ผู้ใช้ระดับ free-tier จะไม่ดาวน์โหลด chunk ของ AI assistant เลย upgrade banner ทำหน้าที่เป็นทั้ง placeholder และสื่อส่งเสริมการอัปเกรดแพ็คเกจ
### การหลีกเลี่ยงข้อผิดพลาดที่พบบ่อย
การซ้อน `@defer` blocks ที่ใช้ trigger เดียวกันจะทำให้ chunks ถูกดาวน์โหลดพร้อมกัน ควรกระจาย triggers ให้แตกต่างกันระหว่างบล็อกที่ซ้อนกัน:
```typescript
// Avoid: both fire on idle simultaneously
@defer {
@defer {
}
}
// Better: outer on idle, inner on viewport
@defer (on idle) {
}
// Inside wrapper-component template:
@defer (on viewport) {
}
```
ข้อผิดพลาดอีกประการคือการใช้ `@defer` กับ components ที่อยู่ใน critical rendering path การ defer เนื้อหา above-the-fold จะทำให้ LCP แย่ลงเพราะเบราว์เซอร์ต้องดาวน์โหลดและ execute chunk ก่อนจึงจะ paint ได้ ควรสงวน `@defer` ไว้สำหรับเนื้อหา below-the-fold หรือเนื้อหาที่ถูกกระตุ้นโดยผู้ใช้
## @defer เทียบกับ Router-Based Lazy Loading
Router-based lazy loading และ `@defer` เป็นเทคนิคที่เสริมซึ่งกันและกัน Router lazy loading แบ่งในระดับ route โดย defer feature modules ทั้งหมด ส่วน `@defer` แบ่งภายใน template โดย defer components แต่ละตัว
| Aspect | Router Lazy Loading | @defer |
|--------|-------------------|--------|
| Granularity | Route level | Component level |
| Trigger | Navigation event | viewport, idle, interaction, hover, timer, condition |
| SSR behavior | Full server render | Placeholder on server |
| Requires standalone | No (works with NgModules) | Yes (standalone only) |
| Prefetching | `preloadingStrategy` | `prefetch on` clause |
| Use case | Feature modules, pages | Widgets, panels, conditional UI |
แอปพลิเคชันทั่วไปจะใช้ทั้งสองเทคนิคร่วมกัน: router lazy loading สำหรับ feature areas ระดับบนสุด และ `@defer` สำหรับ components ที่มีน้ำหนักมากภายใน feature areas เหล่านั้น
## คำถามสัมภาษณ์งานเกี่ยวกับ Angular @defer
การสัมภาษณ์งานด้านเทคนิคมีแนวโน้มครอบคลุม `@defer` มากขึ้น เนื่องจากฟีเจอร์นี้กลายเป็นหัวใจสำคัญของกลยุทธ์ performance ใน Angular ต่อไปนี้คือรูปแบบคำถามที่ปรากฏบ่อยในบริบทของ[คำถามสัมภาษณ์ Angular](/technologies/angular/interview-questions/standalone-components)
**ถาม: จะเกิดอะไรขึ้นเมื่อวาง component ที่ไม่ใช่ standalone ไว้ภายใน @defer?**
Component จะถูกโหลดแบบ eagerly โดยไม่คำนึงถึง `@defer` block Angular compiler ไม่สามารถแยก components ที่ประกาศใน NgModule ออกเป็น chunks แยกต่างหากได้ ไม่มี error ถูก throw แต่การ optimization จะล้มเหลวอย่างเงียบๆ
**ถาม: @defer ทำงานอย่างไรระหว่าง SSR?**
Server จะ render เนื้อหาของ `@placeholder` เท่านั้น deferred component จะไม่ถูก render บน server เลย Hydration จะเกิดขึ้นในฝั่ง client เมื่อ trigger ทำงาน (หรือผ่าน `hydrate on` สำหรับ incremental hydration)
**ถาม: @defer สามารถใช้ร่วมกับ OnPush change detection ได้หรือไม่?**
ได้ `@defer` เป็นฟีเจอร์ระดับ template และทำงานร่วมกับ change detection strategy ใดก็ได้ เมื่อ deferred component ถูกโหลดแล้ว จะทำงานตามการตั้งค่า change detection ของตัวเอง
**ถาม: ความแตกต่างระหว่าง `on idle` กับ `on immediate` คืออะไร?**
`on idle` รอจนกว่าเบราว์เซอร์จะทำงานที่ค้างอยู่เสร็จทั้งหมดผ่าน `requestIdleCallback` ส่วน `on immediate` trigger ทันทีหลังจาก Angular เสร็จสิ้น initial render pass โดยไม่รอ idle state `on immediate` เหมาะสำหรับ components ที่ควรโหลดในไม่ช้าแต่ไม่ควร block first paint
## สรุป
- `@defer` แยก standalone components ออกเป็น chunks แยกต่างหากที่โหลดตามความต้องการ ช่วยลดขนาด initial bundle โดยไม่ต้องปรับโครงสร้าง routes
- trigger ทั้งเจ็ดประเภท (`idle`, `viewport`, `interaction`, `hover`, `timer`, `immediate`, `when`) ครอบคลุมกลยุทธ์การโหลดทุกรูปแบบตั้งแต่แบบ passive ไปจนถึงแบบที่ผู้ใช้กระตุ้น
- `prefetch on` แยกการดาวน์โหลด chunk ออกจากการ render component ช่วยขจัดความรู้สึกล่าช้าสำหรับ components ที่ถูกกระตุ้นโดยผู้ใช้
- Incremental hydration (`hydrate on`) render HTML สมบูรณ์บน server ขณะเลื่อนการ execute JavaScript ไปยัง client ส่งผลให้ LCP ปรับปรุงขึ้น 40-50% สำหรับหน้าที่มีเนื้อหามาก
- เนื้อหาของ `@placeholder` จะถูก render ระหว่าง SSR และต้องมี semantic HTML ที่สมบูรณ์เพื่อรักษาคุณภาพ SEO
- `@defer` เสริมกับ router-based lazy loading: ใช้ routes สำหรับการแบ่งระดับ feature และ `@defer` สำหรับการแบ่งระดับ component ภายใน templates
- components ที่ไม่ใช่ standalone ไม่สามารถ defer ได้และจะถูกโหลดแบบ eagerly อย่างเงียบๆ
---
Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack.
HTML version of this page: https://sharpskill.dev/th/blog/angular/angular-defer-loading-declarative-lazy-loading-guide