# Angular 20 ในปี 2026: Resource API, httpResource และคำถามสัมภาษณ์ > Angular 20 เปิดตัว httpResource และทำให้ Resource API เสถียรสำหรับการดึงข้อมูลแบบอิงกับ signal บทเรียนเชิงปฏิบัติครอบคลุม resource(), rxResource(), httpResource(), การตรวจสอบด้วย Zod และคำถามสัมภาษณ์ที่พบบ่อย - Published: 2026-05-30 - Updated: 2026-06-06 - Author: SharpSkill - Tags: angular, angular-20, resource-api, httpresource, signals, typescript, tutorial - Reading time: 10 min --- Angular 20 ผลักดัน [Resource API](https://angular.dev/guide/signals/resource) และ `httpResource` ขึ้นมาเป็นแกนหลักของการดึงข้อมูลแบบอิงกับ signal API เชิงทดลองเหล่านี้เข้ามาแทนรูปแบบการ subscribe `HttpClient` ที่เยิ่นเย้อ ด้วย primitive เชิงปฏิกิริยาที่ติดตามสถานะ loading, error และ resolved ให้โดยอัตโนมัติ > **สิ่งที่เปลี่ยนไปใน Angular 20** > > Resource API เปลี่ยนชื่อ `request` เป็น `params` และ `loader` เป็น `stream` (สำหรับ `rxResource`) ค่าสถานะตอนนี้เป็น string literal (`'idle'`, `'loading'`, `'resolved'`, `'error'`, `'reloading'`, `'local'`) แทนที่ enum ที่เป็นตัวเลข `httpResource` สร้างขึ้นบน `HttpClient` รองรับ interceptor และการตรวจสอบด้วย Zod ได้ทันที ## สาม Resource ใน Angular 20 Angular 20 มีสามวิธีในการโหลดข้อมูลแบบ asynchronous ในรูปของ signal แต่ละวิธีเหมาะกับกรณีใช้งานที่ต่างกัน แต่ทั้งหมดใช้โมเดลเชิงปฏิกิริยาแบบเดียวกัน คือ ประกาศ dependency กำหนด loader แล้วบริโภคผลลัพธ์ผ่าน signal - **`resource()`** ทำงานกับ Promise เหมาะอย่างยิ่งเมื่อใช้ `fetch()` หรือ API ใด ๆ ที่อิงกับ promise - **`rxResource()`** ทำงานกับ Observable เป็นตัวเลือกที่ถูกต้องเมื่อจำเป็นต้องใช้ operator ของ RxJS เช่น `debounceTime`, `retry` หรือ `switchMap` - **`httpResource()`** ห่อหุ้ม `HttpClient` ของ Angular โดยตรง interceptor เครื่องมือสำหรับทดสอบ และการตรวจสอบ schema ทำงานได้โดยไม่ต้องตั้งค่าเพิ่ม ความแตกต่างสำคัญระหว่าง `httpResource` กับอีกสองตัว คือ `httpResource` ใช้ `HttpClient` อยู่เบื้องหลัง ซึ่งหมายความว่า [interceptor](https://angular.dev/guide/http/interceptors) ที่มีอยู่เดิมยังคงทำงานต่อไป ส่วน API `resource()` ดั้งเดิมข้าม `HttpClient` ไปทั้งหมด ซึ่งเป็นจุดอ่อนสำคัญใน Angular 19 ## สร้างโปรไฟล์ผู้ใช้ด้วย resource() ฟังก์ชัน `resource()` รับฟังก์ชันคำนวณ `params` และฟังก์ชัน `loader` เมื่อ signal ภายใน `params` เปลี่ยน loader จะถูกเรียกทำงานใหม่โดยอัตโนมัติ ```typescript // user-profile.component.ts import { Component, signal, resource } from '@angular/core'; interface User { id: number; name: string; email: string; } @Component({ selector: 'app-user-profile', template: ` @if (userResource.hasValue()) {

{{ userResource.value().name }}

