Angular для React-разработчика: от основ до Senior-уровня

Практический курс перехода с React на Angular для опытных frontend-разработчиков. Каждая глава построена на сравнении с React, реальном коде и разборе типичных ошибок миграции. По итогу курса вы уверенно работаете с современным Angular (v17+), проходите собеседования и строите enterprise-приложения.

1. Введение в Angular: история, версии и принципиальные отличия от AngularJS

Введение в Angular: история, версии и принципиальные отличия от AngularJS

Представьте, что вы приходите на новую работу и вам говорят: «Мы используем Angular». Вы киваете, открываете документацию — и видите что-то совершенно непохожее на тот Angular, о котором читали в старых туториалах. Контроллеры, scope, контроллеров и директив. Архитектура была революционной для своего времени, но с ростом приложений обнаружились серьёзные проблемы: производительность деградировала из-за механизма dirty checking, тестируемость оставляла желать лучшего, а мобильные устройства задыхались под нагрузкой.

Angular (Angular 2+) — это полностью переписанный фреймворк, выпущенный в 2016 году. Общего с AngularJS у него только название и компания-разработчик. Новый Angular написан на TypeScript, использует компонентную архитектуру, иерархическую систему Dependency Injection и реактивное программирование через RxJS. Это принципиально другой инструмент.

> Называть Angular 2+ «AngularJS» — всё равно что называть современный автомобиль «каретой» только потому, что оба перевозят людей.

Когда в вакансии пишут «Angular», имеют в виду Angular 2 и выше. AngularJS официально перешёл в режим долгосрочной поддержки и прекратил активное развитие в декабре 2021 года.

Хронология версий: что менялось и почему

Angular придерживается семантического версионирования и выпускает мажорные версии каждые шесть месяцев. Это не значит, что каждая версия ломает всё — команда Google строго следит за обратной совместимостью внутри мажорных версий и предоставляет инструменты миграции (ng update).

| Версия | Год | Ключевые изменения | |--------|-----|-------------------| | Angular 2 | 2016 | Полный переход на TypeScript, компоненты, DI | | Angular 4 | 2017 | Улучшенный компилятор, уменьшение размера бандла | | Angular 6–7 | 2018 | Angular Elements, CLI Workspaces, CDK | | Angular 8–9 | 2019–2020 | Ivy-компилятор (по умолчанию с v9), Differential Loading | | Angular 12–13 | 2021 | Удаление View Engine, улучшения CLI | | Angular 14 | 2022 | Standalone Components (preview), Typed Forms | | Angular 15 | 2022 | Standalone API стабилен, директивные композиции | | Angular 16 | 2023 | Signals (developer preview), esbuild по умолчанию | | Angular 17 | 2023 | Signals стабильны, новый синтаксис шаблонов (@if, @for) | | Angular 18–19 | 2024 | Zoneless режим, улучшенный SSR, Signal-based inputs | | Angular 20 | 2025 | Полная интеграция Signals, SignalStore, SSR/hydration |

Версии 3 не существует — команда пропустила её, чтобы синхронизировать номера пакетов в монорепозитории. Это единственный «пропуск» в истории Angular.

Революция Ivy: почему это важно для React-разработчика

До Angular 9 фреймворк использовал компилятор View Engine. Ivy — это новый движок рендеринга и компиляции, который изменил всё под капотом. Для вас как для разработчика это означает:

Меньший размер бандла. Ivy генерирует код, который лучше поддаётся tree-shaking — удалению неиспользуемого кода. Приложение на Angular 9+ весит значительно меньше, чем аналогичное на Angular 8.

Инкрементальная компиляция. Пересборка при разработке стала быстрее, потому что Ivy перекомпилирует только изменившиеся компоненты.

Улучшенная отладка. В консоли браузера появились читаемые сообщения об ошибках вместо криптографических стектрейсов.

Если вы видите в коде функции с префиксом ɵɵ (например, ɵɵelementStart), это внутренние инструкции Ivy — именно в них компилируются ваши шаблоны. Как показывает анализ внутренней механики Angular, Angular использует подход Incremental DOM: вместо создания виртуального дерева компилятор генерирует инструкции, которые напрямую создают и обновляют реальные DOM-узлы.

!Эволюция Angular: от AngularJS к современному Angular с Ivy и Signals

Signals: современная реактивность Angular

Начиная с Angular 16, фреймворк получил Signals — примитив реактивности, аналогичный сигналам в SolidJS или useState в React, но с принципиально другой моделью обновлений. Сигнал — это контейнер значения, который отслеживает, кто его читает, и уведомляет подписчиков при изменении.

Ключевое отличие от React: Angular автоматически отслеживает зависимости во время выполнения. Не нужно вручную указывать массив зависимостей — фреймворк сам знает, какие сигналы читает computed или effect.

Философия Angular: «batteries included»

React — это библиотека для рендеринга UI. Всё остальное (роутинг, управление состоянием, HTTP-клиент, формы) вы выбираете сами из экосистемы. Angular — это платформа: он включает всё необходимое для разработки enterprise-приложений из коробки.

Это принципиальная разница в философии. Как отмечают практики из AngularUX:

> At 2 a.m., governance beats cleverness. Angular's defaults turn reliability into muscle memory. > > angularux.com

Когда в три часа ночи падает продакшн, вам не нужно вспоминать, какую библиотеку для форм выбрала команда полгода назад. В Angular ответ всегда один: Reactive Forms. Роутинг — Angular Router. HTTP — HttpClient. Это снижает когнитивную нагрузку и ускоряет онбординг новых разработчиков.

Первый проект: Angular CLI

Установка и создание проекта занимают три команды:

CLI задаст несколько вопросов: нужна ли маршрутизация (почти всегда yes) и какой препроцессор стилей использовать. После этого вы получите полностью настроенный проект с TypeScript, ESLint, тестами и сборкой через esbuild.

Структура проекта выглядит так:

Обратите внимание: в современном Angular (17+) нет AppModule по умолчанию — проект создаётся в режиме standalone components, о котором подробно поговорим в статье 11.

Ключевые команды CLI, которые нужно знать сразу

CLI — это не просто генератор файлов. Он управляет зависимостями, применяет schematics (скрипты миграции) при обновлении версий и интегрируется с системами сборки. Для React-разработчика аналогом является Create React App или Vite, но Angular CLI значительно мощнее и охватывает весь жизненный цикл разработки.

Почему Angular выбирают для enterprise

Согласно данным relevant.software, более 26% профессиональных разработчиков называют Angular своим основным веб-фреймворком. Крупные компании — Upwork, PayPal, Deutsche Bank — строят на нём критически важные системы. Причины:

Строгая типизация. TypeScript не опционален в Angular — он встроен в ДНК фреймворка. Это означает, что ошибки типов ловятся на этапе компиляции, а не в продакшне.

