Angular Resource API และ httpResource: คู่มือครบถ้วน (เสถียรใน Angular 22)
Resource API และ httpResource ของ Angular เสถียรแล้วตั้งแต่ Angular 22 คู่มือนี้ครอบคลุม resource(), rxResource(), httpResource(), helper chain() สำหรับ resource ที่ขึ้นต่อกัน, การตรวจสอบ Zod และคำถามสัมภาษณ์

Resource API และ httpResource ของ Angular ให้การดึงข้อมูลแบบอิงกับ signal ที่ติดตามสถานะ loading, error และ resolved โดยอัตโนมัติ เปิดตัวใน Angular 20 และ API เหล่านี้ได้เสถียรใน Angular 22 แทนที่รูปแบบการ subscribe HttpClient ที่เยิ่นเย้อด้วย primitive เชิงปฏิกิริยา
Resource API (resource(), rxResource(), httpResource()) เลื่อนขั้นจากทดลองเป็นเสถียรใน Angular 22 (มิถุนายน 2026) การตั้งชื่อ params และ stream ค่าสถานะแบบ string literal และ helper chain() ใหม่สำหรับ resource ที่ขึ้นต่อกัน ล้วนพร้อมสำหรับ production หากกำลังอัปเกรดจาก Angular 20 หรือ 21 พื้นผิว API ยังคงเข้ากันได้
สาม Resource
Angular มีสามวิธีในการโหลดข้อมูลแบบ asynchronous ในรูปของ signal แต่ละวิธีเหมาะกับกรณีใช้งานที่ต่างกัน แต่ทั้งหมดใช้โมเดลเชิงปฏิกิริยาแบบเดียวกัน คือ ประกาศ dependency กำหนด loader แล้วบริโภคผลลัพธ์ผ่าน signal
resource()ทำงานกับ Promise เหมาะอย่างยิ่งเมื่อใช้fetch()หรือ API ใดๆ ที่อิงกับ promiserxResource()ทำงานกับ Observable เป็นตัวเลือกที่ถูกต้องเมื่อจำเป็นต้องใช้ operator ของ RxJS เช่นdebounceTime,retryหรือswitchMaphttpResource()ห่อหุ้มHttpClientของ Angular โดยตรง interceptor เครื่องมือสำหรับทดสอบ และการตรวจสอบ schema ทำงานได้โดยไม่ต้องตั้งค่าเพิ่ม
ความแตกต่างสำคัญระหว่าง httpResource กับอีกสองตัว คือ httpResource ใช้ HttpClient อยู่เบื้องหลัง ซึ่งหมายความว่า interceptor ที่มีอยู่เดิมยังคงทำงานต่อไป ส่วน API resource() ดั้งเดิมข้าม HttpClient ไปทั้งหมด ซึ่งเป็นข้อจำกัดใน Angular 19
สร้างโปรไฟล์ผู้ใช้ด้วย resource()
ฟังก์ชัน resource() รับฟังก์ชันคำนวณ params และฟังก์ชัน loader เมื่อ signal ภายใน params เปลี่ยน loader จะถูกเรียกทำงานใหม่โดยอัตโนมัติ
import { Component, signal, resource } from '@angular/core';
interface User {
id: number;
name: string;
email: string;
}
@Component({
selector: 'app-user-profile',
template: \`
@if (userResource.hasValue()) {
<h2>{{ userResource.value().name }}</h2>
<p>{{ userResource.value().email }}</p>
} @else if (userResource.isLoading()) {
<p>Loading profile...</p>
} @else if (userResource.error()) {
<p>Failed to load user</p>
}
\`,
})
export class UserProfileComponent {
userId = signal(1);
// params produces the reactive dependency
// loader receives it and returns a Promise
userResource = resource<User, number>({
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
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) {
<div class="product-card">
<h3>{{ product.name }}</h3>
<span>{{ product.price | currency }}</span>
</div>
}
} @else if (products.isLoading()) {
<p>Loading products...</p>
}
\`,
})
export class ProductListComponent {
category = signal('electronics');
// httpResource re-fetches whenever category() changes
products = httpResource<Product[]>(() => ({
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 ออกแบบมาสำหรับการดึงข้อมูล (คำขอ GET) การนำไปใช้กับการดำเนินการ POST, PUT หรือ DELETE นั้นไม่ปลอดภัย เพราะการยกเลิกคำขออาจยกเลิก mutation กลางคันได้ สำหรับการดำเนินการเขียน ให้ใช้ HttpClient โดยตรงหรือห่อ mutation ไว้ในเมธอดของ service
การเชื่อม Resource ด้วย chain()
Angular 22 เปิดตัว helper chain() สำหรับการโหลด resource ที่ขึ้นต่อกัน เมื่อ resource หนึ่งขึ้นอยู่กับค่าของอีก resource, chain() จะเผยแพร่สถานะ loading และ error โดยอัตโนมัติ
import { Component, signal } from '@angular/core';
import { httpResource } from '@angular/common/http';
interface User {
id: number;
name: string;
authorId: string;
}
interface Post {
id: number;
title: string;
content: string;
}
@Component({
selector: 'app-user-posts',
template: \`
@if (postsResource.isLoading()) {
<p>Loading...</p>
} @else if (postsResource.hasValue()) {
<h2>Posts by {{ userResource.value().name }}</h2>
@for (post of postsResource.value(); track post.id) {
<article>
<h3>{{ post.title }}</h3>
</article>
}
} @else if (postsResource.error()) {
<p>Failed to load posts</p>
}
\`,
})
export class UserPostsComponent {
userId = signal(1);
userResource = httpResource<User>(() => \`/api/users/\${this.userId()}\`);
// chain() propagates userResource's status to postsResource
postsResource = httpResource<Post[]>({
params: ({ chain }) => {
const user = chain(this.userResource);
return { url: '/api/posts', params: { authorId: user.authorId } };
},
});
}chain(userResource) ไม่ได้แค่คืนค่า ขณะที่ userResource กำลัง loading, postsResource ก็จะรายงานว่ากำลัง loading เช่นกันและ loader ของมันจะไม่ถูกเรียกทำงาน หาก userResource เกิดข้อผิดพลาด, postsResource จะเปลี่ยนเป็นสถานะ error พร้อม ResourceDependencyError ที่บรรจุข้อผิดพลาดจาก upstream
สิ่งนี้แทนที่รูปแบบ RxJS ของการเรียก switchMap แบบซ้อนกันด้วยการเผยแพร่สถานะที่ชัดเจนและถูกต้องตามค่าเริ่มต้น
ตรวจสอบ schema ด้วย Zod และ httpResource
การตอบกลับจากบริการภายนอกอาจเบี่ยงเบนจากรูปร่างที่คาดหวัง ตัวเลือก parse บน httpResource ผสานไลบรารีตรวจสอบ schema อย่าง Zod เพื่อจับความไม่ตรงกัน ณ ขณะรันไทม์ แทนที่จะปล่อยให้ข้อมูลที่เสียหายแพร่กระจายไปเงียบๆ
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<typeof OrderSchema>;
@Component({
selector: 'app-order',
template: \`
@if (order.hasValue()) {
<h2>Order #{{ order.value().id }}</h2>
<p>Status: {{ order.value().status }}</p>
<p>Total: {{ order.value().total | currency }}</p>
} @else if (order.error()) {
<p>Invalid order data received</p>
}
\`,
})
export class OrderComponent {
orderId = signal(42);
// parse validates the response before exposing it as a signal
order = httpResource<Order>(
() => \`/api/orders/\${this.orderId()}\`,
{ parse: OrderSchema.parse }
);
}เมื่อ API คืนค่าข้อมูลที่ไม่ตรงกับ OrderSchema resource จะเปลี่ยนเป็นสถานะ 'error' ชนิดของค่าที่ฟังก์ชัน parse คืนกลับมายังกำหนดชนิด TypeScript ของ value() ด้วย ดังนั้นนิยาม schema จึงทำหน้าที่สองอย่าง ทั้งเป็นตัวตรวจสอบขณะรันไทม์และตัวสร้างชนิด
rxResource ด้วย stream และ params
Angular เปลี่ยนชื่อ loader เป็น stream และ request เป็น params ใน rxResource การเปลี่ยนแปลงนี้ทำให้การตั้งชื่อสอดคล้องกับความหมายของ streaming ที่ rxResource รองรับ ฟังก์ชัน stream รับบริบท Observable และต้องคืนค่า Observable
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: \`
<input (input)="query.set($any($event.target).value)" placeholder="Search..." />
@if (results.isLoading()) {
<p>Searching...</p>
}
@if (results.hasValue()) {
@for (item of results.value(); track item.id) {
<div>{{ item.title }}</div>
}
}
\`,
})
export class SearchComponent {
private http = inject(HttpClient);
query = signal('');
// params (was "request") provides the reactive input
// stream (was "loader") returns an Observable
results = rxResource<SearchResult[], string>({
params: () => this.query(),
stream: ({ params: q }) =>
this.http.get<SearchResult[]>('/api/search', {
params: { q },
}),
});
}ต่างจาก httpResource ตรงที่ rxResource ให้การควบคุม pipeline ของ Observable อย่างเต็มที่ operator อย่าง debounceTime หรือ retry สามารถต่อเชื่อมไว้ภายใน stream ได้ อย่างไรก็ตาม httpResource จัดการกรณีที่พบบ่อยที่สุด (คำขอ GET เดียว) ด้วยโค้ดที่น้อยกว่า
พร้อมที่จะพิชิตการสัมภาษณ์ Angular แล้วหรือยังครับ?
ฝึกฝนด้วยตัวจำลองแบบโต้ตอบ, flashcards และแบบทดสอบเทคนิคครับ
การติดตามสถานะ: string literal แทนที่ enum
ResourceStatus เป็นชนิด union ของ string ค่าที่เป็นไปได้ทั้งหกให้มุมมองโดยละเอียดเกี่ยวกับวงจรชีวิตของ resource
| สถานะ | ความหมาย |
|---|---|
'idle' | params คืนค่า undefined, ไม่มีการส่งคำขอ |
'loading' | คำขอแรกกำลังดำเนินการ |
'reloading' | คำขอครั้งต่อไปหลังจากสำเร็จก่อนหน้านี้ |
'resolved' | มีข้อมูลพร้อมใช้ใน value() |
'error' | คำขอล้มเหลว, error() บรรจุข้อผิดพลาด |
'local' | ตั้งค่าในเครื่องผ่าน .set() หรือ .update() |
import { Component, signal, resource } from '@angular/core';
@Component({
selector: 'app-status-demo',
template: \`
<p>Status: {{ data.status() }}</p>
@switch (data.status()) {
@case ('loading') { <spinner /> }
@case ('reloading') { <subtle-spinner /> }
@case ('resolved') { <data-table [rows]="data.value()" /> }
@case ('error') { <error-banner [error]="data.error()" /> }
@case ('idle') { <p>Select a filter to load data</p> }
}
\`,
})
export class StatusDemoComponent {
filter = signal<string | undefined>(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() บน resource ที่อยู่ในสถานะ 'error' จะโยน exception ขณะรันไทม์ ควรป้องกันการอ่านด้วย hasValue() เสมอ หรือตรวจสอบ status() ก่อนเข้าถึง value() นี่เป็นการเปลี่ยนแปลงจาก Angular 19 ที่ value() คืนค่า undefined เมื่อเกิดข้อผิดพลาด
คำถามสัมภาษณ์ Angular httpResource
Resource API กำลังกลายเป็นหัวข้อมาตรฐานในคำถามสัมภาษณ์ Angular ต่อไปนี้คือคำถามที่ทดสอบความเข้าใจที่แท้จริง เกินกว่าความคุ้นเคยแบบผิวเผินกับ 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 เดียว
ถาม: chain() จัดการข้อผิดพลาดจาก resource upstream อย่างไร?
เมื่อใช้ chain(upstreamResource) ในฟังก์ชัน params ของ resource, resource ที่ขึ้นต่อกันจะไม่เรียกทำงาน loader หาก resource upstream กำลัง loading, idle, หรืออยู่ในสถานะ error หาก resource upstream เกิดข้อผิดพลาด, resource ที่ขึ้นต่อกันจะเปลี่ยนเป็นสถานะ error พร้อม ResourceDependencyError ข้อผิดพลาดนี้มี dependency (resource upstream) และ cause (ข้อผิดพลาด upstream จริง) ช่วยให้แยกแยะระหว่าง "คำขอของฉันล้มเหลว" กับ "คำขอของ dependency ล้มเหลว"
หากต้องการเจาะลึกเรื่อง signal ของ Angular และวิธีที่มันผสานเข้ากับเฟรมเวิร์กในวงกว้าง คู่มือ signal ครอบคลุม computed signal, effect และโมเดลเชิงปฏิกิริยา
ย้ายจาก subscription ของ HttpClient ไปยัง httpResource
แอปพลิเคชัน Angular ที่มีอยู่มักดึงข้อมูลด้วย subscription ของ HttpClient ภายใน ngOnInit หรือใช้ AsyncPipe กับ Observable การย้ายไปยัง httpResource เป็นไปตามรูปแบบที่คาดเดาได้
// BEFORE: manual subscription in ngOnInit
@Component({ /* ... */ })
export class BeforeComponent implements OnInit, OnDestroy {
private http = inject(HttpClient);
private destroy$ = new Subject<void>();
users: User[] = [];
loading = false;
error: string | null = null;
ngOnInit() {
this.loading = true;
this.http.get<User[]>('/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<User[]>(() => '/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 อยู่แล้ว การย้ายทำได้ตรงไปตรงมา: เพียงแทนที่การ inject HttpClient และตรรกะ subscription ด้วยการประกาศ httpResource เพียงรายการเดียว
เริ่มฝึกซ้อมเลย!
ทดสอบความรู้ของคุณด้วยตัวจำลองสัมภาษณ์และแบบทดสอบเทคนิคครับ
แหล่งข้อมูล
- เอกสาร Angular Resource API — คู่มือทางการเกี่ยวกับ resource(), rxResource(), และ httpResource()
- What's new in Angular 22.0 — บทความ Ninja Squad เกี่ยวกับ Resource API ที่เสถียรและ helper chain()
- คู่มือ interceptor HTTP ของ Angular — วิธีการทำงานของ interceptor กับ httpResource
ประเด็นสำคัญสำหรับ Angular Resource API
httpResourceแทนที่ subscription ของHttpClientที่ทำเองด้วยการประกาศเชิงปฏิกิริยาเพียงรายการเดียว ที่จัดการ loading, error และการยกเลิกโดยอัตโนมัติ- ใช้
resource()สำหรับ API ที่อิงกับ promise,rxResource()สำหรับ pipeline ของ Observable ที่มี operator ของ RxJS และhttpResource()สำหรับการเรียก HTTP มาตรฐานที่รองรับ interceptor - helper
chain()(Angular 22+) ช่วยให้โหลด resource ที่ขึ้นต่อกันพร้อมการเผยแพร่สถานะอัตโนมัติ; หาก upstream ล้มเหลว, resource ที่ขึ้นต่อกันจะได้รับResourceDependencyError - ค่าสถานะเป็น string literal (
'idle','loading','resolved','error','reloading','local') - ตัวเลือก
parseบนhttpResourceผสาน Zod หรือ Valibot สำหรับการตรวจสอบ schema ขณะรันไทม์ที่ขับเคลื่อนชนิด TypeScript ด้วย - การเรียก
value()บน resource ในสถานะ error จะโยน exception; ป้องกันด้วยhasValue()เสมอ หรือตรวจสอบstatus()ก่อน - resource ทุกแบบยกเลิกคำขอที่กำลังทำงานโดยอัตโนมัติเมื่อ dependency เปลี่ยน ป้องกัน race condition โดยไม่ต้องมีตรรกะการยกเลิกด้วยตนเอง
คุณหาบั๊กใน Angular เจอไหม
โค้ดจริงหนึ่งชิ้น บั๊กที่ซ่อนอยู่หนึ่งจุด วันละหนึ่งครั้ง ลองได้โดยไม่ต้องมีบัญชี

เขียนโดย
Anthony Fillion-Mailletผู้ก่อตั้ง SharpSkill
เป็นนักพัฒนาฟูลสแตกมากว่า 10 ปี ดูแล SharpSkill และรับผิดชอบทุกสิ่งที่เผยแพร่ที่นี่
อัปเดตเมื่อ 13 ตุลาคม 2569
แท็ก
แชร์
บทความที่เกี่ยวข้อง

NgRx Signal Store vs Classic NgRx ในปี 2026: ควรเลือกแบบไหน?
เปรียบเทียบครบถ้วนระหว่าง NgRx Signal Store และ Classic NgRx สำหรับการจัดการ state ใน Angular เรียนรู้ว่าเมื่อไหร่ควรใช้แต่ละวิธีพร้อมตัวอย่างโค้ดจริง

RxJS ใน Angular 2026: โอเปอเรเตอร์ Subject และ interop Signals
RxJS ใน Angular 2026: เชี่ยวชาญโอเปอเรเตอร์ Subject และรูปแบบ interop Signals ที่นักพัฒนา Angular ใช้จริงในโปรดักชัน พร้อมคำถามสัมภาษณ์ที่พบบ่อยที่สุด

Dependency Injection ขั้นสูงใน Angular 2026: Providers, Tokens และคำถามสัมภาษณ์
เรียนรู้ระบบ dependency injection ของ Angular อย่างลึกซึ้ง รวมถึงกลยุทธ์ provider, InjectionToken, injector แบบลำดับชั้น และคำถามสัมภาษณ์ที่พบบ่อยสำหรับนักพัฒนา Angular ที่มีประสบการณ์