# Angular 20 năm 2026: Resource API, httpResource và câu hỏi phỏng vấn > Angular 20 giới thiệu httpResource và ổn định hóa Resource API cho việc lấy dữ liệu dựa trên signal. Hướng dẫn thực hành về resource(), rxResource(), httpResource(), validation Zod và các câu hỏi phỏng vấn thường gặp. - 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 đưa [Resource API](https://angular.dev/guide/signals/resource) và `httpResource` lên hàng đầu trong việc lấy dữ liệu dựa trên signal. Các API thử nghiệm này thay thế những mẫu subscription `HttpClient` dài dòng bằng các primitive phản ứng tự động theo dõi các trạng thái loading, error và resolved. > **Những thay đổi trong Angular 20** > > Resource API đổi tên `request` thành `params` và `loader` thành `stream` (đối với `rxResource`). Giá trị trạng thái giờ là các literal chuỗi (`'idle'`, `'loading'`, `'resolved'`, `'error'`, `'reloading'`, `'local'`) thay vì enum số. `httpResource` được xây dựng trên `HttpClient`, hỗ trợ sẵn interceptor và validation bằng Zod. ## Ba biến thể Resource trong Angular 20 Angular 20 cung cấp ba cách để tải dữ liệu bất đồng bộ dưới dạng signal. Mỗi cách nhắm đến một trường hợp sử dụng khác nhau, nhưng tất cả đều chia sẻ cùng một mô hình phản ứng: khai báo các phụ thuộc, định nghĩa một loader, rồi tiêu thụ kết quả thông qua signal. - **`resource()`** hoạt động với Promise. Lý tưởng khi dùng `fetch()` hoặc bất kỳ API dựa trên promise nào. - **`rxResource()`** hoạt động với Observable. Lựa chọn đúng đắn khi cần các toán tử RxJS như `debounceTime`, `retry` hoặc `switchMap`. - **`httpResource()`** bao bọc trực tiếp `HttpClient` của Angular. Interceptor, các tiện ích kiểm thử và validation schema hoạt động mà không cần thiết lập thêm. Khác biệt then chốt giữa `httpResource` và hai cái còn lại: `httpResource` dùng `HttpClient` bên dưới, nghĩa là các [interceptor](https://angular.dev/guide/http/interceptors) hiện có vẫn tiếp tục hoạt động. API `resource()` ban đầu bỏ qua hoàn toàn `HttpClient`, một điểm khó chịu lớn trong Angular 19. ## Xây dựng hồ sơ người dùng với resource() Hàm `resource()` nhận một hàm tính toán `params` và một hàm `loader`. Khi các signal bên trong `params` thay đổi, loader sẽ tự động chạy lại. ```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 } } ``` Tham số `abortSignal` cho phép Angular hủy các yêu cầu đang chạy khi `userId` thay đổi trước khi yêu cầu trước đó hoàn tất. Điều này ngăn race condition mà không cần quản lý subscription thủ công. ## Lấy dữ liệu phản ứng với httpResource `httpResource` loại bỏ mã lặp lại bằng cách kết hợp khai báo URL và thực thi HTTP trong một lời gọi duy nhất. Nó trả về một `HttpResourceRef` phơi bày `value`, `isLoading`, `error`, `status` và `headers` dưới dạng 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 } } ``` Có nhiều chi tiết quan trọng ở đây. Hàm truyền cho `httpResource` trả về một đối tượng cấu hình yêu cầu. Angular theo dõi các lần đọc signal bên trong hàm này, nên thay đổi `category` sẽ kích hoạt một yêu cầu GET mới. Nếu một yêu cầu đang chạy, Angular sẽ hủy nó trước khi bắt đầu yêu cầu mới. > **httpResource chỉ dành cho việc đọc** > > `httpResource` được thiết kế cho việc lấy dữ liệu (yêu cầu GET). Dùng nó cho các thao tác POST, PUT hoặc DELETE là không an toàn, vì việc hủy yêu cầu có thể làm gián đoạn một mutation giữa chừng. Đối với thao tác ghi, hãy dùng `HttpClient` trực tiếp hoặc bao bọc mutation trong một method của service. ## Validation schema với Zod và httpResource Phản hồi từ các dịch vụ bên ngoài có thể lệch khỏi hình dạng mong đợi. Tùy chọn `parse` trên `httpResource` tích hợp các thư viện validation schema như [Zod](https://zod.dev/) để bắt sự không khớp tại thời điểm runtime, thay vì âm thầm lan truyền dữ liệu hỏng. ```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 } ); } ``` Khi API trả về dữ liệu không khớp với `OrderSchema`, resource chuyển sang trạng thái `'error'`. Kiểu trả về của hàm `parse` cũng quyết định kiểu TypeScript của `value()`, nên các định nghĩa schema đảm nhiệm hai vai trò: vừa là validator runtime vừa là bộ sinh kiểu. ## rxResource với stream và params trong Angular 20 Angular 20 đổi tên `loader` thành `stream` và `request` thành `params` trong `rxResource`. Thay đổi này đồng bộ cách đặt tên với ngữ nghĩa streaming mà `rxResource` hỗ trợ. Hàm `stream` nhận một ngữ cảnh Observable và phải trả về một 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 }, }), }); } ``` Khác với `httpResource`, `rxResource` trao toàn quyền kiểm soát pipeline Observable. Các toán tử như `debounceTime` hoặc `retry` có thể được nối chuỗi bên trong `stream`. Tuy nhiên, `httpResource` xử lý trường hợp phổ biến nhất (một yêu cầu GET đơn lẻ) với ít mã hơn. ## Theo dõi trạng thái: literal chuỗi thay thế enum Angular 20 thay đổi `ResourceStatus` từ một enum số thành một kiểu union chuỗi. Sáu giá trị khả dĩ cung cấp cái nhìn chi tiết về vòng đời của resource: | Trạng thái | Ý nghĩa | |---|---| | `'idle'` | `params` trả về `undefined` — không có yêu cầu nào được gửi | | `'loading'` | Yêu cầu đầu tiên đang diễn ra | | `'reloading'` | Yêu cầu tiếp theo sau một lần thành công trước đó | | `'resolved'` | Dữ liệu có sẵn trong `value()` | | `'error'` | Yêu cầu thất bại — `error()` chứa lỗi | | `'local'` | Giá trị được đặt cục bộ qua `.set()` hoặc `.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(); }, }); } ``` Trả về `undefined` từ `params` đặt trạng thái thành `'idle'` và ngăn loader chạy. Mẫu này phù hợp với việc lấy dữ liệu có điều kiện — hiển thị một lời nhắc cho đến khi người dùng nhập liệu, rồi tải dữ liệu. > **Truy cập value() ở trạng thái lỗi** > > Kể từ Angular 20, gọi `value()` trên một resource ở trạng thái `'error'` sẽ ném ra ngoại lệ tại runtime. Luôn bảo vệ việc đọc bằng `hasValue()` hoặc kiểm tra `status()` trước khi truy cập `value()`. Đây là một thay đổi phá vỡ so với Angular 19, nơi `value()` trả về `undefined` khi lỗi. ## Câu hỏi phỏng vấn httpResource Angular 20 Resource API đang trở thành một chủ đề tiêu chuẩn trong [câu hỏi phỏng vấn Angular](/blog/angular/angular-19-interview-questions-signals-ssr-must-know). Dưới đây là những câu hỏi kiểm tra sự hiểu biết thực sự, vượt ra ngoài sự quen thuộc bề mặt với API. **H: `httpResource` giải quyết vấn đề nào mà `resource()` không giải quyết được?** `resource()` dùng `fetch()` hoặc bất kỳ loader dựa trên promise nào, bỏ qua `HttpClient` của Angular. Điều này nghĩa là các interceptor (cho token xác thực, ghi log, xử lý lỗi) không được áp dụng. `httpResource` dùng `HttpClient` bên trong, nên các interceptor, tiện ích kiểm thử (`HttpTestingController`) và các tính năng như `withFetch()` hoạt động mà không cần cấu hình thêm. **H: Khi nào nên ưu tiên `rxResource` hơn `httpResource`?** `rxResource` cung cấp toàn quyền kiểm soát Observable thông qua hàm `stream` của nó. Hãy chọn nó khi pipeline dữ liệu cần các toán tử RxJS — debounce đầu vào tìm kiếm, thử lại các yêu cầu thất bại với exponential backoff, hoặc kết hợp nhiều stream với `combineLatest`. Với các yêu cầu GET đơn giản, `httpResource` cần ít mã hơn. **H: Angular xử lý các yêu cầu đồng thời thế nào khi một signal thay đổi nhanh chóng?** Cả ba biến thể resource đều hủy các yêu cầu đang chạy khi `params` tạo ra một giá trị mới. Với `httpResource` và `resource()`, `AbortSignal` hủy fetch bên dưới. Với `rxResource`, Angular hủy đăng ký khỏi Observable trước đó. Điều này ngăn các phản hồi cũ ghi đè lên dữ liệu mới. **H: Mục đích của trạng thái `'local'` là gì?** Gọi `.set()` hoặc `.update()` trên một resource thay đổi giá trị của nó cục bộ mà không kích hoạt loader. Trạng thái chuyển sang `'local'`, cho thấy giá trị hiện tại không đến từ máy chủ. Điều này hỗ trợ cập nhật UI lạc quan — UI phản ánh thay đổi ngay lập tức trong khi một yêu cầu mutation riêng biệt đang chạy. **H: Tích hợp Zod hoạt động thế nào với `httpResource`?** Tùy chọn `parse` chấp nhận bất kỳ hàm nào có chữ ký `(data: unknown) => T`. Khi phản hồi HTTP đến, `httpResource` truyền JSON đã phân tích qua `parse` trước khi đặt `value()`. Nếu `parse` ném ngoại lệ (ví dụ `ZodError`), resource chuyển sang trạng thái `'error'`. Kiểu trả về của `parse` quyết định kiểu TypeScript của `value()`, mang lại cả an toàn runtime và kiểu tại thời điểm biên dịch từ một định nghĩa schema duy nhất. Để tìm hiểu sâu hơn về [signal Angular](/technologies/angular/interview-questions/angular-signals) và cách chúng tích hợp với framework rộng hơn, module signal bao quát computed signal, effect và mô hình phản ứng. ## Di chuyển từ subscription HttpClient sang httpResource Các ứng dụng Angular hiện có thường lấy dữ liệu bằng subscription `HttpClient` bên trong `ngOnInit`, hoặc dùng `AsyncPipe` với Observable. Việc di chuyển sang `httpResource` tuân theo một mẫu có thể đoán trước: ```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() } ``` Việc di chuyển loại bỏ mã lặp lại quản lý vòng đời. Việc dọn dẹp subscription diễn ra tự động khi component bị hủy. Các trạng thái loading và error được tích hợp sẵn trong resource — không cần các cờ boolean riêng. Đối với các ứng dụng đã dùng [standalone component](/blog/angular/angular-standalone-components-migration-best-practices), việc di chuyển rất đơn giản: thay thế việc inject `HttpClient` và logic subscription bằng một khai báo `httpResource` duy nhất. ## Kết luận - `httpResource` thay thế các subscription `HttpClient` thủ công bằng một khai báo phản ứng duy nhất tự động xử lý loading, error và hủy yêu cầu - Dùng `resource()` cho các API dựa trên promise, `rxResource()` cho các pipeline Observable với toán tử RxJS, và `httpResource()` cho các lời gọi HTTP tiêu chuẩn có hỗ trợ interceptor - Angular 20 đổi tên `request` thành `params` và `loader` thành `stream` trong `rxResource` — hãy cập nhật mã hiện có cho phù hợp - Giá trị trạng thái giờ là các literal chuỗi (`'idle'`, `'loading'`, `'resolved'`, `'error'`, `'reloading'`, `'local'`), thay thế enum số từ Angular 19 - Tùy chọn `parse` trên `httpResource` tích hợp Zod hoặc Valibot cho validation schema runtime đồng thời điều khiển các kiểu TypeScript - Gọi `value()` trên một resource ở trạng thái lỗi sẽ ném ngoại lệ trong Angular 20 — luôn bảo vệ bằng `hasValue()` hoặc kiểm tra `status()` trước - Tất cả các biến thể resource đều tự động hủy các yêu cầu đang chạy khi các phụ thuộc thay đổi, ngăn race condition mà không cần logic hủy thủ công --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/vi/blog/angular/angular-20-resource-api-httpresource-tutorial