# 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