{{ userResource.value().email }}

} @else if (userResource.isLoading()) {

Loading profile...

} @else if (userResource.error()) {

Failed to load user

} `, }) export class UserProfileComponent { userId = signal(1); // params produces the reactive dependency // loader receives it and returns a Promise userResource = resource({ params: () => this.userId(), loader: async ({ params: id, abortSignal }) => { const res = await fetch(`/api/users/${id}`, { signal: abortSignal }); return res.json(); }, }); loadUser(id: number) { this.userId.set(id); // triggers automatic refetch } } ``` พารามิเตอร์ `abortSignal` ช่วยให้ Angular ยกเลิกคำขอที่กำลังทำงานอยู่ได้ เมื่อ `userId` เปลี่ยนก่อนที่คำขอก่อนหน้าจะเสร็จสิ้น สิ่งนี้ป้องกัน race condition โดยไม่ต้องจัดการ subscription ด้วยตนเอง ## ดึงข้อมูลเชิงปฏิกิริยาด้วย httpResource `httpResource` ขจัดโค้ดซ้ำซากด้วยการรวมการประกาศ URL และการเรียก HTTP ไว้ในการเรียกครั้งเดียว มันคืนค่า `HttpResourceRef` ที่เปิดเผย `value`, `isLoading`, `error`, `status` และ `headers` ในรูปของ signal ```typescript // product-list.component.ts import { Component, signal, computed } from '@angular/core'; import { httpResource } from '@angular/common/http'; interface Product { id: number; name: string; price: number; category: string; } @Component({ selector: 'app-product-list', template: ` @if (products.hasValue()) { @for (product of products.value(); track product.id) {

{{ product.name }}

{{ product.price | currency }}
} } @else if (products.isLoading()) {

Loading products...

} `, }) export class ProductListComponent { category = signal('electronics'); // httpResource re-fetches whenever category() changes products = httpResource(() => ({ url: '/api/products', params: { category: this.category() }, })); filterByCategory(cat: string) { this.category.set(cat); // pending request is cancelled, new one starts } } ``` มีรายละเอียดหลายอย่างที่สำคัญตรงนี้ ฟังก์ชันที่ส่งให้ `httpResource` คืนค่าออบเจ็กต์การตั้งค่าคำขอ Angular ติดตามการอ่าน signal ภายในฟังก์ชันนี้ ดังนั้นการเปลี่ยน `category` จึงกระตุ้นคำขอ GET ใหม่ หากมีคำขอกำลังทำงานอยู่ Angular จะยกเลิกมันก่อนเริ่มคำขอใหม่ > **httpResource ใช้สำหรับการอ่านเท่านั้น** > > `httpResource` ออกแบบมาสำหรับการดึงข้อมูล (คำขอ GET) การนำไปใช้กับการดำเนินการ POST, PUT หรือ DELETE นั้นไม่ปลอดภัย เพราะการยกเลิกคำขออาจยกเลิก mutation กลางคันได้ สำหรับการดำเนินการเขียน ให้ใช้ `HttpClient` โดยตรงหรือห่อ mutation ไว้ในเมธอดของ service ## ตรวจสอบ schema ด้วย Zod และ httpResource การตอบกลับจากบริการภายนอกอาจเบี่ยงเบนจากรูปร่างที่คาดหวัง ตัวเลือก `parse` บน `httpResource` ผสานไลบรารีตรวจสอบ schema อย่าง [Zod](https://zod.dev/) เพื่อจับความไม่ตรงกัน ณ ขณะรันไทม์ แทนที่จะปล่อยให้ข้อมูลที่เสียหายแพร่กระจายไปเงียบ ๆ ```typescript // order.component.ts import { Component, signal } from '@angular/core'; import { httpResource } from '@angular/common/http'; import { z } from 'zod'; // Define the expected shape with Zod const OrderSchema = z.object({ id: z.number(), status: z.enum(['pending', 'shipped', 'delivered', 'cancelled']), total: z.number().positive(), items: z.array(z.object({ productId: z.number(), quantity: z.number().int().positive(), unitPrice: z.number().positive(), })), createdAt: z.string().datetime(), }); type Order = z.infer; @Component({ selector: 'app-order', template: ` @if (order.hasValue()) {

Order #{{ order.value().id }}

Status: {{ order.value().status }}

