Angular Resource API та httpResource: Повний посібник (Стабільно з Angular 22)
Resource API та httpResource в Angular тепер стабільні починаючи з Angular 22. Цей посібник охоплює resource(), rxResource(), httpResource(), новий helper chain() для залежних ресурсів, валідацію Zod та питання для співбесід.

Angular Resource API та httpResource забезпечують сигнал-орієнтоване отримання даних, яке автоматично відстежує стани завантаження, помилки та готовності. Представлені в Angular 20, ці API стали стабільними в Angular 22, замінивши громіздкі патерни підписок HttpClient на реактивні примітиви.
Resource API (resource(), rxResource(), httpResource()) перейшло зі статусу експериментального до стабільного в Angular 22 (червень 2026). Іменування params та stream, рядкові літерали статусів та новий helper chain() для залежних ресурсів готові до продакшену. Якщо оновлюєтеся з Angular 20 чи 21, поверхня API залишається сумісною.
Три варіанти Resource
Angular пропонує три способи завантаження асинхронних даних як сигналів. Кожен призначений для різних сценаріїв використання, але всі поділяють одну реактивну модель: оголошення залежностей, визначення завантажувача та споживання результату через сигнали.
resource()працює з Promise. Ідеальний при використанніfetch()або будь-якого API на основі Promise.rxResource()працює з Observable. Правильний вибір, коли потрібні оператори RxJS такі якdebounceTime,retryчиswitchMap.httpResource()обгортає AngularHttpClientнапряму. Інтерцептори, утиліти тестування та валідація схеми працюють без додаткового налаштування.
Ключова різниця між httpResource та двома іншими: httpResource використовує HttpClient під капотом, що означає що існуючі інтерцептори продовжують працювати. Оригінальний API resource() повністю обходив HttpClient, що було обмеженням в Angular 19.
Створення профілю користувача з resource()
Функція resource() приймає обчислення params та функцію loader. Коли сигнали всередині 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 змінюється до завершення попереднього запиту. Це запобігає гонитві за даними без ручного керування підписками.
Реактивне отримання даних з httpResource
httpResource усуває шаблонний код, поєднуючи оголошення URL та виконання HTTP в одному виклику. Повертає HttpResourceRef, який надає value, isLoading, error, status та headers як сигнали.
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 відстежує читання сигналів всередині цієї функції, тому зміна category викликає новий GET запит. Якщо запит вже виконується, Angular скасовує його перед запуском нового.
httpResource призначений для отримання даних (GET запити). Використання для операцій POST, PUT чи DELETE небезпечне, оскільки скасування запиту може перервати мутації на середині виконання. Для операцій запису використовуйте HttpClient напряму або обгортайте мутації в метод сервісу.
Ланцюжок ресурсів з chain()
Angular 22 представив helper chain() для завантаження залежних ресурсів. Коли один ресурс залежить від значення іншого, chain() автоматично поширює стани завантаження та помилки.
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 завантажується, postsResource також повідомляє про стан завантаження і його loader не виконується. Якщо userResource повертає помилку, postsResource переходить у стан помилки з ResourceDependencyError, який містить вихідну помилку.
Це замінює RxJS патерн з вкладеними викликами switchMap явним, коректним за замовчуванням поширенням статусу.
Валідація схеми з Zod та httpResource
Відповіді API від зовнішніх сервісів можуть відхилятися від очікуваних структур. Опція parse на httpResource інтегрує бібліотеки валідації схем такі як 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, ресурс переходить у статус 'error'. Тип повернення функції parse також визначає тип TypeScript для value(), тому визначення схеми служать подвійній меті як runtime валідатори та генератори типів.
rxResource зі stream та params
Angular перейменовує loader на stream та request на params в rxResource. Це вирівнює іменування з семантикою потокової передачі, яку підтримує 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((.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 дає повний контроль над конвеєром Observable. Оператори такі як debounceTime чи retry можуть бути з'єднані в ланцюжок всередині stream. Проте, httpResource обробляє найпоширеніший випадок (один GET запит) з меншою кількістю коду.
Готовий до співбесід з Angular?
Практикуйся з нашими інтерактивними симуляторами, flashcards та технічними тестами.
Відстеження статусу: рядкові літерали замінюють enum
ResourceStatus це тип об'єднання рядків. Шість можливих значень надають детальний погляд на життєвий цикл ресурсу:
| Статус | Значення |
|---|---|
'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() на ресурсі у статусі 'error' викидає runtime виняток. Завжди захищайте читання за допомогою hasValue() або перевіряйте status() перед доступом до value(). Це зміна порівняно з Angular 19, де value() повертав undefined при помилці.
Питання для співбесід Angular httpResource
Resource API є стандартною темою в питаннях для співбесід Angular. Нижче наведені питання, які тестують реальне розуміння, що виходить за межі поверхневого знайомства з API.
П: Яку проблему вирішує httpResource, яку не вирішує resource()?
resource() використовує fetch() або будь-який loader на основі Promise, обходячи Angular HttpClient. Це означає, що інтерцептори (для auth токенів, логування, обробки помилок) не застосовуються. httpResource використовує HttpClient внутрішньо, тому інтерцептори, утиліти тестування (HttpTestingController) та функції такі як withFetch() працюють без додаткового налаштування.
П: Коли rxResource слід віддавати перевагу над httpResource?
rxResource надає повний контроль над Observable через функцію stream. Обирайте його, коли конвеєр даних вимагає операторів RxJS: debouncing пошукового вводу, повтор невдалих запитів з експоненційним відступом або комбінування кількох потоків за допомогою combineLatest. Для простих GET запитів httpResource вимагає менше коду.
П: Як Angular обробляє конкурентні запити, коли сигнал швидко змінюється?
Всі три варіанти ресурсів скасовують очікуючі запити, коли params продукує нове значення. Для httpResource та resource(), AbortSignal скасовує базовий fetch. Для rxResource, Angular відписується від попереднього Observable. Це запобігає перезапису свіжих даних застарілими відповідями.
П: Яке призначення статусу 'local'?
Виклик .set() або .update() на ресурсі змінює його значення локально без виклику loader. Статус переходить в 'local', вказуючи, що поточне значення не прийшло від сервера. Це підтримує оптимістичні оновлення UI: інтерфейс відображає зміну негайно, поки окремий запит мутації виконується.
П: Як працює інтеграція Zod з httpResource?
Опція parse приймає будь-яку функцію з сигнатурою (data: unknown) => T. Коли приходить HTTP відповідь, httpResource пропускає розпарсений JSON через parse перед встановленням value(). Якщо parse викидає (наприклад, ZodError), ресурс переходить у статус 'error'. Тип повернення parse визначає тип TypeScript для value(), надаючи як runtime безпеку, так і типи часу компіляції з одного визначення схеми.
П: Як chain() обробляє помилки від вищестоящих ресурсів?
При використанні chain(upstreamResource) у функції params ресурсу, залежний ресурс не виконує свій loader, якщо вищестоящий ресурс завантажується, знаходиться в стані idle або має помилку. Якщо вищестоящий ресурс повертає помилку, залежний ресурс переходить у стан помилки з ResourceDependencyError. Ця помилка включає dependency (вищестоящий ресурс) та cause (фактична вищестояща помилка), дозволяючи розрізняти "мій запит не вдався" від "запит моєї залежності не вдався".
Для глибшого занурення в Angular signals та як вони інтегруються з ширшим фреймворком, посібник по сигналам охоплює обчислювані сигнали, ефекти та модель реактивності.
Міграція з підписок HttpClient на httpResource
Існуючі Angular застосунки зазвичай отримують дані за допомогою підписок 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()
}Міграція усуває шаблонний код керування життєвим циклом. Очищення підписки відбувається автоматично при знищенні компонента. Стани завантаження та помилки вбудовані в ресурс, без потреби в окремих boolean прапорцях.
Для застосунків, що вже використовують standalone components, міграція проста: замініть ін'єкцію HttpClient та логіку підписки однією декларацією httpResource.
Починай практикувати!
Перевір свої знання з нашими симуляторами співбесід та технічними тестами.
Джерела
- Документація Angular Resource API - офіційний посібник по resource(), rxResource() та httpResource()
- Що нового в Angular 22.0 - огляд Ninja Squad стабільного Resource API та helper chain()
- Посібник по HTTP інтерцепторам Angular - як інтерцептори працюють з httpResource
Ключові висновки щодо Angular Resource API
httpResourceзамінює ручні підпискиHttpClientоднією реактивною декларацією, яка автоматично обробляє завантаження, помилки та скасування- Використовуйте
resource()для API на основі Promise,rxResource()для конвеєрів Observable з операторами RxJS таhttpResource()для стандартних HTTP викликів з підтримкою інтерцепторів - Helper
chain()(Angular 22+) дозволяє завантажувати залежні ресурси з автоматичним поширенням статусу; якщо вищестоящий ресурс зазнає невдачі, залежний отримуєResourceDependencyError - Значення статусу це рядкові літерали (
'idle','loading','resolved','error','reloading','local') - Опція
parseнаhttpResourceінтегрує Zod або Valibot для runtime валідації схеми, яка також визначає типи TypeScript - Виклик
value()на ресурсі у стані помилки викидає; завжди захищайте доступ за допомогоюhasValue()або перевіряйтеstatus()спочатку - Всі варіанти ресурсів автоматично скасовують активні запити, коли залежності змінюються, запобігаючи гонитві за даними без ручної логіки скасування
Чи знайдеш ти помилку в Angular?
Справжній фрагмент коду, прихована помилка, одна спроба на день. Щоб спробувати, акаунт не потрібен.

Автор:
Anthony Fillion-MailletЗасновник SharpSkill
Fullstack-розробник понад 10 років. Керує SharpSkill і відповідає за все, що тут публікується.
Оновлено 13 жовтня 2026 р.
Теги
Поділитися
Пов'язані статті

NgRx Signal Store vs Класичний NgRx у 2026: Який Обрати?
Комплексне порівняння NgRx Signal Store та класичного NgRx для управління станом в Angular. Функції NgRx 22 з практичними прикладами коду та рекомендаціями.

RxJS в Angular 2026: оператори, Subject'и та interop із Signals
RxJS в Angular 2026: опануйте оператори, Subject'и та патерни interop із Signals, які розробники використовують у продакшені, плюс найпоширеніші питання співбесід.

Просунутий Dependency Injection в Angular 2026: Providers, Tokens та Питання на Співбесідах
Комплексний посібник з просунутих механізмів Dependency Injection в Angular. Providers, InjectionToken, ієрархія інжекторів та найпоширеніші питання на технічних співбесідах.