Перейти до вмісту

Angular

Як влаштований Angular-застосунок, як фреймворк дізнається про зміни, як працювати з RxJS і що змінилося з Angular 13 до актуальної версії 22. На співбесіді це перевіряють і як знання основ, і як готовність підтримувати або оновлювати старий застосунок.

Перед цією темою: JavaScript і TypeScript.

Junior+

З чого складається Angular-застосунок?

Section titled “З чого складається Angular-застосунок?”
Відповідь

Що це

Angular-застосунок — дерево компонентів. Кореневий компонент запускається в main.ts, решта вкладені в його шаблон.

  • Компонент — клас із декоратором @Component і HTML-шаблон. Клас зберігає дані й обробники, шаблон їх показує.
  • Сервіс — клас зі спільною логікою: виклики API, кеш, поточний користувач.
  • Маршрутизатор — показує компонент, який відповідає URL.

Як користуватися

  1. ng generate component orders створює компонент.
  2. Дочірні компоненти й директиви, потрібні шаблону, перелічіть в imports декоратора.
  3. Сервіс отримайте полем класу: private orders = inject(OrderService).

Типова помилка

Забути додати дочірній компонент в imports. Збірка впаде з помилкою 'app-x' is not a known element.

Як влаштовано

Синтаксис у шаблоні Що робить
{{ total }} інтерполяція: значення з класу в текст
[disabled]="busy" прив’язка властивості: клас → DOM
(click)="save()" прив’язка події: DOM → клас
[(value)]="name" двостороння: властивість плюс подія valueChange
  • Директива змінює поведінку елемента: [class.active], ngModel. Компонент — це директива з власним шаблоном.
  • Структурні директиви *ngIf і *ngFor з Angular 17 замінені вбудованими блоками @if і @for.
  • NgModule проти standalone. В Angular 13 компонент оголошували в declarations якогось NgModule. Із v19 компоненти standalone за замовчуванням і перелічують залежності у своїх imports.

Коли що обирати

Спосіб отримати сервіс Коли
inject(Service) у полі за замовчуванням, так радить style guide Angular
параметр конструктора старий код; працює як раніше

Пастки

  • inject() працює лише в injection context: ініціалізатори полів, конструктор, фабрики провайдерів, гарди. Виклик в обробнику події дасть помилку NG0203.
  • Сервіс у providers компонента — окремий екземпляр на кожен екземпляр компонента, а не спільний.

Правило

Нові компоненти — standalone, сервіси — providedIn: 'root', залежності — через inject().

Під капотом

  • AOT-компілятор перетворює шаблон на функції створення й оновлення DOM. Помилки типів у прив’язках видно під час збірки.
  • Пошук провайдера йде знизу вгору: спершу інжектори елементів від компонента до кореня дерева, потім environment-інжектори — маршруту, кореневий, платформний. Не знайшов — NullInjectorError.

Порівняння з ASP.NET Core

Angular Найближчий аналог в ASP.NET Core
providedIn: 'root', @Service() (v22) AddSingleton
providers маршруту екземпляр на розділ застосунку; прямого аналога немає
providers компонента екземпляр на компонент і його піддерево; scopes у .NET не ієрархічні
inject(X, { optional: true }) GetService замість GetRequiredService

Компроміси

  • Standalone робить залежності компонента явними й дозволяє ліниво вантажити окремий компонент. NgModule підтримується, а для переведення є міграція ng generate @angular/core:standalone.
  • @Service() з v22 — короткий запис @Injectable({ providedIn: 'root' }). Він працює лише з inject(), без параметрів конструктора.
  • inject() спрощує успадкування: базовому класу не потрібен super(...) з довгим списком залежностей.
  • Ієрархія дозволяє дати сторінці власний стан через providers, але той самий механізм непомітно плодить дублікати синглтонів.

Як перевірити

Angular DevTools у dev-збірці показує дерево компонентів та інжекторів і звідки прийшов кожен сервіс. Дублікати синглтонів видно там само.

Підсумок: DI в Angular ієрархічний: де оголошено провайдер, там і межа спільного екземпляра.

