Архитектура FSD для Angular - практическое руководство

05 января 2026
Автор

Олег Марков

Введение

Подход Feature Sliced Design (FSD) появился как ответ на проблему роста сложности фронтенд‑приложений. Чем больше экранов и бизнес‑логики, тем тяжелее поддерживать код, в котором всё «перемешано» по техническим признакам — по видам слоев (components, services, directives и т.п.) или по модулям, которые отражают только маршруты.

В экосистеме React FSD уже достаточно распространен. В Angular мире подход только набирает обороты, и часто его называют angular-fsd — просто как адаптацию идей FSD под стандартные инструменты Angular (NgModules, standalone components, DI, RxJS).

Здесь я покажу вам, как можно:

  • переложить идеи FSD на Angular;
  • построить структуру проекта;
  • организовать зависимости между слоями;
  • использовать Angular‑специфику (модули, DI, lazy loading) так, чтобы она не ломала архитектуру;
  • постепенно мигрировать к FSD в уже существующем проекте.

Я буду опираться на классическую концепцию FSD: слои (layers), срезы по фичам (features), ограничение зависимостей и явные публичные API.


Базовые принципы FSD в Angular

Что такое FSD в контексте Angular

Идея FSD — строить архитектуру вокруг бизнес‑возможностей (фич), а не вокруг технических сущностей. Вместо структуры вида:

  • components/
  • services/
  • models/
  • pages/

вы двигаетесь к структуре:

  • app/
    • processes/
    • pages/
    • features/
    • entities/
    • shared/

где каждый слой решает свою задачу и имеет четкие правила, кому он может зависеть и что может экспортировать.

В Angular эта концепция хорошо ложится на:

  • модули (NgModule / standalone component + routing);
  • сервисы (через DI);
  • файлы с моделями и утилитами (TypeScript);
  • lazy loading модулей по маршрутам.

Смотрите, концептуально FSD для Angular отвечает на вопросы:

  1. Куда положить новый компонент?
  2. Где разместить бизнес‑логику?
  3. Какие зависимости допустимы между частями приложения?
  4. Как сделать так, чтобы код не превращался в «общую свалку» shared?

Основные слои в angular-fsd

На практике чаще всего используют 5–6 слоев:

  1. app — точка входа, глобальная конфигурация приложения.
  2. processes — долгоживущие пользовательские сценарии, которые объединяют несколько страниц и фич.
  3. pages — экраны и маршруты (то, что привязано к URL).
  4. features — независимые бизнес‑функции, которые могут быть использованы на разных страницах.
  5. entities — бизнес‑сущности (User, Product, Order и т.п.).
  6. shared — переиспользуемые «технические» части: UI‑компоненты, утилиты, библиотеки.

Чуть дальше я покажу, как это выглядит в структуре файлов и модулей.


Структура проекта angular-fsd

Базовый шаблон структуры

Давайте разберемся на примере условного интернет‑магазина:

  • каталог товаров;
  • корзина;
  • оформление заказа;
  • профиль пользователя.

Предложенная структура:

src/
  app/
    app.module.ts
    app-routing.module.ts
    providers/        // Глобальные провайдеры, если нужны
    layout/           // Общий каркас приложения (shell)
  processes/
    checkout/         // Цепочка корзина -> оформление -> оплата
  pages/
    home/
    catalog/
    product/
    cart/
    profile/
  features/
    add-to-cart/
    manage-cart/
    auth-by-email/
    update-profile/
  entities/
    product/
    cart/
    user/
  shared/
    ui/
      button/
      input/
      modal/
    lib/
      form/
      date/
      http/
    config/
    styles/

Ключевые моменты:

  • каждая директория верхнего уровня — это слой;
  • внутри слоев — папки конкретных фич, сущностей или страниц;
  • у каждой фичи/сущности есть собственное «локальное» API.

Пример структуры одной фичи

Теперь вы увидите, как это выглядит внутри фичи add-to-cart:

features/
  add-to-cart/
    ui/                 // Компоненты UI этой фичи
      add-to-cart-button/
    model/              // Бизнес-логика, состояние и эффекты
      add-to-cart.facade.ts
      add-to-cart.types.ts
    lib/                // Вспомогательные функции
      map-product-to-cart-item.ts
    index.ts            // Публичный API фичи

