Angular Resource API та httpResource: Повний посібник (Стабільно з Angular 22)

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

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

Angular Resource API та httpResource забезпечують сигнал-орієнтоване отримання даних, яке автоматично відстежує стани завантаження, помилки та готовності. Представлені в Angular 20, ці API стали стабільними в Angular 22, замінивши громіздкі патерни підписок HttpClient на реактивні примітиви.

Стабільно з Angular 22

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() обгортає Angular HttpClient напряму. Інтерцептори, утиліти тестування та валідація схеми працюють без додаткового налаштування.

Ключова різниця між httpResource та двома іншими: httpResource використовує HttpClient під капотом, що означає що існуючі інтерцептори продовжують працювати. Оригінальний API resource() повністю обходив HttpClient, що було обмеженням в Angular 19.

Створення профілю користувача з resource()

Функція resource() приймає обчислення params та функцію loader. Коли сигнали всередині params змінюються, loader автоматично виконується повторно.

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
  }
}

Параметр abortSignal дозволяє Angular скасовувати активні запити, коли userId змінюється до завершення попереднього запиту. Це запобігає гонитві за даними без ручного керування підписками.

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

httpResource усуває шаблонний код, поєднуючи оголошення URL та виконання HTTP в одному виклику. Повертає HttpResourceRef, який надає value, isLoading, error, status та headers як сигнали.

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
  }
}

Функція, передана в httpResource, повертає об'єкт конфігурації запиту. Angular відстежує читання сигналів всередині цієї функції, тому зміна category викликає новий GET запит. Якщо запит вже виконується, Angular скасовує його перед запуском нового.

httpResource лише для читання

httpResource призначений для отримання даних (GET запити). Використання для операцій POST, PUT чи DELETE небезпечне, оскільки скасування запиту може перервати мутації на середині виконання. Для операцій запису використовуйте HttpClient напряму або обгортайте мутації в метод сервісу.

Ланцюжок ресурсів з chain()

Angular 22 представив helper chain() для завантаження залежних ресурсів. Коли один ресурс залежить від значення іншого, chain() автоматично поширює стани завантаження та помилки.

user-posts.component.tstypescript
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, щоб виловлювати невідповідності під час виконання замість тихого поширення пошкоджених даних.

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 повертає дані, що не відповідають OrderSchema, ресурс переходить у статус 'error'. Тип повернення функції parse також визначає тип TypeScript для value(), тому визначення схеми служать подвійній меті як runtime валідатори та генератори типів.

rxResource зі stream та params

Angular перейменовує loader на stream та request на params в rxResource. Це вирівнює іменування з семантикою потокової передачі, яку підтримує rxResource. Функція stream отримує контекст Observable і повинна повертати 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((.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()
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();
    },
  });
}

Повернення undefined з params встановлює статус в 'idle' та запобігає виконанню loader. Цей патерн добре працює для умовного отримання даних: показ підказки до тих пір, поки користувач не надасть вхідні дані, потім завантаження даних.

Доступ до value() у стані помилки

Виклик 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 слідує передбачуваному патерну:

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()
}

Міграція усуває шаблонний код керування життєвим циклом. Очищення підписки відбувається автоматично при знищенні компонента. Стани завантаження та помилки вбудовані в ресурс, без потреби в окремих boolean прапорцях.

Для застосунків, що вже використовують standalone components, міграція проста: замініть ін'єкцію HttpClient та логіку підписки однією декларацією 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

Автор:

Anthony Fillion-Maillet

Засновник SharpSkill

Fullstack-розробник понад 10 років. Керує SharpSkill і відповідає за все, що тут публікується.

Оновлено 13 жовтня 2026 р.

Теги

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

Поділитися

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