Angular @defer ในปี 2026: Declarative Lazy Loading สำหรับแอปพลิเคชันที่เร็วขึ้น

เจาะลึก Angular @defer blocks สำหรับ declarative lazy loading พร้อม triggers ทุกประเภท prefetching, incremental hydration, พฤติกรรมบน SSR และรูปแบบการใช้งานจริงเพื่อเพิ่มประสิทธิภาพ

Angular @defer lazy loading

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 สี่ประเภทที่ควบคุมสิ่งที่ผู้ใช้เห็นตลอดวงจรชีวิตการโหลด:

app.component.htmltypescript
@defer (on viewport) {
  <analytics-dashboard />
} @placeholder {
  <div class="h-64 bg-muted animate-pulse"></div>
} @loading (minimum 200ms) {
  <loading-spinner />
} @error {
  <p>Failed to load the dashboard. Please refresh.</p>
}

@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

product-page.component.htmltypescript
// Loads when the browser goes idle (default)
@defer {
  <recommendation-engine />
}

// Loads when the element enters the viewport
@defer (on viewport) {
  <customer-reviews [productId]="product.id" />
}

// Loads on click or keydown
@defer (on interaction) {
  <product-configurator />
} @placeholder {
  <button>Configure this product</button>
}

// Loads on mouseenter or focusin
@defer (on hover) {
  <quick-preview />
}

// Loads after 3 seconds
@defer (on timer(3s)) {
  <chat-widget />
}

// Loads immediately after initial render
@defer (on immediate) {
  <notification-center />
}

Trigger on viewport ใช้ Intersection Observer API ภายในเพื่อตรวจจับว่า element เลื่อนเข้าสู่พื้นที่ที่มองเห็นบนหน้าจอ on idle อาศัย requestIdleCallback ทำให้เป็นค่าเริ่มต้นที่ปลอดภัยที่สุดสำหรับเนื้อหาที่อยู่ด้านล่างของหน้าจอ on timer รับค่าระยะเวลาในหน่วยมิลลิวินาที (500ms) หรือวินาที (3s)

Trigger when สำหรับเงื่อนไขแบบ Reactive

นอกเหนือจาก trigger ที่อิงกับ event แล้ว when ยังรับ boolean expression ใดก็ได้ รวมถึงการอ่านค่า signal:

dashboard.component.tstypescript
export class DashboardComponent {
  showAdvanced = signal(false);
}

// dashboard.component.html
@defer (when showAdvanced()) {
  <advanced-analytics />
} @placeholder {
  <button (click)="showAdvanced.set(true)">Show advanced analytics</button>
}

การรวม on กับ when เข้าด้วยกันถูกต้องตามไวยากรณ์ Angular ถือว่าเป็นเงื่อนไข OR กล่าวคือบล็อกจะโหลดเมื่อเงื่อนไขใดเงื่อนไขหนึ่งเป็นจริง

Prefetching: การแยกการดาวน์โหลดออกจากการแสดงผล

Prefetching แยกจังหวะที่ chunk ถูกดาวน์โหลดออกจากจังหวะที่ component ถูก render ช่วยขจัดความรู้สึกล่าช้าของผู้ใช้โดยการดึง JavaScript ล่วงหน้าก่อนที่ผู้ใช้จะกระตุ้น trigger จริง

settings.component.htmltypescript
@defer (on interaction; prefetch on idle) {
  <account-settings />
} @placeholder {
  <button>Open settings</button>
}

ในรูปแบบนี้เบราว์เซอร์จะดาวน์โหลด chunk ของ account-settings ในช่วง idle time เมื่อผู้ใช้คลิก component จะ render ทันทีเพราะโค้ดพร้อมใช้งานแล้ว Prefetching รองรับ trigger types เดียวกันกับ on clause หลัก

รูปแบบที่นิยมใช้สำหรับ dashboards คือ prefetch components ที่มีน้ำหนักมากในช่วง idle ขณะที่เลื่อนการ render ไปจนกว่าจะเข้าสู่ viewport:

analytics.component.htmltypescript
@defer (on viewport; prefetch on idle) {
  <chart-widget [data]="salesData()" />
} @placeholder (minimum 100ms) {
  <div class="h-48 bg-muted rounded-none"></div>
}

พร้อมที่จะพิชิตการสัมภาษณ์ Angular แล้วหรือยังครับ?

ฝึกฝนด้วยตัวจำลองแบบโต้ตอบ, flashcards และแบบทดสอบเทคนิคครับ

@defer กับ Server-Side Rendering

บนฝั่ง server @defer blocks จะ render เนื้อหาของ @placeholder เป็นค่าเริ่มต้น deferred component จะไม่ถูก render บน server เลย พฤติกรรมนี้เป็นไปตามเจตนา เนื่องจากการดาวน์โหลด chunk มีความหมายเฉพาะในบริบทของเบราว์เซอร์เท่านั้น

