# Angular 20 Resource API, httpResource та питання для технічних співбесід > Детальний посібник з Resource API в Angular 20: resource(), rxResource() та httpResource() для реактивного отримання даних. Валідація Zod, рядкові статуси, міграція з HttpClient та актуальні питання для технічних співбесід Angular у 2026 році. - Published: 2026-05-30 - Updated: 2026-05-30 - Author: SharpSkill - Tags: angular, angular-20, resource-api, httpresource, signals, typescript, tutorial - Reading time: 10 min --- Angular 20 перетворив Resource API з експериментальної функціональності на стабільний примітив фреймворку. Набір інструментів `resource()`, `rxResource()` та `httpResource()` надає розробникам декларативну модель для завантаження, відстеження та інвалідації асинхронних даних, тісно інтегровану із системою сигналів Angular. Ці примітиви усувають необхідність ручного керування підписками, прапорцями стану завантаження та хуками життєвого циклу компонентів — класичними джерелами випадкової складності в Angular-застосунках. У статті детально розглядаються три варіанти Resource API, продемонстровано практичні сценарії з прикладами коду, описано підхід до валідації відповідей сервера через Zod та зібрано питання, що найчастіше зустрічаються на технічних співбесідах Angular у 2026 році. > **Що змінилося в Angular 20 порівняно з версією 19** > > Resource API вийшов зі стадії developer preview та отримав стабільний статус. Головні зміни стосуються API: параметр `request` перейменовано на `params`, а функції `loader` та `stream` замінили попередні назви у контрактах `resource()` та `rxResource()`. Крім того, `httpResource` підтримує нативну валідацію через опцію `parse`, а тип `ResourceStatus` тепер використовує рядкові літерали (`'loading'`, `'resolved'`, `'error'`) замість числового enum. Семантика залишається незмінною, проте синтаксис потребує оновлення при переході з Angular 19. ## Три варіанти Resource API: resource, rxResource та httpResource Resource API в Angular 20 складається з трьох окремих примітивів, кожен із яких розрахований на конкретний сценарій використання. Вибір між ними визначається архітектурними рішеннями проєкту та вже наявними залежностями. **`resource()`** являє собою фундаментальний варіант на базі Promise. Функція `params` визначає реактивні залежності, а функція `loader` повертає `Promise` із результатом. При кожній зміні значення, яке повертає `params`, loader запускається повторно, а попередній запит автоматично скасовується через `AbortSignal`. Цей варіант доцільно застосовувати для прямих викликів `fetch()` або інтеграцій із Promise-орієнтованими бібліотеками. **`rxResource()`** призначений для кодових баз, де активно використовується RxJS. Функція `stream` (у версії 19 називалася `loader`) повертає `Observable`, а фреймворк внутрішньо керує підпискою та відпискою. Відпадає необхідність у конструкціях на кшталт `takeUntil(this.destroy$)` або ручному `Subject`. Це рішення особливо виправдане, коли логіка завантаження потребує RxJS-операторів: `debounceTime`, `switchMap`, `retry` тощо. **`httpResource()`** є високорівневою спеціалізацією поверх `HttpClient`. Достатньо передати URL або об'єкт конфігурації запиту як реактивну функцію, і фреймворк самостійно побудує HTTP-виклик із урахуванням інтерсепторів та заголовків. На відміну від `resource()`, тут не потрібна явна функція `loader`. Саме цей варіант рекомендовано для переважної більшості стандартних сценаріїв отримання даних. ## Побудова профілю користувача за допомогою resource() Наступний приклад ілюструє роботу `resource()` для реактивного завантаження профілю користувача. Сигнал `userId` слугує реактивною залежністю: зміна його значення автоматично ініціює нове завантаження. ```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 } } ``` Суть цього патерну полягає у чіткому розмежуванні реактивних залежностей та логіки завантаження. Функція `params` виконується всередині реактивного контексту Angular, тому кожен сигнал, який вона зчитує, автоматично стає відстежуваною залежністю. Як тільки `userId` набуває нового значення, фреймворк передає `abortSignal` для скасування попереднього запиту й негайно запускає новий виклик loader. Розробнику не потрібно писати жодного коду очищення. Для глибшого розуміння системи сигналів, що лежить в основі Resource API, варто ознайомитися з посібником щодо [Angular Signals](/technologies/angular/interview-questions/angular-signals). ## Реактивне отримання даних через httpResource `httpResource` спрощує патерн ще більше, позбавляючи від необхідності писати функцію loader. Фреймворк самостійно формує HTTP-запит, використовуючи `HttpClient` разом з інтерсепторами, налаштованими у застосунку. ```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 } } ``` При виклику `filterByCategory()` сигнал `category` оновлюється. Функція конфігурації, передана до `httpResource`, зчитує `this.category()`, тому фреймворк фіксує зміну залежності, скасовує поточний HTTP-запит і запускає новий з актуальними параметрами. За поведінкою це аналогічно оператору `switchMap` у RxJS, проте без додаткового синтаксичного навантаження. > **httpResource працює лише на читання** > > httpResource розроблений виключно для операцій читання (GET-запити). Методи POST, PUT та DELETE не підтримуються. Для операцій запису слід використовувати `HttpClient` напряму або серверні дії. Виклик `.set()` або `.update()` на resource змінює лише локальне значення сигналу без відправлення будь-якого запиту на сервер. ## Валідація схеми за допомогою Zod та httpResource Одна з типових проблем при отриманні даних на фронтенді — розбіжність між структурою, яку очікує клієнтський код, та структурою, яку фактично повертає сервер. Angular 20 розв'язує цю проблему через інтеграцію механізму валідації безпосередньо у `httpResource` завдяки опції `parse`. Використання [Zod](https://zod.dev) дозволяє оголосити очікувану схему та валідувати кожну відповідь автоматично перед тим, як дані стануть доступними у шаблоні. Якщо валідація зазнає невдачі, resource переходить у стан `error` з детальним описом порушення, що унеможливлює рендеринг некоректних даних. ```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 створюються окремими командами, а структура відповідей може змінюватися без попередження. Runtime-валідація через Zod діє як контракт безпеки між фронтендом та бекендом, забезпечуючи негайне виявлення несумісностей. ## rxResource зі stream та params У сценаріях, що потребують RxJS-операторів — наприклад, поле пошуку з debounce — `rxResource` пропонує інтерфейс на основі Observable. Функція `stream` (перейменована з `loader` в Angular 20) отримує реактивні параметри та повертає 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 }, }), }); } ``` Принципова відмінність `rxResource` від `resource` полягає у типі повернення: `Observable` замість `Promise`. Angular бере на себе керування підпискою, автоматичне скасування при зміні параметрів та синхронізацію з циклом change detection. Для проєктів, де вже широко застосовуються RxJS та `HttpClient`, саме `rxResource` є найорганічнішим шляхом міграції. ## Відстеження статусу: рядкові літерали замість enum Кожен екземпляр resource надає сигнал `status()`, що повертає рядковий літерал із поточним станом завантаження. Цей підхід замінив числові enum з попередніх версій, покращивши читабельність коду та зручність використання з конструкцією `@switch` у шаблонах. | Status | Значення | |---|---| | `'idle'` | `params` повернув `undefined` — запит не надсилається | | `'loading'` | Перший запит виконується | | `'reloading'` | Повторний запит після попереднього успішного результату | | `'resolved'` | Дані доступні через `value()` | | `'error'` | Запит завершився невдачею — `error()` містить помилку | | `'local'` | Значення встановлено локально через `.set()` або `.update()` | Розмежування `'loading'` та `'reloading'` дає змогу будувати диференційовані UX-патерни: повноекранний скелетон при першому завантаженні та непомітний індикатор оновлення при перезавантаженні, коли попередні дані залишаються видимими. ```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(); }, }); } ``` Окремої уваги заслуговує стан `'idle'`: коли функція `params` повертає `undefined`, resource не ініціює жодного запиту. Цей патерн реалізує умовне завантаження, де дані запитуються з сервера лише після конкретної дії користувача — обрання фільтра, категорії чи елемента списку. > **Обережно з value() у стані помилки** > > Метод `value()` генерує виключення, якщо resource перебуває у стані `'error'`. Для безпечного доступу до даних слід застосовувати `hasValue()` як умовний захист у шаблоні або метод `value.asReadonly()`, який поверне `undefined` за відсутності даних. Таке проєктне рішення змушує розробника явно обробляти помилкові стани, запобігаючи випадковому рендерингу невалідних даних. ## Питання для співбесід з Angular 20 Resource API Нижче наведено питання, які найчастіше порушуються на технічних співбесідах для позицій Angular-розробників у 2026 році. Відповіді мають достатню глибину для демонстрації практичного володіння Resource API. **Питання 1: Яка різниця між resource(), rxResource() та httpResource() в Angular 20?** `resource()` є базовим примітивом, що приймає функцію `loader`, яка повертає `Promise`. `rxResource()` призначений для RxJS: функція `stream` повертає `Observable`. `httpResource()` являє собою високорівневу абстракцію над `HttpClient`, де достатньо вказати URL та параметри запиту. Усі три варіанти автоматично відстежують реактивні залежності через `params`, скасовують активні запити при зміні параметрів та надають стан через сигнали `value()`, `status()`, `error()` та `isLoading()`. Вибір залежить від контексту: `httpResource` для стандартних сценаріїв, `resource` для не-HTTP джерел, `rxResource` для логіки з RxJS-операторами. **Питання 2: Як Angular реалізує автоматичне скасування запитів у Resource API?** При зміні значення, що повертається функцією `params()`, фреймворк автоматично скасовує поточний запит. У випадку `resource()` це відбувається через `AbortSignal`, переданий у `loader`, який припиняє виконання `fetch()`. Для `rxResource()` фреймворк здійснює відписку від поточного Observable перед створенням нового — поведінка, аналогічна `switchMap`. Для `httpResource()` скасування керується внутрішніми механізмами `HttpClient`. Це запобігає перегоновим умовам (race conditions), коли повільніша відповідь може витіснити актуальнішу — явище, відоме як "stale response override". **Питання 3: Що відбувається, коли params() повертає undefined?** Коли `params()` повертає `undefined`, resource переходить у стан `'idle'` і не ініціює HTTP-запит. Це патерн умовного завантаження: resource залишається неактивним, доки не будуть виконані необхідні передумови. Наприклад, компонент деталей може повертати `undefined`, поки жоден елемент не обрано, і розпочинати завантаження лише після вибору. Стан `'idle'` принципово відрізняється від `'loading'`: жоден запит не виконується й не планується, доки `params()` не поверне визначене значення. **Питання 4: Як інтегрується Zod-валідація з httpResource?** Опція `parse` в `httpResource` приймає функцію валідації, яка виконується над HTTP-відповіддю до того, як значення стане доступним через сигнал `value()`. Передаючи `OrderSchema.parse`, кожну відповідь буде перевірено відповідно до визначеної Zod-схеми. Якщо валідація не пройде, resource перейде у стан `'error'`, а `error()` міститиме об'єкт `ZodError` із переліком порушень. Цей підхід фактично впроваджує runtime-контракт між фронтендом і бекендом, що має критичне значення, коли API розвиваються незалежними командами й зміни схеми можуть відбуватися без попередньої координації. **Питання 5: Чим стан 'loading' відрізняється від 'reloading' у ResourceStatus?** Стан `'loading'` сигналізує, що виконується перший запит і жодних попередніх даних у resource немає. Стан `'reloading'` означає, що новий запит розпочато після попереднього успіху: попереднє значення залишається доступним через `value()` під час виконання нового запиту. Ця відмінність дозволяє будувати різні UX-патерни: skeleton-loader або повноекранний спіннер під час першого завантаження, а при перезавантаженні — дискретний індикатор (прогрес-бар, іконка обертання) із збереженням уже відображених даних, що запобігає мерехтінню порожнього вмісту. Додаткові питання щодо сигналів та SSR в Angular розглянуто у статті про [питання для співбесід з Angular 19](/blog/angular/angular-19-interview-questions-signals-ssr-must-know). ## Міграція з підписок HttpClient на httpResource Resource API кардинально скорочує обсяг коду, потрібного для отримання даних, порівняно з класичним підходом на основі ручних підписок до `HttpClient`. Нижче наведено порівняння, яке наочно демонструє усунення шаблонного коду та явного керування життєвим циклом. ```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() } ``` Компонент у варіанті "before" потребує реалізації `OnInit` для ініціювання запиту, `Subject` для скасування підписки, окремих змінних для даних, стану завантаження та помилки, а також `OnDestroy` для запобігання витокам пам'яті. Варіант "after" зводить усю цю логіку до одного декларативного рядка. Фреймворк внутрішньо бере на себе ініціювання запиту, відстеження стану, скасування та очищення ресурсів при знищенні компонента. Особливо відчутний ефект ця міграція дає у великих кодових базах, де сотні компонентів побудовані за патерном `ngOnInit` + `subscribe` + `takeUntil`. Процес переходу можна виконувати поступово, компонент за компонентом, без впливу на решту застосунку. Детальніше про стратегії міграції компонентів Angular описано у статті про [міграцію на standalone components](/blog/angular/angular-standalone-components-migration-best-practices). ## Висновок Resource API в Angular 20 змінює підхід до отримання даних в Angular-застосунках на фундаментальному рівні. Ключові висновки: - **resource()** є базовим примітивом для асинхронного завантаження через Promise з автоматичним скасуванням запитів через AbortSignal та реактивним відстеженням залежностей - **httpResource()** надає високорівневу абстракцію над HttpClient, що усуває шаблонний код для GET-операцій та нативно інтегрується з інтерсепторами й заголовками - **rxResource()** забезпечує сумісність із RxJS для випадків, де потрібні оператори на кшталт debounceTime чи retry, використовуючи Observable замість Promise - **Валідація через Zod** з опцією `parse` створює runtime-контракти між фронтендом та бекендом, запобігаючи рендерингу некоректних даних - Система **ResourceStatus** із рядковими літералами (`'idle'`, `'loading'`, `'reloading'`, `'resolved'`, `'error'`, `'local'`) уможливлює диференційовані UX-патерни для першого завантаження та наступних оновлень - **Міграція** з патерну `ngOnInit` + `subscribe` + `takeUntil` виконується поступово, суттєво зменшуючи обсяг шаблонного коду та потенційні джерела витоків пам'яті Для команд, які готуються до технічних співбесід з Angular, впевнене володіння Resource API та її патернами використання стало базовою вимогою у 2026 році. Здатність пояснити різницю між трьома варіантами, механізм автоматичного скасування та інтеграцію з валідацією схем свідчить про зріле розуміння реактивної архітектури Angular 20. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/uk/blog/angular/angular-20-resource-api-httpresource-tutorial