Total: {{ order.value().total | currency }}

} @else if (order.error()) {

Invalid order data received

} `, }) export class OrderComponent { orderId = signal(42); // parse validates the response before exposing it as a signal order = httpResource( () => `/api/orders/${this.orderId()}`, { parse: OrderSchema.parse } ); } ``` เมื่อ API คืนค่าข้อมูลที่ไม่ตรงกับ `OrderSchema` resource จะเปลี่ยนเป็นสถานะ `'error'` ชนิดของค่าที่ฟังก์ชัน `parse` คืนกลับมายังกำหนดชนิด TypeScript ของ `value()` ด้วย ดังนั้นนิยาม schema จึงทำหน้าที่สองอย่าง ทั้งเป็นตัวตรวจสอบขณะรันไทม์และตัวสร้างชนิด ## rxResource ด้วย stream และ params ใน Angular 20 Angular 20 เปลี่ยนชื่อ `loader` เป็น `stream` และ `request` เป็น `params` ใน `rxResource` การเปลี่ยนแปลงนี้ทำให้การตั้งชื่อสอดคล้องกับความหมายของ streaming ที่ `rxResource` รองรับ ฟังก์ชัน `stream` รับบริบท Observable และต้องคืนค่า Observable ```typescript // search.component.ts import { Component, signal } from '@angular/core'; import { rxResource } from '@angular/core/rxjs-interop'; import { HttpClient } from '@angular/common/http'; import { inject } from '@angular/core'; import { debounceTime, switchMap } from 'rxjs'; interface SearchResult { id: number; title: string; excerpt: string; } @Component({ selector: 'app-search', template: ` @if (results.isLoading()) {

Searching...

} @if (results.hasValue()) { @for (item of results.value(); track item.id) {
{{ item.title }}
} } `, }) export class SearchComponent { private http = inject(HttpClient); query = signal(''); // params (was "request") provides the reactive input // stream (was "loader") returns an Observable results = rxResource({ params: () => this.query(), stream: ({ params: q }) => this.http.get('/api/search', { params: { q }, }), }); } ``` ต่างจาก `httpResource` ตรงที่ `rxResource` ให้การควบคุม pipeline ของ Observable อย่างเต็มที่ operator อย่าง `debounceTime` หรือ `retry` สามารถต่อเชื่อมไว้ภายใน `stream` ได้ อย่างไรก็ตาม `httpResource` จัดการกรณีที่พบบ่อยที่สุด (คำขอ GET เดียว) ด้วยโค้ดที่น้อยกว่า ## การติดตามสถานะ: string literal แทนที่ enum Angular 20 เปลี่ยน `ResourceStatus` จาก enum ตัวเลขเป็นชนิด union ของ string ค่าที่เป็นไปได้ทั้งหกให้มุมมองโดยละเอียดเกี่ยวกับวงจรชีวิตของ resource | สถานะ | ความหมาย | |---|---| | `'idle'` | `params` คืนค่า `undefined` — ไม่มีการส่งคำขอ | | `'loading'` | คำขอแรกกำลังดำเนินการ | | `'reloading'` | คำขอครั้งต่อไปหลังจากสำเร็จก่อนหน้านี้ | | `'resolved'` | มีข้อมูลพร้อมใช้ใน `value()` | | `'error'` | คำขอล้มเหลว — `error()` บรรจุข้อผิดพลาด | | `'local'` | ตั้งค่าในเครื่องผ่าน `.set()` หรือ `.update()` | ```typescript // status-demo.component.ts import { Component, signal, resource } from '@angular/core'; @Component({ selector: 'app-status-demo', template: `

Status: {{ data.status() }}