Приклад
import { Component, Injectable, inject } from '@angular/core';
@Injectable({ providedIn: 'root' }) // один екземпляр на застосунок, як AddSingleton
export class GreetingService {
greet(name: string) {
return `Hello, ${name}!`;
}
}
@Component({
selector: 'app-greeting', // standalone за замовчуванням із v19
templateUrl: './greeting.html',
})
export class Greeting {
private readonly greetings = inject(GreetingService); // замість параметра конструктора
protected message = '';
protected say(name: string) {
this.message = this.greetings.greet(name);
}
}
greeting.html
<input #name value="Angular" />
<button (click)="say(name.value)">Greet</button>
<p [class.empty]="!message">{{ message }}</p> <!-- після кліку: Hello, Angular! -->
Middle+

Як працює виявлення змін: zone.js, OnPush, signals і zoneless?

Section titled “Як працює виявлення змін: zone.js, OnPush, signals і zoneless?”
Відповідь

Що це

Change detection — перевірка, чи збігається те, що на екрані, з даними компонентів. Якщо значення в прив’язці змінилося, Angular оновлює цей шматок DOM.

Що змінилося з Angular 13

  • Тоді перевірку запускав zone.js після кожного кліку, таймера чи HTTP-відповіді — по всьому дереву.
  • Тепер стан тримають у сигналах: signal(0) створює значення, count() читає його, count.set(1) змінює. Angular знає, які шаблони читають сигнал, і оновлює лише їх.
  • Із v21 нові застосунки працюють без zone.js, із v22 компоненти за замовчуванням OnPush.

Типова помилка

Змінювати масив усередині сигналу: items().push(x). Посилання те саме, сигнал не повідомить про зміну. Потрібно items.update(list => [...list, x]).

Як влаштовано із zone.js

  1. zone.js підміняє асинхронні API браузера: setTimeout, Promise, addEventListener, XHR.
  2. Коли задача та її мікрозадачі завершилися, NgZone повідомляє про це, і Angular викликає ApplicationRef.tick().
  3. Дерево компонентів перевіряється згори донизу: кожен вираз шаблону порівнюється з минулим значенням.
  4. У dev-режимі другий прохід ловить значення, що змінилися під час перевірки, — ExpressionChangedAfterItHasBeenCheckedError.

Стратегії

Стратегія Коли компонент перевіряється
Eager (раніше Default) у кожному циклі
OnPush (за замовчуванням із v22) новий input за посиланням, подія в піддереві, markForCheck(), зміна прочитаного сигналу

Сигнали

signal зберігає значення, computed обчислює похідне — ліниво й з кешем, effect виконує побічну дію: лог, запис у localStorage, виклик сторонньої бібліотеки.

Пастки

  • Мутація об’єкта у вході OnPush-компонента: посилання те саме, перевірки не буде.
  • Поле, змінене в setTimeout чи в підписці, в OnPush-компоненті не відмалюється без сигналу або markForCheck().
  • effect, який копіює один сигнал в інший. Для похідних значень є computed і linkedSignal.

Правило

Стан — у сигналах, входи — незмінні, похідне — через computed.

Під капотом

  • Сигнали утворюють граф: шаблони, computed і effect — споживачі, сигнали — джерела. Читання сигналу в реактивному контексті реєструє залежність.
  • set() позначає залежні представлення й планує перевірку. OnPush-предки слугують лише шляхом до них і самі не перевіряються повторно.
  • Без zone.js перевірку запускають лише сповіщення: markForCheck() (його викликає async pipe), ComponentRef.setInput, зміна сигналу з шаблону, обробник події в шаблоні.

Ціна zone.js

  • Перевірка після будь-якої асинхронної події, навіть якщо дані не змінювалися.
  • Вага бандла, час старту й довгі стек-трейси.
  • Нативний async/await zone.js перехопити не може, тому збірка переписує його в генератори.

Компроміси під час міграції

  1. ng update до v21 додає provideZoneChangeDetection(), до v22 — ChangeDetectionStrategy.Eager компонентам без явної стратегії. Поведінка зберігається.
  2. Компоненти переводять на OnPush-сумісний код: сигнали, незмінні входи, async pipe.
  3. NgZone.onStable і onMicrotaskEmpty у zoneless не спрацьовують — їх замінюють на afterNextRender.
  4. Останніми прибирають provideZoneChangeDetection() і zone.js з polyfills.