สำหรับเนื้อหาที่สำคัญต่อ SEO หมายความว่า @placeholder ต้องมี HTML ที่มีความหมาย ไม่ใช่เพียงแค่ spinner:

blog-post.component.htmltypescript
@defer (on viewport) {
  <comment-section [postId]="post.id" />
} @placeholder {
  <section aria-label="Comments">
    <h2>Comments</h2>
    <p>Loading comments...</p>
  </section>
}

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

product-page.component.htmltypescript
@defer (hydrate on viewport) {
  <product-gallery [images]="product.images" />
}

@defer (hydrate on interaction) {
  <product-reviews [productId]="product.id" />
}

สิ่งที่แตกต่างจาก @defer แบบมาตรฐานคือ incremental hydration จะ render HTML ของ component อย่างสมบูรณ์บน server ฝั่ง client ได้รับ markup ที่ครบถ้วนแต่ข้ามการ hydrate component ไปจนกว่า trigger จะทำงาน ผลลัพธ์คือเบราว์เซอร์ paint หน้าจอทั้งหมดได้ทันที แต่การ execute JavaScript ถูกเลื่อนออกไป

รูปแบบการใช้งานจริงและกลยุทธ์ด้าน Performance

รูปแบบที่ 1: Dashboard ขนาดใหญ่ที่มีหลาย Deferred Panels

dashboard.component.htmltypescript
<div class="grid grid-cols-2 gap-4">
  @defer (on viewport; prefetch on idle) {
    <sales-chart />
  } @placeholder {
    <skeleton-card height="300px" />
  }

  @defer (on viewport; prefetch on idle) {
    <user-activity-feed />
  } @placeholder {
    <skeleton-card height="300px" />
  }

  @defer (on interaction) {
    <export-panel />
  } @placeholder {
    <button>Export data</button>
  }
</div>

แต่ละ panel โหลดอย่างอิสระจากกัน export panel จะไม่ถูกโหลดจนกว่าผู้ใช้จะร้องขออย่างชัดเจน ช่วยประหยัดการดาวน์โหลด chunk ทั้ง chunk สำหรับผู้ใช้ที่ไม่เคยใช้ฟังก์ชันส่งออกข้อมูล

รูปแบบที่ 2: การโหลดฟีเจอร์ตามเงื่อนไขด้วย Signals

editor.component.tstypescript
export class EditorComponent {
  user = inject(UserService).currentUser;
  isPremium = computed(() => this.user()?.plan === 'premium');
}

// editor.component.html
@defer (when isPremium()) {
  <ai-assistant />
} @placeholder {
  <upgrade-banner />
}

ผู้ใช้ระดับ free-tier จะไม่ดาวน์โหลด chunk ของ AI assistant เลย upgrade banner ทำหน้าที่เป็นทั้ง placeholder และสื่อส่งเสริมการอัปเกรดแพ็คเกจ

การหลีกเลี่ยงข้อผิดพลาดที่พบบ่อย

การซ้อน @defer blocks ที่ใช้ trigger เดียวกันจะทำให้ chunks ถูกดาวน์โหลดพร้อมกัน ควรกระจาย triggers ให้แตกต่างกันระหว่างบล็อกที่ซ้อนกัน:

typescript
// Avoid: both fire on idle simultaneously
@defer {
  @defer {
    <nested-heavy-component />
  }
}

// Better: outer on idle, inner on viewport
@defer (on idle) {
  <wrapper-component />
}
// Inside wrapper-component template:
@defer (on viewport) {
  <nested-heavy-component />
}

ข้อผิดพลาดอีกประการคือการใช้ @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

ถาม: จะเกิดอะไรขึ้นเมื่อวาง 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 อย่างเงียบๆ

เริ่มฝึกซ้อมเลย!

ทดสอบความรู้ของคุณด้วยตัวจำลองสัมภาษณ์และแบบทดสอบเทคนิคครับ

แท็ก

#angular
#performance
#lazy-loading

แชร์

บทความที่เกี่ยวข้อง

แผนภาพสถาปัตยกรรม Angular zoneless change detection แสดง signals และการเพิ่มประสิทธิภาพ

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 Standalone Components Migration Guide 2026

Angular Standalone Components: คู่มือการย้ายระบบและแนวทางปฏิบัติที่ดีที่สุดในปี 2026

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

คำถามสัมภาษณ์ Angular 19: Signals, SSR และ incremental hydration

คำถามสัมภาษณ์ Angular 19: Signals, SSR และแนวคิดสำคัญที่ต้องรู้

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