@switch (data.status()) { @case ('loading') { } @case ('reloading') { } @case ('resolved') { } @case ('error') { } @case ('idle') {

Select a filter to load data

} } `, }) export class StatusDemoComponent { filter = signal(undefined); data = resource({ params: () => this.filter(), loader: async ({ params: f, abortSignal }) => { const res = await fetch(`/api/data?filter=${f}`, { signal: abortSignal }); return res.json(); }, }); } ``` การคืนค่า `undefined` จาก `params` ตั้งสถานะเป็น `'idle'` และป้องกันไม่ให้ loader ทำงาน รูปแบบนี้เหมาะกับการดึงข้อมูลแบบมีเงื่อนไข แสดงข้อความแจ้งจนกว่าผู้ใช้จะป้อนข้อมูล แล้วจึงโหลดข้อมูล > **การเข้าถึง value() ในสถานะ error** > > ตั้งแต่ Angular 20 การเรียก `value()` บน resource ที่อยู่ในสถานะ `'error'` จะโยน exception ขณะรันไทม์ ควรป้องกันการอ่านด้วย `hasValue()` เสมอ หรือตรวจสอบ `status()` ก่อนเข้าถึง `value()` นี่เป็น breaking change จาก Angular 19 ที่ `value()` คืนค่า `undefined` เมื่อเกิดข้อผิดพลาด ## คำถามสัมภาษณ์ httpResource ของ Angular 20 Resource API กำลังกลายเป็นหัวข้อมาตรฐานใน [คำถามสัมภาษณ์ Angular](/blog/angular/angular-19-interview-questions-signals-ssr-must-know) ต่อไปนี้คือคำถามที่ทดสอบความเข้าใจที่แท้จริง เกินกว่าความคุ้นเคยแบบผิวเผินกับ API **ถาม: `httpResource` แก้ปัญหาอะไรที่ `resource()` แก้ไม่ได้?** `resource()` ใช้ `fetch()` หรือ loader ใด ๆ ที่อิงกับ promise โดยข้าม `HttpClient` ของ Angular นั่นหมายความว่า interceptor (สำหรับ token การยืนยันตัวตน การบันทึก log การจัดการข้อผิดพลาด) จะไม่ถูกนำมาใช้ ส่วน `httpResource` ใช้ `HttpClient` ภายใน ดังนั้น interceptor เครื่องมือทดสอบ (`HttpTestingController`) และฟีเจอร์อย่าง `withFetch()` จึงทำงานได้โดยไม่ต้องตั้งค่าเพิ่มเติม **ถาม: ควรเลือก `rxResource` แทน `httpResource` เมื่อใด?** `rxResource` ให้การควบคุม Observable อย่างเต็มที่ผ่านฟังก์ชัน `stream` เลือกใช้เมื่อ pipeline ข้อมูลต้องการ operator ของ RxJS เช่น การ debounce การป้อนค้นหา การลองคำขอที่ล้มเหลวซ้ำด้วย exponential backoff หรือการรวมหลาย stream ด้วย `combineLatest` สำหรับคำขอ GET ทั่วไป `httpResource` ใช้โค้ดน้อยกว่า **ถาม: Angular จัดการคำขอพร้อมกันอย่างไรเมื่อ signal เปลี่ยนอย่างรวดเร็ว?** ทั้งสาม resource ยกเลิกคำขอที่กำลังทำงานเมื่อ `params` สร้างค่าใหม่ สำหรับ `httpResource` และ `resource()` `AbortSignal` ยกเลิก fetch ที่อยู่เบื้องหลัง สำหรับ `rxResource` Angular ยกเลิกการ subscribe จาก Observable ก่อนหน้า สิ่งนี้ป้องกันไม่ให้การตอบกลับที่ล้าสมัยเขียนทับข้อมูลใหม่ **ถาม: สถานะ `'local'` มีไว้เพื่ออะไร?** การเรียก `.set()` หรือ `.update()` บน resource เปลี่ยนค่าในเครื่องโดยไม่กระตุ้น loader สถานะเปลี่ยนเป็น `'local'` ซึ่งบ่งบอกว่าค่าปัจจุบันไม่ได้มาจากเซิร์ฟเวอร์ สิ่งนี้รองรับการอัปเดต UI แบบ optimistic โดย UI สะท้อนการเปลี่ยนแปลงทันทีในขณะที่คำขอ mutation แยกต่างหากกำลังทำงานอยู่ **ถาม: การผสาน Zod ทำงานกับ `httpResource` อย่างไร?** ตัวเลือก `parse` รับฟังก์ชันใด ๆ ที่มี signature `(data: unknown) => T` เมื่อการตอบกลับ HTTP มาถึง `httpResource` ส่ง JSON ที่แปลงแล้วผ่าน `parse` ก่อนตั้งค่า `value()` หาก `parse` โยน exception (เช่น `ZodError`) resource จะเปลี่ยนเป็นสถานะ `'error'` ชนิดที่ `parse` คืนค่ากำหนดชนิด TypeScript ของ `value()` ให้ทั้งความปลอดภัยขณะรันไทม์และชนิดขณะคอมไพล์จากนิยาม schema เดียว หากต้องการเจาะลึกเรื่อง [signal ของ Angular](/technologies/angular/interview-questions/angular-signals) และวิธีที่พวกมันผสานเข้ากับเฟรมเวิร์กในวงกว้าง โมดูล signal ครอบคลุม computed signal, effect และโมเดลเชิงปฏิกิริยา ## ย้ายจาก subscription ของ HttpClient ไปยัง httpResource แอปพลิเคชัน Angular ที่มีอยู่มักดึงข้อมูลด้วย subscription ของ `HttpClient` ภายใน `ngOnInit` หรือใช้ `AsyncPipe` กับ Observable การย้ายไปยัง `httpResource` เป็นไปตามรูปแบบที่คาดเดาได้ ```typescript // BEFORE: manual subscription in ngOnInit @Component({ /* ... */ }) export class BeforeComponent implements OnInit, OnDestroy { private http = inject(HttpClient); private destroy$ = new Subject(); users: User[] = []; loading = false; error: string | null = null; ngOnInit() { this.loading = true; this.http.get('/api/users') .pipe(takeUntil(this.destroy$)) .subscribe({ next: (data) => { this.users = data; this.loading = false; }, error: (err) => { this.error = err.message; this.loading = false; }, }); } ngOnDestroy() { this.destroy$.next(); this.destroy$.complete(); } } // AFTER: httpResource handles lifecycle automatically @Component({ /* ... */ }) export class AfterComponent { users = httpResource(() => '/api/users'); // No ngOnInit, no Subject, no manual unsubscribe // Template uses users.value(), users.isLoading(), users.error() } ``` การย้ายนี้ขจัดโค้ดซ้ำซากในการจัดการวงจรชีวิต การล้าง subscription เกิดขึ้นโดยอัตโนมัติเมื่อ component ถูกทำลาย สถานะ loading และ error ถูกฝังอยู่ใน resource อยู่แล้ว ไม่จำเป็นต้องมี flag แบบ boolean แยกต่างหาก สำหรับแอปพลิเคชันที่ใช้ [standalone component](/blog/angular/angular-standalone-components-migration-best-practices) อยู่แล้ว การย้ายทำได้ตรงไปตรงมา เพียงแทนที่การ inject `HttpClient` และตรรกะ subscription ด้วยการประกาศ `httpResource` เพียงรายการเดียว ## บทสรุป - `httpResource` แทนที่ subscription ของ `HttpClient` ที่ทำเองด้วยการประกาศเชิงปฏิกิริยาเพียงรายการเดียว ที่จัดการ loading, error และการยกเลิกโดยอัตโนมัติ - ใช้ `resource()` สำหรับ API ที่อิงกับ promise, `rxResource()` สำหรับ pipeline ของ Observable ที่มี operator ของ RxJS และ `httpResource()` สำหรับการเรียก HTTP มาตรฐานที่รองรับ interceptor - Angular 20 เปลี่ยนชื่อ `request` เป็น `params` และ `loader` เป็น `stream` ใน `rxResource` — ปรับปรุงโค้ดที่มีอยู่ให้สอดคล้อง - ค่าสถานะตอนนี้เป็น string literal (`'idle'`, `'loading'`, `'resolved'`, `'error'`, `'reloading'`, `'local'`) แทนที่ enum ตัวเลขจาก Angular 19 - ตัวเลือก `parse` บน `httpResource` ผสาน Zod หรือ Valibot สำหรับการตรวจสอบ schema ขณะรันไทม์ที่ขับเคลื่อนชนิด TypeScript ด้วย - การเรียก `value()` บน resource ในสถานะ error จะโยน exception ใน Angular 20 — ป้องกันด้วย `hasValue()` เสมอ หรือตรวจสอบ `status()` ก่อน - resource ทุกแบบยกเลิกคำขอที่กำลังทำงานโดยอัตโนมัติเมื่อ dependency เปลี่ยน ป้องกัน race condition โดยไม่ต้องมีตรรกะการยกเลิกด้วยตนเอง --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/th/blog/angular/angular-20-resource-api-httpresource-tutorial