Обратите внимание:

  • UI и логика разнесены, но остаются «рядом» внутри фичи;
  • наружу вы экспортируете только то, что нужно — через index.ts;
  • извне никто не лезет во внутреннюю структуру фичи напрямую.

Пример index.ts:

// features/add-to-cart/index.ts

// Экспортируем публичные компоненты и сервисы фичи
export * from './ui/add-to-cart-button/add-to-cart-button.component';
export * from './model/add-to-cart.facade';
export * from './model/add-to-cart.types';

Так вы формируете четкий контракт фичи: все, что нужно внешнему коду, импортируется только из features/add-to-cart.


Слои и их ответственность

Слой app

Слой app отвечает за:

  • инициализацию приложения;
  • глобальный layout (header, sidebar);
  • глобальный роутинг;
  • регистрацию глобальных провайдеров.

Структурно:

app/
  app.module.ts
  app-routing.module.ts
  layout/
    shell/
      shell.component.ts

Пример app-routing.module.ts с lazy loading страниц:

// app/app-routing.module.ts

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { ShellComponent } from './layout/shell/shell.component';

const routes: Routes = [
  {
    path: '',
    component: ShellComponent, // Общий макет приложения
    children: [
      {
        path: '',
        loadChildren: () =>
          import('../pages/home/home.module').then(m => m.HomeModule),
      },
      {
        path: 'catalog',
        loadChildren: () =>
          import('../pages/catalog/catalog.module').then(m => m.CatalogModule),
      },
      {
        path: 'cart',
        loadChildren: () =>
          import('../pages/cart/cart.module').then(m => m.CartModule),
      },
    ],
  },
];

@NgModule({
  imports: [
    // Здесь мы регистрируем маршруты приложения
    RouterModule.forRoot(routes),
  ],
  exports: [RouterModule],
})
export class AppRoutingModule {}

Комментарии:

  • здесь мы не пишем бизнес‑логику;
  • роутинг перенаправляет на модули страниц (pages).

Слой pages

pages — это экраны. Они:

  • определяют маршруты (route config) для конкретного сегмента URL;
  • собирают из фич и сущностей интерфейс страницы;
  • не содержат «глубокую» бизнес‑логику.

Пример страницы CatalogPage:

pages/
  catalog/
    catalog.module.ts
    catalog-routing.module.ts
    ui/
      catalog-page/
        catalog-page.component.ts

Теперь давайте посмотрим, что происходит в модуле:

// pages/catalog/catalog.module.ts

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { CatalogRoutingModule } from './catalog-routing.module';
import { CatalogPageComponent } from './ui/catalog-page/catalog-page.component';
import { ProductListComponent } from '../../entities/product/ui/product-list/product-list.component';
import { AddToCartButtonComponent } from '../../features/add-to-cart';

@NgModule({
  declarations: [
    CatalogPageComponent, // Страница как контейнер
  ],
  imports: [
    CommonModule,
    CatalogRoutingModule,
    ProductListComponent,     // Standalone компонент сущности
    AddToCartButtonComponent, // Standalone компонент фичи
  ],
})
export class CatalogModule {}

А вот пример компонента страницы:

// pages/catalog/ui/catalog-page/catalog-page.component.ts

import { ChangeDetectionStrategy, Component } from '@angular/core';
import { ProductListFacade } from '../../../../entities/product/model/product-list.facade';