Як перевірити

  • У dev-збірці provideCheckNoChangesConfig({ exhaustive: true, interval: 1000 }) знаходить прив’язки, змінені без сповіщення.
  • Профайлер Angular DevTools показує тривалість кожного циклу й перевірені компоненти.

Підсумок: zoneless — перехід від «перевіряти все після будь-якої події» до «оновлювати те, про що повідомили», і готовність до нього визначає код компонентів, а не прапорець.

Приклад
import { ChangeDetectionStrategy, Component, computed, effect, signal } from '@angular/core';
@Component({
selector: 'app-cart',
templateUrl: './cart.html',
// Із v22 OnPush — за замовчуванням; в Angular 13 його вказували явно
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class Cart {
protected readonly items = signal<{ name: string; price: number }[]>([]);
// Перерахується лише в разі зміни items
protected readonly total = computed(() => this.items().reduce((sum, i) => sum + i.price, 0));
constructor() {
effect(() => console.log('Items:', this.items().length)); // побічна дія, не стан
}
protected add() {
// Нове посилання: сигнал повідомить про зміну.
// this.items().push(...) не спрацює — посилання те саме
this.items.update(list => [...list, { name: 'Book', price: 10 }]);
}
}
cart.html
<button (click)="add()">Add</button>
<p>Items: {{ items().length }}, total: {{ total() }}</p> <!-- після кліку: Items: 1, total: 10 -->
Режим під час запуску
import { bootstrapApplication } from '@angular/platform-browser';
import { provideZoneChangeDetection, provideZonelessChangeDetection } from '@angular/core';
import { App } from './app/app';
// Angular 20: zoneless вмикають явно; із v21 це поведінка за замовчуванням
bootstrapApplication(App, { providers: [provideZonelessChangeDetection()] });
// Старий застосунок після ng update до v21+ лишається на zone.js так:
// bootstrapApplication(App, { providers: [provideZoneChangeDetection()] });
Middle+

Як працювати з RxJS в Angular?

Section titled “Як працювати з RxJS в Angular?”
Відповідь

Що це

Observable — джерело, яке після підписки видає значення: нуль, одне або багато, а потім завершується чи падає з помилкою.

Ознака Promise Observable
Коли починається робота одразу під час створення під час підписки
Скільки значень одне скільки завгодно
Скасування немає відписка (unsubscribe)

Як користуватися

  • http.get<User[]>('/api/users') повертає Observable, запит піде під час підписки.
  • У шаблоні: users$ | async — підписка й відписка автоматично.
  • У коді: .subscribe(users => ...).

Типова помилка

Підписатися на потік, який сам не завершується (interval, router.events, Subject у сервісі), і не відписатися. Компонент знищено, а підписка живе й тримає його в пам’яті.

Як влаштовано

  • HttpClient видає одне значення й завершується. Кожна підписка — новий запит, відписка до відповіді скасовує його.
  • Оператори в pipe() будують новий потік: map, filter, debounceTime, catchError.

Коли що обирати

Оператор Нове значення, поки минуле не завершено Приклад
switchMap скасовує минуле пошук під час введення
mergeMap виконує паралельно завантаження незалежних файлів
concatMap ставить у чергу збереження, де важливий порядок
exhaustMap ігнорує нове кнопка «Надіслати», вхід

Як відписуватися

  • async pipe — у шаблоні.
  • toSignal(obs$) перетворює потік на сигнал і відписується під час знищення компонента; toObservable(sig) робить зворотне.
  • takeUntilDestroyed() (стабільний із v19) — для ручного subscribe. Поза injection context йому передають DestroyRef.

Пастки

  • Два async pipe на один HTTP-потік — два запити. Спільний результат — через shareReplay або toSignal.
  • takeUntil(destroy$) не останнім у pipe(): оператори після нього можуть лишити підписку живою.
  • Вкладений subscribe усередині subscribe замість switchMap: підписки не скасовуються, помилки губляться.

Правило

Підписку створює шаблон або toSignal; ручний subscribe — лише з takeUntilDestroyed().

Під капотом

  • Observable — функція підписки. pipe() нічого не запускає, а subscribe() будує ланцюжок підписників до джерела.
  • Відписка йде ланцюжком угору. Для HttpClient це скасування запиту: AbortController у fetch-бекенда (за замовчуванням із v22) або abort() у XHR.
  • toObservable віддає значення через effect, тому кілька set() поспіль дадуть одне значення — останнє.

Ціна

  • switchMap скасовує відповідь на клієнті, але не роботу на сервері. Для POST і PUT це втрачені чи задвоєні зміни, тому запис — через concatMap або exhaustMap.
  • mergeMap без ліміту конкурентності (mergeMap(fn, 4)) здатен надіслати сотні запитів разом.
  • shareReplay(1) без refCount: true тримає підписку на джерело й після відходу всіх підписників.

Компроміси

Задача Інструмент
стан для шаблону сигнал
завантаження даних за параметрами httpResource, rxResource (стабільні із v22)
події в часі, WebSocket, debounce, retry RxJS

Як перевірити

  • Витік видно в Chrome DevTools: знімок купи після кількох переходів туди й назад показує від’єднані (detached) елементи й живі екземпляри компонента.
  • На вкладці Network скасовані запити позначені (canceled), а дублікати запитів видають зайву підписку.

Підсумок: RxJS — інструмент для потоків подій і конкурентності, а не для зберігання стану; стан — у сигналах.

Приклад
import { Component, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { FormControl, ReactiveFormsModule } from '@angular/forms';
import { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop';
import { debounceTime, distinctUntilChanged, interval, switchMap } from 'rxjs';
@Component({ selector: 'app-product-search', imports: [ReactiveFormsModule], templateUrl: './product-search.html' })
export class ProductSearch {
private readonly http = inject(HttpClient);
protected readonly query = new FormControl('', { nonNullable: true });
// switchMap скасовує минулий запит, якщо користувач продовжив друкувати
protected readonly products = toSignal(
this.query.valueChanges.pipe(
debounceTime(300),
distinctUntilChanged(),
switchMap(q => this.http.get<{ id: number; name: string }[]>('/api/products', { params: { q } })),
),
{ initialValue: [] }, // відписка — автоматично під час знищення компонента
);
constructor() {
// Нескінченний потік: без takeUntilDestroyed підписка переживе компонент
interval(60_000).pipe(takeUntilDestroyed()).subscribe(n => console.log('tick', n));
}
}
product-search.html
<input [formControl]="query" placeholder="Search" />
@for (p of products(); track p.id) {
<p>{{ p.name }}</p>
}
Middle+

Що змінилося в Angular з версії 13 до актуальної?

Section titled “Що змінилося в Angular з версії 13 до актуальної?”
Відповідь

Що це

Angular 13 вийшов у листопаді 2021, поточна версія — 22 (червень 2026). За цей час змінився сам стиль коду.

Головне

  • Компоненти без NgModule — standalone.
  • Новий синтаксис шаблонів: @if, @for, @switch замість *ngIf і *ngFor.
  • @defer — ліниве завантаження частини шаблону.
  • Сигнали: стан, входи input() і виходи output().
  • inject() замість параметрів конструктора.
  • Збірка на esbuild і Vite — помітно швидша за Webpack.
  • SSR з гідратацією — пакет @angular/ssr замість Angular Universal.
  • Робота без zone.js і OnPush за замовчуванням.

Типова помилка

Думати, що старий код перестав працювати. NgModule, @Input() і *ngIf (deprecated із v20) підтримуються, а ng update сам виправляє те, що ламається.

Коли що з’явилося

Версія Що змінилося
14–15 (2022) standalone-компоненти, типізовані форми, inject(), функціональні гарди й інтерцептори
16 (травень 2023) сигнали (preview), takeUntilDestroyed, гідратація (preview), збірка на esbuild (preview)
17 (листопад 2023) @if, @for, @defer; стабільні сигнали; application builder і Vite для нових проєктів
17.1–19 input(), output(), model(), сигнальні запити; із v19 стабільні, standalone за замовчуванням
20 (травень 2025) стабільні effect, linkedSignal, toSignal; *ngIf і *ngFor deprecated; zoneless стабільний із 20.2
21 (листопад 2025) zoneless і Vitest за замовчуванням у нових проєктах; експериментальні Signal Forms
22 (червень 2026) OnPush за замовчуванням; стабільні Signal Forms, resource і httpResource; @Service(); HttpClient на fetch

Пастки міграції

  • ng update переходить лише на одну мажорну версію за раз. З 13 до 22 — дев’ять кроків, у кожного свої вимоги до Node.js і TypeScript.
  • Сторонні бібліотеки оновлюють разом з Angular: Kendo UI for Angular, angular-oauth2-oidc та інші мають підтримувати цільову версію.
  • Власні налаштування Webpack в application builder не переносяться.

Правило

Спершу довести версії до актуальної зі збереженням поведінки, потім модернізувати код міграціями ng generate @angular/core:... — по одній за раз.

Чому так

  • Сигнали й zoneless прибирають головну ціну старого Angular — перевірку всього дерева на будь-яку подію.
  • Standalone і @defer роблять одиницею завантаження компонент, а не модуль.
  • esbuild і Vite пришвидшують збірку; Webpack-збирачі у v22 оголошено deprecated.
  • Із v22 мажорний реліз виходить раз на 12 місяців, а не раз на пів року. v23 очікується близько червня 2027.

Стратегія міграції

  1. Оновлювати мажор за мажором за update guide, після кожного кроку — збірка й тести.
  2. Автоматичні міграції зберігають поведінку: у v21 застосунку додається provideZoneChangeDetection(), у v22 компонентам без стратегії — ChangeDetectionStrategy.Eager.
  3. Збирач переводять командою ng update @angular/cli --name use-application-builder.
  4. Далі по одній: standalone, control flow, inject(), input() і output(), ліниві маршрути.
  5. Останніми — OnPush і zoneless: вони змінюють логіку, а не синтаксис.

Ціна

  • Кожна мажорна версія піднімає мінімум TypeScript і Node.js: v22 вимагає TypeScript 6.0 і Node.js 22.22+ або 24.13+.
  • Видаляються старі API: у v22 — ComponentFactoryResolver. HttpClient перейшов на fetch, і для прогресу завантаження файлів потрібен withXhr().
  • Станом на жовтень 2026 Kendo UI for Angular 25.x підтримує Angular 20–22, а angular-oauth2-oidc 22.x вимагає Angular 22.

Компроміси

Переписувати все одразу немає потреби: старий і новий стиль співіснують в одному застосунку. Нове пишуть у новому стилі, старе переводять під час змін.

Підсумок: міграція з 13 — спершу безпечне оновлення версій зі збереженням поведінки, потім поступовий перехід на сигнали й zoneless.

Приклад
import { Component, EventEmitter, Input, Output, inject, input, output } from '@angular/core';
import { HttpClient } from '@angular/common/http';
interface User { id: number; name: string; }
// Angular 13: декоратори, конструктор, оголошення в declarations якогось NgModule
@Component({ selector: 'app-user-old', templateUrl: './user-old.component.html' })
export class UserOldComponent {
@Input() user!: User;
@Output() selected = new EventEmitter<User>();
constructor(private http: HttpClient) {}
}
// Angular 22: standalone, сигнальні входи й виходи, inject()
@Component({ selector: 'app-user', templateUrl: './user.html' })
export class UserCard {
readonly user = input.required<User>(); // читається як user()
readonly selected = output<User>();
private readonly http = inject(HttpClient);
}
<!-- Angular 13 -->
<li *ngFor="let o of orders; trackBy: trackById">{{ o.title }}</li>
<p *ngIf="!orders.length">No orders</p>
<!-- Angular 22: track обов'язковий, @empty замість окремого *ngIf -->
@for (o of orders(); track o.id) {
<li>{{ o.title }}</li>
} @empty {
<p>No orders</p>
}
<!-- Код графіка завантажиться, коли блок з'явиться в області видимості -->
@defer (on viewport) {
<app-sales-chart />
} @placeholder {
<p>Chart</p>
}
Senior+

Як будувати великий Angular-застосунок і керувати станом?

Section titled “Як будувати великий Angular-застосунок і керувати станом?”
Відповідь

Що це

Великий застосунок ділять за фічами — бізнес-розділами: замовлення, клієнти, звіти.

  • core/ — те, що потрібне один раз: авторизація, інтерцептори, каркас сторінки.
  • shared/ — спільні UI-компоненти й пайпи без бізнес-логіки.
  • features/orders/ — маршрути, сторінки й стан однієї фічі.

Як користуватися

Фічу підключають лінивим маршрутом: loadChildren: () => import('./orders/orders.routes'). Її код потрапляє в окремий файл (чанк) і завантажується під час першого переходу.

Типова помилка

Статично імпортувати компонент лінивої фічі в іншому місці, наприклад в app.ts. Тоді він потрапить в основний бандл, і ліниве завантаження зникне.

Коли що обирати

Підхід до стану Коли
сервіс із сигналами стан однієї фічі, прості операції
NgRx SignalStore багато сторів, потрібна єдина структура й плагіни: withEntities, події
NgRx Store спільний стан багатьох фіч, складні потоки подій, Redux DevTools

Продуктивність

  • OnPush і сигнали — менше перевірок.
  • track за id в @for — DOM перевикористовується, а не створюється заново.
  • Довгі списки — віртуальне прокручування: cdk-virtual-scroll-viewport з Angular CDK або scrollable="virtual" у Kendo Grid.
  • Важкі віджети — графіки, планувальник — в @defer.

Пастки

  • providedIn: 'root' для стану сторінки: він переживає відхід зі сторінки, і після повернення видно старі дані. Такий стор оголошують у providers маршруту чи компонента.
  • Глобальний стор для всього підряд, включно зі станом однієї форми.
  • Kendo Grid на тисячі рядків без пейджингу чи віртуального прокручування.

Правило

Починати із сервісу на сигналах. SignalStore — коли сторів багато й потрібна єдина форма, NgRx Store — коли стан справді спільний і складний.

Компроміси в стані

  • Більша частина «стану» — кеш серверних даних. Його закривають httpResource або сервіс; глобальний стор потрібен для спільного: користувач, права, налаштування.
  • NgRx Store дає трасування й time travel у Redux DevTools ціною шаблонного коду: action, reducer, selector і effect на операцію.
  • providers маршруту дають стору час життя фічі — як scope в ASP.NET Core, лише на розділ застосунку.

Ціна розміру бандла

  • budgets в angular.json валять збірку, якщо початковий бандл перевищив ліміт.
  • Склад бандла показують ng build --stats-json і аналізатор esbuild.
  • Kendo UI і Angular Material додають ваги: імпортують лише потрібні компоненти, а Chart і Scheduler ховають за @defer.

Бібліотеки компонентів

  • Оновлення Angular обмежене їхніми peerDependencies — перевіряють до ng update.
  • Власна обгортка на кшталт app-data-grid поверх Kendo Grid локалізує налаштування й майбутню заміну бібліотеки.

Авторизація в SPA

Функціональний інтерцептор додає access-токен, гард canMatch ховає недоступні маршрути. Гард — зручність для користувача, права перевіряє API. Де зберігати токени й коли потрібен BFF — у темі OAuth 2.0 і OpenID Connect.

Як перевірити в продакшені

Core Web Vitals реальних користувачів і Lighthouse показують підсумок, вкладка Coverage у Chrome — скільки завантаженого JavaScript не виконалося.

Підсумок: масштаб тримають межі фіч, ліниве завантаження й усвідомлений вибір, де живе стан.

Приклад
app.routes.ts
import { Routes } from '@angular/router';
import { inject } from '@angular/core';
import { AuthService } from './core/auth.service';
import { OrdersStore } from './features/orders/orders.store';
export const routes: Routes = [
{ path: '', loadComponent: () => import('./features/home/home').then(m => m.Home) },
{
path: 'orders',
canMatch: [() => inject(AuthService).isLoggedIn()], // зручність; права перевіряє API
providers: [OrdersStore], // один стор на фічу, живе, поки відкрито розділ
// Окремий чанк: завантажиться під час першого переходу на /orders
loadChildren: () => import('./features/orders/orders.routes').then(m => m.ORDERS_ROUTES),
},
];
NgRx SignalStore
import { computed, inject } from '@angular/core';
import { patchState, signalStore, withComputed, withMethods, withState } from '@ngrx/signals';
import { Order, OrdersApi } from './orders.api';
export const OrdersStore = signalStore(
withState({ orders: [] as Order[], filter: '', loading: false }),
withComputed(({ orders, filter }) => ({
// Похідний стан — звичайний computed
visible: computed(() => orders().filter(o => o.customer.includes(filter()))),
})),
withMethods((store, api = inject(OrdersApi)) => ({
setFilter(filter: string) {
patchState(store, { filter });
},
async load() {
patchState(store, { loading: true });
patchState(store, { orders: await api.loadAll(), loading: false });
},
})),
);