Предсказуемая архитектура. Когда в команде 20 разработчиков, важно, чтобы все писали код одинаково. Angular навязывает структуру — это не ограничение, а защита от хаоса.

Долгосрочная поддержка. Google использует Angular во внутренних продуктах (Google Cloud Console, Google Ads). Это гарантирует, что фреймворк не будет заброшен.

Инструменты миграции. Переход с Angular 12 на Angular 20 — это ng update, а не переписывание приложения с нуля.

Для React-разработчика переход на Angular — это не просто изучение нового синтаксиса. Это смена парадигмы: от «выбери сам» к «лучшая практика уже встроена». В следующей статье разберём, как именно эти парадигмы различаются на уровне архитектуры.

10. Change Detection, Zone.js и оптимизация производительности Angular-приложений

Change Detection, Zone.js и оптимизация производительности Angular-приложений

Почему Angular-приложение иногда «тормозит» без видимой причины? Ответ почти всегда один: Change Detection запускается слишком часто или обрабатывает слишком много компонентов. Понимание механизма обнаружения изменений — это разница между разработчиком, который «добавляет OnPush и надеется на лучшее», и разработчиком, который точно знает, почему компонент перерендерился и как это предотвратить.

Как работает Change Detection

Angular должен знать, когда данные изменились, чтобы обновить DOM. В React это происходит явно: вы вызываете setState или dispatch, и React знает, что нужно перерендерить. Angular исторически использовал другой подход — Zone.js.

Zone.js — это библиотека, которая патчит все асинхронные API браузера: setTimeout, setInterval, Promise, fetch, addEventListener и другие. Когда любая из этих операций завершается, Zone.js уведомляет Angular: «что-то произошло, возможно, данные изменились». Angular запускает цикл обнаружения изменений.

Проблема: Zone.js не знает, что именно изменилось. Он только знает, что что-то произошло. Поэтому Angular по умолчанию проверяет все компоненты в дереве при каждом асинхронном событии.

Default vs OnPush: два режима Change Detection

По умолчанию Angular использует стратегию Default — проверяет всё дерево компонентов сверху вниз при каждом событии. Для небольших приложений это нормально. Для больших — катастрофа производительности.