@Component({
  selector: 'app-catalog-page',
  templateUrl: './catalog-page.component.html',
  styleUrls: ['./catalog-page.component.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CatalogPageComponent {
  // Здесь мы получаем данные каталога из сущности Product
  products$ = this.productListFacade.products$;

  constructor(
    // Внедряем фасад, который инкапсулирует работу с сущностью Product
    private readonly productListFacade: ProductListFacade,
  ) {}

  // Метод реакции на событие из шаблона
  onProductSelected(productId: string): void {
    // Внутри страницы мы не описываем сложную бизнес-логику
    // Здесь только связываем события и вызываем методы из фич/сущностей
    this.productListFacade.selectProduct(productId);
  }
}

Страница — это «режиссер», который говорит: здесь отрисовать такой список, сюда — кнопку фичи и т.д.

Слой features

Фичи — центральная часть FSD. Они:

  • реализуют бизнес‑функции: «добавить в корзину», «авторизоваться», «оформить заказ»;
  • имеют свое состояние, логику, UI;
  • используются на разных страницах.

Для Angular удобно придерживаться паттерна «фасад + компоненты»:

features/
  add-to-cart/
    ui/
      add-to-cart-button/
        add-to-cart-button.component.ts
    model/
      add-to-cart.facade.ts
      add-to-cart.types.ts
    index.ts

Покажу вам, как это реализовано на практике.

Фасад:

// features/add-to-cart/model/add-to-cart.facade.ts

import { Injectable } from '@angular/core';
import { CartFacade } from '../../../entities/cart/model/cart.facade';
import { Product } from '../../../entities/product/model/product.types';

@Injectable({
  providedIn: 'root', // Или в модуле фичи, если хотите ограничить область
})
export class AddToCartFacade {
  constructor(
    // Используем сущность Cart для выполнения операции
    private readonly cartFacade: CartFacade,
  ) {}

  // Публичный метод фичи - добавить товар в корзину
  add(product: Product): void {
    // Здесь мы можем описать бизнес-правила фичи
    // Например - логировать событие или проверять ограничения
    this.cartFacade.addItem({
      productId: product.id,
      price: product.price,
      quantity: 1,
    });
  }
}

Компонент кнопки:

// features/add-to-cart/ui/add-to-cart-button/add-to-cart-button.component.ts

import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
import { AddToCartFacade } from '../../model/add-to-cart.facade';
import { Product } from '../../../../entities/product/model/product.types';

@Component({
  selector: 'app-add-to-cart-button',
  templateUrl: './add-to-cart-button.component.html',
  styleUrls: ['./add-to-cart-button.component.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush,
  standalone: true,
})
export class AddToCartButtonComponent {
  // Принимаем продукт, для которого отображаем кнопку
  @Input() product!: Product;

  // Опциональный output для страницы или других компонентов
  @Output() added = new EventEmitter<void>();

  constructor(
    // Внедряем фасад фичи
    private readonly addToCartFacade: AddToCartFacade,
  ) {}

  onClick(): void {
    // Вызываем бизнес-логику фичи
    this.addToCartFacade.add(this.product);
    // Сообщаем наружу что действие выполнено
    this.added.emit();
  }
}

Так фича полностью инкапсулирует сценарий «добавить в корзину». Страница просто вставляет кнопку и передает ей product.

Слой entities

Сущности — это фундамент бизнес‑модели. Они отвечают за:

  • данные и их структуру;
  • базовые операции (CRUD);
  • синхронизацию с API и кеширование.

Структура сущности cart:

entities/
  cart/
    model/
      cart.facade.ts
      cart.store.ts
      cart.types.ts
      cart.api.ts
    ui/
      cart-widget/
        cart-widget.component.ts
    index.ts

Фасад корзины:

// entities/cart/model/cart.facade.ts

import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs';
import { CartItem } from './cart.types';

@Injectable({
  providedIn: 'root',
})
export class CartFacade {
  // Здесь мы храним текущее состояние корзины
  private readonly itemsSubject = new BehaviorSubject<CartItem[]>([]);

  // Публичный поток для подписчиков
  readonly items$: Observable<CartItem[]> = this.itemsSubject.asObservable();

  // Метод для добавления элемента в корзину
  addItem(item: CartItem): void {
    const items = this.itemsSubject.getValue();
    const existing = items.find(i => i.productId === item.productId);

    if (existing) {
      // Обратите внимание - мы не мутируем массив напрямую
      const updated = items.map(i =>
        i.productId === item.productId
          ? { ...i, quantity: i.quantity + item.quantity }
          : i,
      );
      this.itemsSubject.next(updated);
    } else {
      this.itemsSubject.next([...items, item]);
    }
  }

  // Метод очистки корзины
  clear(): void {
    this.itemsSubject.next([]);
  }
}

Сущность может также содержать работу с API. Важно, чтобы:

  • сущность не знала о конкретных страницах;
  • сущность могла использоваться разными фичами.

Слой shared

shared — это общий инструментальный слой. Здесь главное не допускать, чтобы в него уходило все подряд.

Чаще всего в shared выделяют:

  • ui — атомарные и простые UI‑компоненты без жесткой бизнес‑логики (Button, Input, Modal);
  • lib — функции и классы общего назначения (validators, date, http, forms);
  • config — общие конфиги (например, значения из environment в удобной обертке).

Пример shared/ui/button/button.component.ts:

// shared/ui/button/button.component.ts

import { ChangeDetectionStrategy, Component, Input } from '@angular/core';

@Component({
  selector: 'app-button',
  template: `
    <!-- Универсальная кнопка с несколькими вариациями стилей -->
    <button
      [ngClass]="['btn', variant]"
      [disabled]="disabled"
      type="button"
    >
      <ng-content></ng-content>
    </button>
  `,
  styleUrls: ['./button.component.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush,
  standalone: true,
})
export class ButtonComponent {
  // Вариант дизайна кнопки
  @Input() variant: 'primary' | 'secondary' | 'danger' = 'primary';

  // Флаг блокировки кнопки
  @Input() disabled = false;
}

Эта кнопка ничего не знает о бизнес‑логике и может использоваться в любом слое.


Правила зависимостей между слоями

Чтобы FSD в Angular был полезен, важно следовать правилам зависимостей. Иначе через несколько месяцев структура снова превратится в хаос.

Типичная матрица зависимостей:

  • app может зависеть от всех слоев.
  • processes может зависеть от pages, features, entities, shared.
  • pages может зависеть от features, entities, shared.
  • features может зависеть от entities, shared.
  • entities может зависеть только от shared.
  • shared не должен зависеть ни от кого выше себя.

Практические советы:

  1. Не импортируйте страницу в фичу. Если вам кажется, что это нужно, значит фича «знает слишком много».
  2. Фичи не должны зависеть друг от друга напрямую. Вместо этого вы можете:
    • вынести общую часть в сущность;
    • или поднять композицию на уровень страницы/процесса.
  3. Избегайте бизнес‑логики в shared. Shared — не свалка. Если код связан с конкретной сущностью или фичей — поместите его туда.

Публичный API и barrel файлы

FSD очень опирается на идею явных публичных API. В Angular это удобно реализовать через barrel файлы (index.ts), как вы уже видели в примере фичи.

Зачем нужен публичный API

  • скрыть внутреннюю структуру модуля;
  • регулировать, какие части доступны извне;
  • облегчить рефакторинг структуры директорий.

Например, у вас есть фича auth-by-email:

features/
  auth-by-email/
    ui/
      login-form/
      register-form/
    model/
      auth.facade.ts
      auth.types.ts
    index.ts

index.ts:

// features/auth-by-email/index.ts

// Экспортируем только то, что должно быть доступно другим слоям
export * from './ui/login-form/login-form.component';
export * from './ui/register-form/register-form.component';
export * from './model/auth.facade';
export * from './model/auth.types';

Теперь в странице авторизации импорт выглядит так:

// pages/auth/auth.module.ts

import { LoginFormComponent } from '../../features/auth-by-email';

Если вы решите переименовать внутренние папки, переписывать импорты по всему приложению не придется — достаточно обновить index.ts.


Использование Angular DI в FSD

Инъекция зависимостей (DI) — ключевой механизм Angular. В контексте FSD есть несколько рекомендаций.

Где объявлять провайдеры

Рекомендация:

  • сущности и фичи с глобальным состоянием — providedIn: 'root' или в отдельном модуле слоя;
  • локальные фичи, которые должны жить в пределах страницы/процесса — провайдеры в модуле страницы или в компоненте (через providers).

Пример локального фасада фичи, привязанного к странице:

// features/update-profile/model/update-profile.facade.ts

import { Injectable } from '@angular/core';
import { UserFacade } from '../../../entities/user/model/user.facade';

@Injectable()
export class UpdateProfileFacade {
  constructor(
    private readonly userFacade: UserFacade,
  ) {}

  // Здесь мы описываем операции по обновлению профиля
}

Провайдер в модуле страницы:

// pages/profile/profile.module.ts

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ProfileRoutingModule } from './profile-routing.module';
import { ProfilePageComponent } from './ui/profile-page/profile-page.component';
import { UpdateProfileFacade } from '../../features/update-profile/model/update-profile.facade';

@NgModule({
  declarations: [ProfilePageComponent],
  imports: [CommonModule, ProfileRoutingModule],
  providers: [
    // Фасад фичи будет создан в scope этого модуля
    UpdateProfileFacade,
  ],
})
export class ProfileModule {}

Так вы контролируете жизненный цикл и область видимости фич.


Реализация процессов в angular-fsd

Слой processes нужен не всегда. Он особенно полезен, когда у вас есть сложные многошаговые сценарии:

  • онбординг пользователя;
  • checkout с несколькими шагами;
  • wizard для создания сложного объекта.

Пример процесса checkout

Представим процесс checkout:

processes/
  checkout/
    model/
      checkout.facade.ts
    ui/
      checkout-flow/
        checkout-flow.component.ts
    index.ts

checkout.facade.ts может объединять:

  • данные корзины (entities/cart);
  • данные пользователя (entities/user);
  • фичи оплаты и доставки (features/select-delivery, features/select-payment).

Пример фасада процесса:

// processes/checkout/model/checkout.facade.ts

import { Injectable } from '@angular/core';
import { CartFacade } from '../../../entities/cart/model/cart.facade';
import { UserFacade } from '../../../entities/user/model/user.facade';

@Injectable()
export class CheckoutFacade {
  constructor(
    private readonly cartFacade: CartFacade,
    private readonly userFacade: UserFacade,
  ) {}

  // Пример сложного бизнес-сценария
  async placeOrder(): Promise<void> {
    // Получаем текущие данные корзины и пользователя
    const cartItems = await this.cartFacade.items$.pipe(take(1)).toPromise();
    const user = await this.userFacade.user$.pipe(take(1)).toPromise();

    // Здесь мы могли бы вызвать API оформления заказа
    // и обработать различные бизнес-правила

    // После успешного оформления очищаем корзину
    this.cartFacade.clear();
  }
}

Компонент процесса может управлять шагами, состоянием и отображением:

// processes/checkout/ui/checkout-flow/checkout-flow.component.ts

import { ChangeDetectionStrategy, Component } from '@angular/core';
import { CheckoutFacade } from '../../model/checkout.facade';

@Component({
  selector: 'app-checkout-flow',
  templateUrl: './checkout-flow.component.html',
  styleUrls: ['./checkout-flow.component.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush,
  providers: [CheckoutFacade],
})
export class CheckoutFlowComponent {
  // Здесь могли бы быть observables с шагами и статусом

  constructor(private readonly checkoutFacade: CheckoutFacade) {}

  onConfirm(): void {
    // Запускаем процесс оформления заказа
    this.checkoutFacade.placeOrder();
  }
}

Процесс — это «оркестратор» нескольких фич и сущностей.


Как мигрировать существующий Angular проект к FSD

Миграция — самый частый вопрос. Давайте посмотрим, как можно двигаться постепенно.

Шаг 1. Выделить слои

Сначала не трогайте внутренности модулей, а просто:

  1. Создайте директории pages, features, entities, shared, processes.
  2. Переместите существующие модули страниц в pages.
  3. Вынесите наиболее очевидные сущности (User, Product и т.п.) в entities.

На этом этапе код может быть еще «грязным», но у вас уже появится базовый каркас.

Шаг 2. Ввод публичных API

Далее:

  • создайте index.ts в каждой фиче и сущности;
  • постепенно переведите импорты на использование этих barrel файлов.

Так вы создадите слой абстракции, который позже облегчит рефакторинг структуры.

Шаг 3. Разделение features и entities

Посмотрите на код страниц:

  • все, что связано с конкретным бизнес‑сценарием (например, «подписаться на рассылку») — кандидат в features;
  • всё, что описывает объект и его данные (User, Product) — в entities.

Переносите код постепенно, не пытаясь сделать всё за один раз.

Шаг 4. Ограничение зависимостей

Внедрите простое правило:

  • новый код должен соблюдать правила зависимостей FSD;
  • старый код трогаете только по мере необходимости.

Можно добавить ESLint‑правила (через import/no-restricted-paths или похожие плагины), которые запрещают «прыгать» через слои.


Практический пример: мини‑фича в angular-fsd

Давайте посмотрим на законченный пример небольшой фичи — подсчет и отображение количества товаров в корзине в хедере.

Задача

В шапке приложения нужно отобразить иконку корзины и число товаров в ней. Логика подсчета уже есть в entities/cart.

Где размещать код

  • отображение в хедере — это часть layout (слой app);
  • данные о количестве товаров — из entities/cart;
  • UI‑элемент корзины в хедере — можно оформить как небольшую фичу features/cart-indicator.

Структура

features/
  cart-indicator/
    ui/
      cart-indicator/
        cart-indicator.component.ts
    index.ts

cart-indicator.component.ts:

// features/cart-indicator/ui/cart-indicator/cart-indicator.component.ts

import { ChangeDetectionStrategy, Component } from '@angular/core';
import { map } from 'rxjs/operators';
import { CartFacade } from '../../../../entities/cart/model/cart.facade';

@Component({
  selector: 'app-cart-indicator',
  template: `
    <!-- Значок корзины с количеством товаров -->
    <a [routerLink]="['/cart']" class="cart-indicator">
      <span class="cart-indicator__icon">🛒</span>
      <span class="cart-indicator__count" *ngIf="count$ | async as count">
        {{ count }}
      </span>
    </a>
  `,
  styleUrls: ['./cart-indicator.component.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush,
  standalone: true,
})
export class CartIndicatorComponent {
  // Здесь мы получаем количество товаров из сущности Cart
  readonly count$ = this.cartFacade.items$.pipe(
    map(items => items.reduce((sum, item) => sum + item.quantity, 0)),
  );

  constructor(
    private readonly cartFacade: CartFacade,
  ) {}
}

Потом используем компонент в хедере:

// app/layout/header/header.component.ts

import { ChangeDetectionStrategy, Component } from '@angular/core';
import { CartIndicatorComponent } from '../../../features/cart-indicator';

@Component({
  selector: 'app-header',
  template: `
    <header class="app-header">
      <!-- Логотип -->
      <a routerLink="/" class="app-header__logo">Store</a>

      <!-- Индикатор корзины -->
      <app-cart-indicator></app-cart-indicator>
    </header>
  `,
  styleUrls: ['./header.component.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush,
  standalone: true,
  imports: [CartIndicatorComponent],
})
export class HeaderComponent {}

Так вы повторно используете сущность cart и при этом сохраняете логику отображения в границах конкретной фичи.


Итог

Подход FSD в Angular (angular-fsd) помогает организовывать код вокруг бизнес‑логики и пользовательских сценариев, а не вокруг технических слоев. Вы:

  • делите приложение на слои (app, processes, pages, features, entities, shared);
  • ограничиваете зависимости между слоями;
  • организуете код фич и сущностей через фасады и публичные API;
  • используете модули, DI и lazy loading так, чтобы структура приложения оставалась понятной и масштабируемой.

Ключевая идея — каждый новый файл должен «отвечать» на вопрос: к какой фиче или сущности он относится и в каком слое ему место. Если вы будете отвечать на этот вопрос осознанно, архитектура станет предсказуемой и легче в поддержке.


Частозадаваемые технические вопросы

Как лучше совмещать FSD и NgRx в Angular проекте

Рекомендация — привязывать сторы к сущностям и фичам:

  • сторы, описывающие состояние бизнес‑сущности (users, products), размещайте в entities/<entity>/model;
  • сторы, описывающие состояние сложной фичи (например, multi-step формы), размещайте в features/<feature>/model;
  • избегайте «глобального» стора с огромным корневым стейтом, не привязанным к бизнес‑домену.

Для подключения используйте StoreModule.forFeature в модулях соответствующих слоев (страниц или фич).

Как организовать unit-тесты при использовании FSD

Привязывайте tests к тем же директориям:

  • entities/user/model/__tests__/user.facade.spec.ts;
  • features/add-to-cart/ui/add-to-cart-button/__tests__/add-to-cart-button.component.spec.ts.

Так вы тестируете компоненты и фасады в контексте их фич/сущностей и упрощаете навигацию по проекту.

Как поступать с глобальными интерцепторами и HTTP‑клиентами

  • глобальные HTTP‑интерцепторы и базовую настройку HttpClient держите в слое app или shared/lib/http;
  • конкретные API‑сервисы размещайте в entities/<entity>/model (например, user.api.ts), чтобы они соответствовали доменной модели.

Как делить большие фичи на подфичи в FSD

Если фича разрастается, создайте внутри нее поддиректории:

  • features/profile/ с подфичами change-email, change-password, upload-avatar;
  • при этом каждая подфича имеет свой ui, model, index.ts.

Снаружи импорт осуществляется через features/profile/change-email и т.п., что сохраняет читаемость структуры.

Как внедрять FSD в монорепо с несколькими Angular приложениями

Выделите общие entities и shared в отдельные библиотеки (например, через Nx или Angular CLI ng g library), а внутри каждой библиотеки сохраните FSD‑структуру директорий. Приложения в монорепо используют эти библиотеки как внешние пакеты с четким публичным API.

Стрелочка влевоМикрофронтенды в FSD - microfrontends на практике

Все гайды по Fsd

Открыть базу знаний

Отправить комментарий