Angular 20 Resource API, httpResource та питання для технічних співбесід

Детальний посібник з Resource API в Angular 20: resource(), rxResource() та httpResource() для реактивного отримання даних. Валідація Zod, рядкові статуси, міграція з HttpClient та актуальні питання для технічних співбесід Angular у 2026 році.

Angular 20 Resource API та httpResource: посібник з прикладами коду та питаннями для співбесід

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 слугує реактивною залежністю: зміна його значення автоматично ініціює нове завантаження.

user-profile.component.tstypescript
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
  }
}

Суть цього патерну полягає у чіткому розмежуванні реактивних залежностей та логіки завантаження. Функція params виконується всередині реактивного контексту Angular, тому кожен сигнал, який вона зчитує, автоматично стає відстежуваною залежністю. Як тільки userId набуває нового значення, фреймворк передає abortSignal для скасування попереднього запиту й негайно запускає новий виклик loader. Розробнику не потрібно писати жодного коду очищення.

Для глибшого розуміння системи сигналів, що лежить в основі Resource API, варто ознайомитися з посібником щодо Angular Signals.

Реактивне отримання даних через httpResource

httpResource спрощує патерн ще більше, позбавляючи від необхідності писати функцію loader. Фреймворк самостійно формує HTTP-запит, використовуючи HttpClient разом з інтерсепторами, налаштованими у застосунку.

product-list.component.tstypescript
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
  }
}

При виклику 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 дозволяє оголосити очікувану схему та валідувати кожну відповідь автоматично перед тим, як дані стануть доступними у шаблоні. Якщо валідація зазнає невдачі, resource переходить у стан error з детальним описом порушення, що унеможливлює рендеринг некоректних даних.

order.component.tstypescript
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 створюються окремими командами, а структура відповідей може змінюватися без попередження. Runtime-валідація через Zod діє як контракт безпеки між фронтендом та бекендом, забезпечуючи негайне виявлення несумісностей.

rxResource зі stream та params

У сценаріях, що потребують RxJS-операторів — наприклад, поле пошуку з debounce — rxResource пропонує інтерфейс на основі Observable. Функція stream (перейменована з loader в Angular 20) отримує реактивні параметри та повертає Observable, яким фреймворк керує самостійно.

search.component.tstypescript
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 },
      }),
  });
}

Принципова відмінність rxResource від resource полягає у типі повернення: Observable замість Promise. Angular бере на себе керування підпискою, автоматичне скасування при зміні параметрів та синхронізацію з циклом change detection. Для проєктів, де вже широко застосовуються RxJS та HttpClient, саме rxResource є найорганічнішим шляхом міграції.

Готовий до співбесід з Angular?

Практикуйся з нашими інтерактивними симуляторами, flashcards та технічними тестами.

Відстеження статусу: рядкові літерали замість enum

Кожен екземпляр resource надає сигнал status(), що повертає рядковий літерал із поточним станом завантаження. Цей підхід замінив числові enum з попередніх версій, покращивши читабельність коду та зручність використання з конструкцією @switch у шаблонах.

| Status | Значення | |---|---| | 'idle' | params повернув undefined — запит не надсилається | | 'loading' | Перший запит виконується | | 'reloading' | Повторний запит після попереднього успішного результату | | 'resolved' | Дані доступні через value() | | 'error' | Запит завершився невдачею — error() містить помилку | | 'local' | Значення встановлено локально через .set() або .update() |

Розмежування 'loading' та 'reloading' дає змогу будувати диференційовані UX-патерни: повноекранний скелетон при першому завантаженні та непомітний індикатор оновлення при перезавантаженні, коли попередні дані залишаються видимими.

status-demo.component.tstypescript
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();
    },
  });
}

Окремої уваги заслуговує стан '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.

Міграція з підписок 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<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()
}

Компонент у варіанті "before" потребує реалізації OnInit для ініціювання запиту, Subject для скасування підписки, окремих змінних для даних, стану завантаження та помилки, а також OnDestroy для запобігання витокам пам'яті. Варіант "after" зводить усю цю логіку до одного декларативного рядка. Фреймворк внутрішньо бере на себе ініціювання запиту, відстеження стану, скасування та очищення ресурсів при знищенні компонента.

Особливо відчутний ефект ця міграція дає у великих кодових базах, де сотні компонентів побудовані за патерном ngOnInit + subscribe + takeUntil. Процес переходу можна виконувати поступово, компонент за компонентом, без впливу на решту застосунку. Детальніше про стратегії міграції компонентів Angular описано у статті про міграцію на standalone components.

Починай практикувати!

Перевір свої знання з нашими симуляторами співбесід та технічними тестами.

Висновок

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.

Теги

#angular
#angular-20
#resource-api
#httpresource
#signals
#typescript
#tutorial

Поділитися

Пов'язані статті