Стратегия OnPush говорит Angular: «Проверяй этот компонент только если:

  • Изменился один из @Input (по ссылке)
  • Компонент или его потомок сгенерировал событие
  • Вручную запущена проверка через ChangeDetectorRef
  • Использован async pipe (он сам триггерит проверку)
  • Изменился Signal, используемый в шаблоне»
  • Критически важно: при OnPush мутация объекта не вызовет перерендер:

    Это то же правило иммутабельности, что и в Redux/React. При OnPush Angular сравнивает @Input по ссылке, а не по значению.

    ChangeDetectorRef: ручное управление

    Иногда нужно вручную контролировать цикл обнаружения изменений:

    markForCheck() — самый важный метод. Он помечает компонент и всех его предков как «требующие проверки» при следующем цикле. Используйте его, когда данные изменяются вне Angular-контекста.

    Signals и Zoneless: будущее Angular

    Angular 16+ представил Zoneless режим — возможность полностью отказаться от Zone.js. В этом режиме Angular обновляет только те компоненты, которые читают изменившийся Signal:

    Это фундаментальный сдвиг: вместо «проверить всё дерево» Angular обновляет только конкретные DOM-узлы, которые читают изменившийся Signal. Производительность сопоставима с SolidJS.

    !Сравнение Default и OnPush Change Detection: как Angular обходит дерево компонентов

    Практические техники оптимизации

    1. TrackBy в @for — всегда

    2. Pure Pipes вместо методов в шаблоне

    3. Виртуализация длинных списков

    4. Запуск тяжёлых операций вне Zone.js

    Профилирование с Angular DevTools

    Angular DevTools (расширение для Chrome) показывает, какие компоненты перерендерились и сколько времени заняла каждая проверка:

  • Откройте DevTools → вкладка Angular
  • Включите «Record» и выполните действие
  • Посмотрите flame chart: красные компоненты — долгие проверки
  • Компоненты с OnPush показывают значительно меньше проверок
  • Как отмечают в AngularUX, использование Angular DevTools flame charts для доказательства снижения рендеров — стандартная практика в enterprise-разработке. Цифры, а не ощущения.

    Чеклист оптимизации Angular-приложения

  • Все компоненты используют ChangeDetectionStrategy.OnPush
  • Все списки используют track с уникальным идентификатором
  • Трансформации данных в шаблоне — через Pure Pipes, не методы
  • Длинные списки () используют виртуализацию через CDK
  • Тяжёлые вычисления вынесены в computed() или Web Workers
  • HTTP-запросы используют switchMap для отмены устаревших запросов
  • Подписки управляются через takeUntilDestroyed или async pipe
  • Lazy loading включён для всех feature-маршрутов
  • 11. Директивы, пайпы, standalone components и современные API Angular

    Директивы, пайпы, standalone components и современные API Angular

    Три инструмента, которые отличают Angular от других фреймворков: директивы позволяют расширять HTML произвольным поведением, пайпы трансформируют данные прямо в шаблоне, а standalone components упрощают архитектуру до уровня, понятного React-разработчику. Вместе они формируют уникальный инструментарий, которого нет ни в одной другой экосистеме.

    Директивы: три вида и их назначение

    В Angular есть три типа директив. Компоненты — это директивы с шаблоном (мы уже разбирали их). Структурные директивы изменяют структуру DOM. Атрибутные директивы изменяют поведение или внешний вид существующих элементов.

    Атрибутные директивы

    Атрибутная директива — это класс с декоратором @Directive, который получает доступ к элементу через ElementRef и Renderer2:

    Почему Renderer2, а не прямой доступ к element.style? Renderer2 абстрагирует работу с DOM, что позволяет Angular работать в средах без браузера (SSR, Web Workers). Прямая мутация DOM через nativeElement — антипаттерн в Angular.

    Директива с хост-привязками

    Структурные директивы

    Структурные директивы изменяют DOM — добавляют или удаляют элементы. Они используют * в синтаксисе (который является синтаксическим сахаром над ng-template):

    Пайпы: трансформация данных в шаблоне

    Пайп — это класс с декоратором @Pipe и методом transform. Pure pipe (по умолчанию) вызывается только при изменении входных данных. Impure pipe вызывается при каждом цикле Change Detection — используйте осторожно.

    Встроенные пайпы Angular, которые нужно знать:

    | Пайп | Пример | Результат | |------|--------|-----------| | date | {{ date \| date:'dd.MM.yyyy' }} | 15.04.2025 | | currency | {{ 1234.5 \| currency:'RUB':'symbol' }} | ₽1 234,50 | | decimal | {{ 3.14159 \| number:'1.2-2' }} | 3.14 | | percent | {{ 0.75 \| percent }} | 75% | | uppercase | {{ 'hello' \| uppercase }} | HELLO | | slice | {{ [1,2,3,4,5] \| slice:1:3 }} | [2, 3] | | async | {{ data{this.userId()});

    // Output через output() selected = output<User>();

    // Two-way binding через model() value = model(''); // Аналог [(value)]

    // ViewChild как сигнал inputRef = viewChild<ElementRef>('inputEl');

    // ContentChild как сигнал icon = contentChild(IconComponent);

    handleClick() { this.selected.emit(this.currentUser); // Доступ к ViewChild без проверки на undefined this.inputRef()?.nativeElement.focus(); } } typescript // Умный компонент — знает о сервисах, управляет данными @Component({ selector: 'app-user-list-page', standalone: true, imports: [UserCardComponent, AsyncPipe], template: @for (user of userService.users(); track user.id) { <app-user-card [user]="user" (delete)="onDelete($event)" /> } , changeDetection: ChangeDetectionStrategy.OnPush }) export class UserListPageComponent { protected userService = inject(UserService);

    onDelete(userId: string) { this.userService.deleteUser(userId); } }

    // Тупой компонент — только отображение, никаких сервисов @Component({ selector: 'app-user-card', standalone: true, imports: [DatePipe], template: <div class="card"> <h3>{{ user.name }}</h3> <p>{{ user.email }}</p> <time>{{ user.createdAt | date }}</time> <button (click)="delete.emit(user.id)">Удалить</button> </div> , changeDetection: ChangeDetectionStrategy.OnPush }) export class UserCardComponent { user = input.required<User>(); delete = output<string>(); } ``

    Тупые компоненты с OnPush` и Signal inputs — это максимально оптимизированные компоненты. Angular обновляет их только при изменении входных данных, и они полностью переиспользуемы.

    12. HTTP Client, Interceptors, работа с API и обработка ошибок в Angular

    HTTP Client, Interceptors, работа с API и обработка ошибок в Angular

    В React вы выбираете между fetch, axios и TanStack Query. В Angular выбора нет — есть HttpClient, и это хорошая новость. Встроенный HTTP-клиент Angular глубоко интегрирован с RxJS, поддерживает типизацию из коробки, имеет мощную систему перехватчиков и тестируется без моков. Разберём его от базового использования до продвинутых паттернов.

    Настройка и базовые запросы

    Обратите внимание: HttpClient возвращает Observable, а не Promise. Запрос не выполняется до тех пор, пока кто-то не подпишется на Observable. Это ленивое выполнение — важное отличие от fetch.

    Interceptors: перехватчики запросов

    Interceptors — это middleware для HTTP-запросов. Они перехватывают каждый запрос или ответ и могут их модифицировать. Аналог axios.interceptors или middleware в Redux.

    Retry и обработка сетевых ошибок

    Тестирование HTTP-запросов

    Angular предоставляет HttpClientTestingModule для тестирования без реальных запросов:

    Это принципиальное преимущество Angular перед React: тестирование HTTP встроено в фреймворк. Не нужны MSW или jest.mockHttpTestingController перехватывает запросы на уровне Angular и позволяет проверить URL, метод, заголовки и тело запроса.

    !Поток HTTP-запроса через interceptors в Angular: от компонента до сервера и обратно

    13. Модульная архитектура и лучшие практики построения enterprise-приложений на Angular

    Модульная архитектура и лучшие практики построения enterprise-приложений на Angular

    Когда приложение вырастает до 50+ компонентов и 20+ разработчиков, вопрос «как организовать код» становится важнее вопроса «как написать код». Плохая архитектура в Angular не сломает приложение сразу — она убьёт его медленно: через конфликты при мёрже, через компоненты с тысячами строк, через сервисы, которые знают обо всём. Разберём архитектурные паттерны, которые масштабируются.

    Feature-based структура: основа enterprise-архитектуры

    Первое решение, которое определяет всё остальное — как организовать папки. Есть два подхода: по типу файлов (все компоненты в одной папке, все сервисы в другой) и по функциональности (всё, что относится к пользователям — в одной папке).

    Правило: если удалить папку features/users, всё остальное должно продолжать работать. Это признак правильной инкапсуляции.

    Три слоя: Core, Shared, Features

    Core содержит синглтоны, которые существуют в единственном экземпляре на всё приложение: сервис аутентификации, HTTP-перехватчики, глобальные guards, конфигурация. Core-сервисы используют providedIn: 'root'.

    Shared содержит переиспользуемые «тупые» компоненты, директивы и пайпы без бизнес-логики. Компонент кнопки, модального окна, спиннера — это Shared. Shared не зависит от Features и не зависит от Core (кроме базовых утилит).

    Features содержит всю бизнес-логику, разбитую по доменам. Каждая feature — это изолированный вертикальный срез: компоненты, сервисы, стор, маршруты. Features могут зависеть от Core и Shared, но не должны зависеть друг от друга.

    Barrel exports: управление публичным API

    Каждая feature должна экспортировать только то, что нужно снаружи. Остальное — детали реализации:

    Настройка path aliases в tsconfig.json:

    Паттерн Facade: упрощение интерфейса к сложной подсистеме

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

    Nx Monorepo: масштабирование до нескольких приложений

    Для крупных enterprise-проектов с несколькими приложениями (основное приложение, admin-панель, мобильное приложение) используют Nx — инструмент для монорепозиториев:

    Nx предоставляет dependency graph — визуализацию зависимостей между библиотеками, и affected commands — запуск тестов только для изменённых библиотек:

    Как отмечают в AngularUX, Nx monorepos с типизированными event-схемами и CI-гейтами снижают общую стоимость владения для долгоживущих enterprise-приложений.

    Правила архитектуры: что запрещено

    Хорошая архитектура — это не только то, что разрешено, но и то, что запрещено. Nx позволяет настроить boundary rules:

    Теперь если разработчик попытается импортировать из другой feature напрямую, ESLint выдаст ошибку. Архитектурные правила автоматически соблюдаются.

    Паттерн: умные маршруты с Resolve

    В enterprise-приложениях страницы часто требуют нескольких источников данных. Паттерн «умного маршрута» загружает все данные до рендера:

    Чеклист enterprise-архитектуры

  • Структура проекта: Core / Shared / Features
  • Каждая feature имеет публичный API через index.ts
  • Path aliases настроены в tsconfig.json
  • Все компоненты используют ChangeDetectionStrategy.OnPush
  • Все компоненты standalone
  • Lazy loading для всех feature-маршрутов
  • Facade-сервисы для сложных взаимодействий
  • ESLint с правилами архитектурных границ
  • Nx для монорепозитория (при нескольких приложениях)
  • CI/CD с проверкой бюджетов бандла
  • !Трёхслойная архитектура Angular enterprise-приложения: Core, Shared и Features

    14. Интеграция внешних библиотек, микрофронтенды и масштабирование Angular-проектов

    Интеграция внешних библиотек, микрофронтенды и масштабирование Angular-проектов

    Реальный Angular-проект никогда не существует в вакууме. Он интегрируется с UI-библиотеками, аналитикой, картами, редакторами, платёжными системами. Он может быть частью микрофронтенд-архитектуры, где несколько команд разрабатывают независимые части одного приложения. Разберём, как Angular справляется с этими задачами — и где возникают нетривиальные проблемы.

    Интеграция UI-библиотек

    Три наиболее популярных UI-библиотеки для Angular:

    Angular Material — официальная библиотека от Google, реализует Material Design. Полная интеграция с Angular DI, темизация через CSS-переменные, доступность из коробки.

    PrimeNG — богатая библиотека компонентов для enterprise. Таблицы с виртуализацией, графики, сложные формы. Популярна в корпоративных проектах.

    Ng-Zorro — Angular-реализация Ant Design. Популярна в проектах с китайскими корнями и в enterprise.

    Интеграция JavaScript-библиотек без Angular-обёртки

    Иногда нужно интегрировать библиотеку, у которой нет Angular-обёртки: Chart.js, Leaflet, Monaco Editor, Quill. Паттерн всегда одинаков: инициализация в ngAfterViewInit, очистка в ngOnDestroy, работа с DOM через ElementRef.

    Ключевые правила интеграции:

  • Никогда не инициализируйте DOM-зависимые библиотеки в ngOnInit — DOM ещё не готов
  • Всегда уничтожайте экземпляры библиотек в ngOnDestroy — иначе утечки памяти
  • Используйте NgZone.runOutsideAngular() для библиотек с частыми обновлениями (анимации, карты)
  • Angular Elements: компоненты как Web Components

    Angular Elements позволяет упаковать Angular-компонент как стандартный Web Component (Custom Element). Это позволяет использовать Angular-компоненты в React, Vue или обычном HTML:

    Микрофронтенды: Module Federation

    Микрофронтенды — архитектурный паттерн, при котором большое приложение разбивается на независимо разрабатываемые и деплоируемые части. Webpack Module Federation — основной инструмент для этого в Angular.

    Микрофронтенды решают организационную проблему: разные команды могут деплоить свои части независимо. Но они добавляют техническую сложность: нужно синхронизировать версии Angular, управлять shared-зависимостями и обеспечивать консистентный UX.

    Nx для масштабирования: практические команды

    Производительность при масштабировании: бюджеты и мониторинг

    Анализ бандла для поиска тяжёлых зависимостей:

    SSR и Hydration: Angular Universal

    Server-Side Rendering (SSR) критически важен для SEO и производительности первой загрузки. Angular поддерживает SSR через Angular Universal:

    Angular 17+ добавил Incremental Hydration — возможность гидрировать компоненты по мере их появления во viewport, а не все сразу. Это значительно улучшает Time to Interactive:

    Типичные проблемы масштабирования и их решения

    Проблема: Circular dependencies

    Решение: переместить общие типы в shared/models, использовать интерфейсы вместо конкретных классов для разрыва циклов.

    Проблема: Медленная сборка

    Проблема: Конфликты версий в монорепозитории

    Используйте peerDependencies в библиотеках и resolutions в package.json для принудительного использования одной версии.

    Масштабирование Angular-приложений — это прежде всего организационная задача. Технические инструменты (Nx, Module Federation, SSR) решают конкретные проблемы, но без чётких архитектурных правил и командных соглашений они не помогут.

    !Архитектура микрофронтендов на Angular: Shell и Remote приложения через Module Federation

    15. Финальный проект: разработка полноценного enterprise-приложения на Angular с нуля

    Финальный проект: разработка полноценного enterprise-приложения на Angular с нуля

    Теория без практики — это просто красивые слова. В этой статье мы построим полноценное enterprise-приложение TaskFlow — систему управления задачами с аутентификацией, ролевым доступом, реактивным состоянием, HTTP-интеграцией и оптимизированной архитектурой. Каждое решение будет обосновано, каждый паттерн — из реальной практики.

    Что мы строим: требования TaskFlow

    TaskFlow — корпоративная система управления задачами:

  • Аутентификация (JWT, refresh tokens)
  • Роли: Admin, Manager, Employee
  • Проекты с задачами, статусами и исполнителями
  • Реалтайм-обновления через WebSocket
  • Дашборд с аналитикой
  • Полная типизация, OnPush везде, lazy loading
  • Шаг 1: Инициализация проекта

    Шаг 2: Структура проекта

    Шаг 3: Модели данных

    Шаг 4: Сервис аутентификации

    Шаг 5: Interceptors

    Шаг 6: SignalStore для задач

    Шаг 7: Kanban-доска

    Итоговая архитектура: что мы применили

    | Концепция | Реализация в TaskFlow | |-----------|----------------------| | Архитектура | Core / Shared / Features | | Состояние | NgRx SignalStore | | HTTP | HttpClient + Interceptors (auth, error) | | Маршрутизация | Lazy loading + Guards + Resolve | | Формы | Reactive Forms с типизацией | | Change Detection | OnPush везде | | Типизация | Strict TypeScript + strictTemplates | | Тестирование | Jasmine + TestBed + HttpTestingController | | CI/CD | GitHub Actions с бюджетами бандла |

    TaskFlow — это не учебный пример, а шаблон реального enterprise-приложения. Каждое решение здесь продиктовано практическими требованиями: масштабируемостью, тестируемостью и производительностью. Именно с такими знаниями и таким кодом вы готовы к собеседованию на позицию Senior Angular Developer.

    2. Сравнение архитектуры React и Angular: компоненты, DI, шаблоны и философия фреймворка

    Сравнение архитектуры React и Angular: компоненты, DI, шаблоны и философия фреймворка

    Вы открываете Angular-проект после нескольких лет работы с React и чувствуете лёгкое головокружение. Файлов в три раза больше, декораторы везде, какой-то NgModule, инжекторы, провайдеры. Первый импульс — «зачем всё это?». Но за каждым из этих элементов стоит конкретное архитектурное решение, которое решает реальные проблемы масштабирования. Разберём их по одному, сравнивая с тем, что вы уже знаете.

    Библиотека против платформы: фундаментальная разница

    React — это библиотека для построения UI. Она отвечает за один вопрос: как отрисовать данные в DOM и обновить их при изменении. Всё остальное — ваш выбор. Роутинг? React Router или TanStack Router. Состояние? Redux, Zustand, Jotai, Recoil. HTTP? Fetch, Axios, TanStack Query. Формы? React Hook Form, Formik.

    Angular — это платформа. Она отвечает на все эти вопросы сразу и встроенными средствами. Это не ограничение — это архитектурное решение, которое устраняет decision fatigue (усталость от выбора) в больших командах.

    Когда в команде 30 разработчиков, унификация стека — это не удобство, а необходимость.

    Компоненты: функция против класса с декоратором

    В React компонент — это функция, которая принимает props и возвращает JSX:

    В Angular компонент — это класс с декоратором @Component:

    Разница не только синтаксическая. В Angular компонент — это класс с состоянием и методами, а не замыкание с хуками. Это означает, что логика компонента организована объектно-ориентированно: методы, свойства, наследование (хотя наследование компонентов в Angular используется редко).

    !Сравнение архитектуры React и Angular: компонент, шаблон, DI и поток данных

    Шаблоны: JSX против HTML с директивами

    JSX — это JavaScript с синтаксическим сахаром. Вы пишете «HTML» прямо в JS-файле, и это даёт полную мощь языка: любые выражения, деструктуризация, map/filter, тернарные операторы.

    Angular-шаблоны — это расширенный HTML. Они компилируются отдельно от TypeScript-кода и имеют собственный синтаксис. Начиная с Angular 17, появился новый синтаксис управляющих конструкций:

    До Angular 17 использовались структурные директивы ngIf, ngFor, *ngSwitch. Вы встретите их в legacy-коде, поэтому важно знать оба синтаксиса.

    | Концепция | React (JSX) | Angular (Template) | |-----------|-------------|-------------------| | Условный рендер | {condition && <Comp />} | @if (condition) { <comp /> } | | Список | items.map(i => <Item key={i.id} />) | @for (i of items; track i.id) { <item /> } | | Привязка события | onClick={handler} | (click)="handler()" | | Привязка свойства | className={cls} | [class]="cls" | | Двусторонняя привязка | Нет встроенной | [(ngModel)]="value" | | Передача данных вниз | <Comp prop={value} /> | <comp [prop]="value" /> | | Передача событий вверх | <Comp onChange={fn} /> | <comp (change)="fn(event" (событие вверх). Под капотом поток данных остаётся однонаправленным — просто Angular автоматизирует обновление в обе стороны.

    Важно: двустороннее связывание удобно для форм, но его не стоит использовать для передачи данных между несвязанными компонентами — это ведёт к запутанному коду.

    Жизненный цикл компонента

    React и Angular имеют схожие концепции жизненного цикла, но реализованные по-разному:

    | React Hook | Angular Hook | Когда вызывается | |------------|--------------|-----------------| | useEffect(fn, []) | ngOnInit | После первой инициализации | | useEffect(fn, [dep]) | ngOnChanges | При изменении @Input | | useEffect(() => cleanup) | ngOnDestroy | При уничтожении компонента | | useLayoutEffect | ngAfterViewInit | После рендера DOM | | — | ngAfterContentInit | После проекции контента |

    Модульность: NgModules vs Standalone

    Исторически Angular организовывал код через NgModules — декларативные блоки, которые группировали компоненты, директивы и пайпы. Это мощная система, но с высоким порогом входа.

    Начиная с Angular 14, появились Standalone Components — компоненты, которые не требуют NgModule и сами декларируют свои зависимости:

    Для React-разработчика standalone-подход значительно привычнее: каждый компонент явно импортирует то, что использует — как ES-модули в React.

    Почему Angular «многословнее» React

    Часто слышат: «Angular требует слишком много boilerplate». Это правда, но за каждой строкой стоит причина:

  • @Injectable({ providedIn: 'root' }) — явно объявляет, что класс является сервисом и доступен глобально. Нет магии, нет неявных зависимостей.
  • @Input() и @Output() — явный контракт компонента. Любой разработчик видит публичный API компонента без чтения шаблона.
  • implements OnInit — явная декларация используемых хуков жизненного цикла. TypeScript проверит, что метод ngOnInit реализован правильно.
  • Как отмечают в Selectel, Angular знает конкретную ноду, которую нужно изменить, и обновляет только её — без перерисовки всего компонента. Эта точность достигается именно благодаря явной архитектуре.

    Многословность Angular — это цена за предсказуемость. В React вы можете написать компонент в 10 строк, но через полгода никто не поймёт, откуда берутся данные. В Angular архитектура самодокументируется.

    3. TypeScript в Angular: декораторы, дженерики, утилитарные типы и строгий режим

    TypeScript в Angular: декораторы, дженерики, утилитарные типы и строгий режим

    Если в React TypeScript — это опция, которую можно включить или проигнорировать, то в Angular он встроен в саму ДНК фреймворка. Декораторы, которые делают Angular Angular-ом (@Component, @Injectable, @Input), — это TypeScript-фича. Без понимания системы типов вы будете писать Angular-код вслепую, полагаясь на any там, где компилятор мог бы поймать ошибку ещё до запуска браузера.

    Строгий режим: почему strict: true — это не опция

    При создании Angular-проекта CLI предлагает включить строгий режим. Всегда соглашайтесь. Строгий режим включает несколько флагов TypeScript одновременно:

    Флаг strictTemplates — уникальная Angular-фича. Он включает проверку типов прямо в HTML-шаблонах. Если вы передаёте строку туда, где ожидается число, компилятор сообщит об ошибке при сборке, а не в рантайме:

    Это принципиально отличает Angular от React, где ошибки типов в JSX часто проявляются только в браузере.

    Декораторы: метапрограммирование в Angular

    Декораторы — это функции, которые добавляют метаданные к классам, методам и свойствам. В TypeScript они обозначаются символом @. Angular использует их повсеместно.

    Декоратор @Component — самый важный. Он превращает обычный TypeScript-класс в Angular-компонент:

    Обратите внимание на ! после userId и nameInputRef. Это non-null assertion operator — вы говорите TypeScript: «Я знаю, что это значение будет инициализировано, доверяй мне». Используйте его осторожно — только когда уверены, что Angular гарантирует инициализацию (как в случае с @Input({ required: true })).

    Дженерики в Angular: типизированные сервисы и компоненты

    Дженерики позволяют создавать переиспользуемые компоненты и сервисы, которые работают с разными типами данных, сохраняя строгую типизацию. В Angular это особенно полезно для сервисов работы с данными:

    Дженерики в Angular также используются в Signal<T>, Observable<T>, EventEmitter<T> и FormControl<T>. Понимание дженериков — это не академическая тонкость, а ежедневный инструмент.

    Утилитарные типы TypeScript в Angular-контексте

    TypeScript предоставляет набор встроенных утилитарных типов, которые особенно часто применяются в Angular-разработке:

    Реальный пример применения в Angular-компоненте:

    Типизация форм: Typed Forms в Angular 14+

    До Angular 14 формы были практически нетипизированными — FormControl возвращал any. Начиная с Angular 14, формы полностью типизированы:

    Это огромный шаг вперёд по сравнению с React Hook Form, где типизация требует дополнительных дженериков и иногда ведёт к сложным конструкциям.

    Интерфейсы vs классы для моделей данных

    В Angular-проектах часто встречается вопрос: использовать interface или class для описания моделей данных? Практическое правило:

    Интерфейсы существуют только на этапе компиляции и полностью исчезают из JavaScript-бандла. Классы остаются в рантайме и могут использоваться как токены для DI.

    Типизация RxJS-потоков

    RxJS — неотъемлемая часть Angular. Правильная типизация Observable-потоков предотвращает целый класс ошибок:

    Строгая типизация в Angular — это не бюрократия, а страховочная сетка. Когда проект вырастает до 100+ компонентов и 50+ сервисов, именно TypeScript не даёт изменениям в одном месте незаметно сломать другое.

    4. Компоненты, шаблоны и привязка данных: от JSX к Angular-синтаксису

    Компоненты, шаблоны и привязка данных: от JSX к Angular-синтаксису

    Первое, с чем сталкивается React-разработчик в Angular — это шаблоны. Вместо JSX, где вся мощь JavaScript доступна прямо в разметке, Angular использует расширенный HTML с собственным синтаксисом привязки данных. Поначалу это кажется ограничением, но на практике оказывается, что Angular-шаблоны дают компилятору достаточно информации для статической проверки типов и оптимизации рендеринга — то, что JSX обеспечить не может.

    Анатомия Angular-компонента

    Каждый компонент в Angular состоит из трёх частей: класс (логика), шаблон (представление) и стили. Они могут быть в одном файле или разделены:

    Для сложных компонентов шаблон и стили выносятся в отдельные файлы (templateUrl, styleUrl). Angular CLI генерирует такую структуру по умолчанию.

    Четыре типа привязки данных

    Angular строит всю систему шаблонов вокруг четырёх типов привязки. Понять их — значит понять 80% синтаксиса шаблонов.

    1. Интерполяция {{ expression }} — вывод значения в текст:

    Внутри {{ }} можно использовать любые выражения TypeScript, но не операторы (if, for, присваивание). Это намеренное ограничение — шаблон должен только отображать данные, не управлять ими.

    2. Привязка свойства [property]="expression" — передача данных в DOM или компонент:

    Разница между [property] и attr.property: привязка к свойству работает с DOM-объектом (быстро), привязка к атрибуту работает с HTML-атрибутом (нужна для SVG, ARIA и нестандартных атрибутов).

    3. Привязка события (event)="handler(event)" (keyup.enter)="onSubmit()" /> <form (ngSubmit)="onFormSubmit()">...</form>

    <!-- Доступ к объекту события --> <div (mousemove)="onMouseMove(event" /> html <!-- Условный рендер --> @if (user.isAdmin) { <admin-dashboard /> } @else if (user.isModerator) { <moderator-panel /> } @else { <user-home /> }

    <!-- Цикл с отслеживанием и пустым состоянием --> @for (product of products; track product.id) { <app-product-card [product]="product" /> } @empty { <p class="empty-state">Товары не найдены</p> }

    <!-- Switch --> @switch (order.status) { @case ('pending') { <span class="badge yellow">Ожидает</span> } @case ('shipped') { <span class="badge blue">Отправлен</span> } @case ('delivered') { <span class="badge green">Доставлен</span> } @default { <span class="badge gray">Неизвестно</span> } }

    Переписываем React-компонент в Angular

    Практический пример: переносим типичный React-компонент поиска с фильтрацией:

    Ключевые наблюдения при переносе:

  • useState → свойство класса (или signal() для реактивности)
  • useMemocomputed()
  • onChange[(ngModel)] или (input)="handler()"
  • keytrack`
  • Форматирование в JSX → пайпы в шаблоне
  • 5. Dependency Injection и сервисы: инверсия зависимостей вместо prop drilling и Context

    Dependency Injection и сервисы: инверсия зависимостей вместо prop drilling и Context

    Представьте компонент глубоко в дереве, которому нужен доступ к данным пользователя. В React вы либо пробрасываете props через пять промежуточных компонентов, либо создаёте Context и оборачиваете дерево провайдером. Оба подхода работают, но оба имеют цену: prop drilling засоряет интерфейсы промежуточных компонентов, а Context не даёт контроля над областью видимости и временем жизни данных. Angular решает эту проблему через встроенную систему Dependency Injection — и решает её элегантнее.

    Что такое DI и зачем он нужен

    Dependency Injection (инверсия зависимостей) — это паттерн, при котором объект не создаёт свои зависимости сам, а получает их извне. Вместо new UserService() внутри компонента Angular сам создаёт нужный экземпляр и передаёт его.

    Почему это важно? Представьте, что UserService зависит от HttpClient, который зависит от HttpBackend, который зависит от конфигурации. Без DI вам пришлось бы вручную создавать всю цепочку зависимостей. С DI вы просто объявляете: «мне нужен UserService» — и Angular разбирается с остальным.

    Для тестирования это бесценно: вместо реального UserService можно подставить мок, не меняя код компонента.

    Иерархия инжекторов: главное отличие от Context

    В React Context — это глобальный провайдер. Вы либо предоставляете значение для всего дерева, либо нет. Angular устроен иначе: инжекторы образуют иерархию, и каждый уровень может переопределять зависимости для своего поддерева.

    Когда компонент запрашивает зависимость, Angular ищет её снизу вверх по иерархии. Первый инжектор, который может предоставить зависимость, выигрывает.

    Практический пример: компонент модального окна может иметь свой изолированный FormService, который уничтожается вместе с модалкой. Другие компоненты не видят этот сервис и не могут случайно его изменить.

    Создание и использование сервисов

    Сервис в Angular — это обычный TypeScript-класс с декоратором @Injectable:

    Использование в компоненте через функцию inject() — современный способ, рекомендованный в Angular 14+:

    inject() vs конструктор: два способа получить зависимость

    Angular поддерживает два синтаксиса для получения зависимостей:

    inject() предпочтительнее по нескольким причинам: код чище, зависимости можно использовать при инициализации свойств класса, и функцию можно вызывать в вспомогательных функциях (не только в конструкторе).

    Важное ограничение: inject() работает только в injection context — во время конструирования класса или в функциях, вызываемых из конструктора. Нельзя вызвать inject() в методе ngOnInit или в обработчике события.

    Токены и интерфейсы: продвинутый DI

    Иногда нужно предоставить не класс, а примитивное значение или объект, реализующий интерфейс. Для этого используются InjectionToken:

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

    Лучшая практика Angular — держать состояние в сервисах, а компоненты делать «тупыми» (presentational). Это прямой аналог паттерна из Redux, но без boilerplate:

    Любой компонент в приложении может инжектировать CartService и получить актуальное состояние корзины — без prop drilling, без Context, без глобального стора.

    Сравнение с React-подходами

    | Задача | React | Angular | |--------|-------|---------| | Глобальное состояние | Context + useReducer / Zustand | Сервис с providedIn: 'root' | | Локальное состояние компонента | useState / useReducer | Сигналы в компоненте | | Изолированное состояние поддерева | Context с ограниченным провайдером | Сервис в providers компонента | | Зависимость от конфигурации | Переменные окружения / Context | InjectionToken | | Мокирование в тестах | Jest mock / MSW | TestBed.overrideProvider() |

    Типичные ошибки при работе с DI

    Ошибка 1: Создание сервиса через new

    Ошибка 2: Хранение состояния в компоненте вместо сервиса

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

    Ошибка 3: Подписка на Observable в сервисе без отписки

    DI в Angular — это не просто способ передать зависимости. Это архитектурный инструмент, который определяет, как организован весь код приложения. Правильное использование DI делает код тестируемым, переиспользуемым и понятным — независимо от размера команды и проекта.

    !Иерархия инжекторов Angular: от корневого до компонентного уровня

    6. RxJS и реактивное программирование: Observable, операторы и паттерны вместо Promise

    RxJS и реактивное программирование: Observable, операторы и паттерны вместо Promise

    Если Dependency Injection — это архитектурный хребет Angular, то RxJS — его нервная система. Каждый HTTP-запрос, каждое событие формы, каждое изменение маршрута в Angular возвращает Observable. Игнорировать RxJS в Angular — всё равно что игнорировать Promise в JavaScript. Но для React-разработчика, привыкшего к async/await и useEffect, реактивное программирование поначалу кажется избыточным усложнением. Разберём, почему это не так.

    Observable vs Promise: принципиальная разница

    Promise — это контейнер для одного будущего значения. Он либо выполняется, либо отклоняется. После завершения он неизменен.

    Observable — это поток нуля или более значений во времени. Он может:

  • Вернуть одно значение (как Promise)
  • Вернуть много значений (WebSocket, события мыши)
  • Не вернуть ничего (пустой поток)
  • Быть отменён (в отличие от Promise!)
  • Быть синхронным или асинхронным
  • typescript import { of, interval } from 'rxjs'; import { map, filter, take, debounceTime, switchMap, catchError } from 'rxjs/operators';

    // map — трансформация каждого значения (аналог Array.map) const doubled = of(1, 2, 3, 4, 5).pipe( filter(n => n % 2 === 0) ); // Эмитирует: 2, 4

    // take — взять первые N значений и завершить поток const firstThree = searchInput = searchQuery{query})) );

    // mergeMap — выполняет все запросы параллельно // Для независимых операций, где порядок не важен const allData.pipe( mergeMap(id => this.http.get<User>(/api/users/ = fileQueue | async; track result.id) { <div class="result" (click)="select(result)"> {{ result.name }} </div> } }) export class SearchComponent { private http = inject(HttpClient);

    searchControl = new FormControl(''); loading = signal(false);

    results{query}).pipe( catchError(() => of([])) // При ошибке — пустой массив ) ), tap(() => this.loading.set(false)) );

    select(result: SearchResult) { console.log('Выбрано:', result); } }

    takeUntilDestroyed — предпочтительный современный подход. Он работает как с компонентами, так и с сервисами, и не требует реализации OnDestroy.

    Subject: мост между императивным и реактивным кодом

    Subject — это Observable, в который можно вручную отправлять значения. Это мост между обычным JavaScript-кодом и реактивными потоками:

    RxJS vs async/await: когда что использовать

    Не всё должно быть Observable. Вот практическое правило:

    | Сценарий | Рекомендация | |----------|-------------| | Одиночный HTTP-запрос без отмены | async/await или Observable — оба OK | | Поиск с debounce и отменой | Observable + switchMap | | WebSocket / Server-Sent Events | Observable | | Комбинирование нескольких потоков | Observable + combineLatest | | Простая загрузка данных в ngOnInit | async/await через firstValueFrom() | | Реактивные формы | Observable (встроено в Angular) |

    RxJS — это инвестиция. Первые две недели он кажется сложным. Потом вы начинаете видеть, как декларативные потоки делают код чище и предсказуемее, чем цепочки useEffect с флагами isMounted и AbortController.

    7. Формы в Angular: Template-driven и Reactive Forms против React Hook Form

    Формы в Angular: Template-driven и Reactive Forms против React Hook Form

    Формы — это то место, где Angular действительно блистает по сравнению с React. Пока в React-экосистеме идут споры между Formik, React Hook Form и нативными решениями, Angular предлагает два встроенных подхода с чёткими сценариями применения. Понять разницу между ними — значит сделать правильный архитектурный выбор с первого раза.

    Два подхода: когда какой выбирать

    Template-driven Forms — логика формы живёт в шаблоне. Подход декларативный, похож на Vue.js или AngularJS. Подходит для простых форм: логин, подписка на рассылку, простой поиск.

    Reactive Forms — логика формы живёт в TypeScript-классе. Подход императивный, полностью типизированный, тестируемый. Подходит для сложных форм: многошаговые визарды, динамические поля, сложная валидация.

    В React Hook Form вся логика тоже живёт в JavaScript, но без встроенной типизации состояния формы и без иерархии форм. Angular Reactive Forms предоставляют это из коробки.

    Template-driven Forms: быстрый старт

    Template-driven Forms используют директивы (required, email, minlength) прямо в HTML — это те же HTML5-атрибуты валидации, но Angular перехватывает их и добавляет реактивное поведение.

    Reactive Forms: полный контроль

    Reactive Forms — это то, что вы будете использовать в 90% реальных проектов. Они дают полный контроль над состоянием формы, валидацией и динамическими полями:

    Динамические формы: FormArray

    Одна из сильнейших возможностей Reactive Forms — FormArray для динамических списков полей. В React Hook Form это требует useFieldArray и дополнительной настройки. В Angular это встроено:

    Реактивное отслеживание изменений формы

    Reactive Forms предоставляют Observable-потоки для отслеживания изменений — это то, чего нет в React Hook Form без дополнительных хуков:

    Кастомные валидаторы и асинхронная валидация

    Сравнение с React Hook Form

    | Возможность | Angular Reactive Forms | React Hook Form | |-------------|----------------------|-----------------| | Типизация | Полная (Angular 14+) | Через дженерики | | Динамические поля | FormArray встроен | useFieldArray | | Асинхронная валидация | Встроена | Встроена | | Отслеживание изменений | valueChanges Observable | watch() | | Вложенные формы | FormGroup в FormGroup | Вложенные объекты | | Производительность | Хорошая с OnPush | Отличная (uncontrolled) | | Зависимости | Нет (встроено) | Внешняя библиотека | | Интеграция с RxJS | Нативная | Через адаптеры |

    Ключевое преимущество Angular Reactive Forms — нативная интеграция с RxJS. valueChanges и statusChanges — это настоящие Observable, которые можно комбинировать с любыми другими потоками. В React Hook Form watch() возвращает значение, а не поток — для реактивного поведения нужны дополнительные усилия.

    Типичные ошибки при работе с формами

    Ошибка 1: Доступ к значению формы без проверки валидности

    Ошибка 2: Мутация значения FormControl напрямую

    Ошибка 3: Забыть markAllAsTouched() при сабмите

    Ошибки валидации показываются только для touched полей. Если пользователь нажал «Отправить», не трогая поля, ошибки не появятся. Вызов this.form.markAllAsTouched() перед проверкой валидности решает это.

    !Архитектура Reactive Forms: FormGroup, FormControl, FormArray и их взаимосвязи

    8. Маршрутизация, lazy loading и route guards: Angular Router vs React Router

    Маршрутизация, lazy loading и route guards: Angular Router vs React Router

    Маршрутизация в Angular — это не просто «какой компонент показать по какому URL». Angular Router — это полноценная подсистема с иерархическими маршрутами, ленивой загрузкой модулей, защитой маршрутов, резолверами данных и стратегиями предзагрузки. Для React-разработчика, привыкшего к React Router v6, здесь много знакомых концепций — но реализованных значительно мощнее.

    Базовая конфигурация маршрутов

    В современном Angular (17+) маршруты конфигурируются в файле app.routes.ts:

    Сравнение с React Router v6:

    Структура похожа, но Angular Router добавляет title, data, resolve, canActivate и другие поля прямо в конфигурацию маршрута.

    Навигация в шаблонах и коде

    Получение параметров маршрута

    Важное отличие от React Router: Angular Router по умолчанию переиспользует компонент при изменении параметров маршрута (например, переход с /users/1 на /users/2). Поэтому нужно использовать Observable-подход, а не читать параметры только в ngOnInit.

    Lazy Loading: загрузка по требованию

    Lazy loading — загрузка кода маршрута только тогда, когда пользователь переходит на него. Это критически важно для производительности больших приложений. В Angular это реализуется через динамический импорт:

    Angular CLI автоматически создаёт отдельные чанки для lazy-loaded маршрутов. В angular.json можно настроить бюджеты размера бандла:

    Route Guards: защита маршрутов

    Guards — это функции или классы, которые решают, можно ли перейти на маршрут. Аналог в React Router — loader с редиректом или компонент-обёртка <PrivateRoute>.

    Angular поддерживает несколько типов guards:

    | Guard | Назначение | Аналог в React | |-------|-----------|----------------| | canActivate | Разрешить/запретить вход на маршрут | <PrivateRoute> | | canActivateChild | Защита дочерних маршрутов | — | | canDeactivate | Предотвратить уход со страницы | useBlocker | | canMatch | Выбор маршрута по условию | — | | resolve | Загрузка данных перед рендером | loader в React Router v6 |

    Resolve: данные до рендера компонента

    Resolve guard загружает данные до того, как компонент отрендерится. Это устраняет мерцание загрузчика:

    Стратегии предзагрузки

    После первоначальной загрузки Angular может предзагружать lazy-loaded маршруты в фоне:

    Для продакшна лучше использовать кастомную стратегию, которая предзагружает только маршруты с флагом data: { preload: true }:

    Вложенные маршруты и router-outlet

    <router-outlet> — это точка монтирования компонента маршрута. Аналог <Outlet /> в React Router v6. Можно иметь несколько именованных outlets для сложных макетов (например, боковая панель и основной контент рендерятся независимо).

    !Архитектура Angular Router: иерархия маршрутов, guards и lazy loading

    9. Управление состоянием: NgRx, Signals, NgXS и сравнение с Redux и Zustand

    Управление состоянием: NgRx, Signals, NgXS и сравнение с Redux и Zustand

    Вопрос управления состоянием в Angular — это не «нужен ли нам стор», а «какой стор выбрать». В React-экосистеме этот выбор давно решён в пользу Zustand или Redux Toolkit для большинства проектов. В Angular картина сложнее: встроенные сервисы с Signals уже покрывают большинство задач, NgRx добавляет предсказуемость для сложных сценариев, а NgXS предлагает компромисс. Разберём каждый подход честно.

    Когда встроенных сервисов достаточно

    Прежде чем тянуться к NgRx, ответьте на вопрос: действительно ли вам нужен Redux-подобный стор? Для большинства приложений ответ — нет. Сервисы с Signals покрывают 80% задач:

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

    NgRx: Redux для Angular

    NgRx — это Angular-реализация паттерна Redux. Если вы работали с Redux Toolkit, концепции знакомы: actions, reducers, selectors, effects. NgRx добавляет строгую типизацию и интеграцию с Angular DI.

    Сравнение подходов

    | Критерий | Сервисы + Signals | NgRx Classic | NgRx SignalStore | NgXS | |----------|------------------|--------------|-----------------|------| | Boilerplate | Минимальный | Высокий | Средний | Средний | | Типизация | Отличная | Отличная | Отличная | Хорошая | | DevTools | Нет | Redux DevTools | Redux DevTools | NgXS DevTools | | Time-travel debugging | Нет | Да | Да | Да | | Тестируемость | Хорошая | Отличная | Отличная | Хорошая | | Порог входа | Низкий | Высокий | Средний | Средний | | Подходит для | Малые/средние | Крупные | Любые | Средние |

    Сравнение с Redux и Zustand

    Если вы пришли из Redux Toolkit:

  • NgRx Classic — прямой аналог. Те же концепции, та же предсказуемость, больше Angular-специфики.
  • NgRx SignalStore — ближе к Zustand: меньше boilerplate, но с сохранением предсказуемости.
  • Если вы пришли из Zustand:

  • Сервисы с Signals — ближайший аналог. Простота Zustand + Angular DI.
  • NgRx SignalStore — Zustand с DevTools и строгой типизацией.
  • Практическая рекомендация: начинайте с сервисов + Signals. Если появляется необходимость в time-travel debugging, сложных эффектах или команда уже знает NgRx — переходите на NgRx SignalStore. Классический NgRx оправдан только в очень крупных командах с устоявшимися процессами.