Разработка корпоративных приложений на Angular

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

1. Проектирование архитектуры: модульность, Standalone Components и монорепозитории

Проектирование архитектуры: модульность, Standalone Components и монорепозитории

Добро пожаловать в курс «Разработка корпоративных приложений на Angular». Мы начинаем наше погружение не с написания кода, а с фундамента, на котором строится любое успешное крупное приложение — с архитектуры. В мире Enterprise-разработки цена ошибки на этапе проектирования возрастает экспоненциально по мере роста проекта. Плохая архитектура приводит к «спагетти-коду», невозможности масштабирования и мучительной поддержке.

В этой статье мы разберем три кита современной Angular-разработки: модульность (как стратегию разделения ответственности), Standalone Components (как новый стандарт компоновки) и монорепозитории (как способ организации кода в больших командах).

Что делает приложение «корпоративным»?

Прежде чем говорить о технологиях, определим контекст. Корпоративное (Enterprise) приложение — это не просто «большой сайт». Это система, которая характеризуется:

  • Долгоживущим кодом. Проект будет развиваться годами.
  • Сложной предметной областью. Сотни сущностей, сложные бизнес-правила.
  • Работой большой команды. Над кодом могут одновременно работать десятки разработчиков, разделенных на подкоманды.
  • Чтобы управлять этой сложностью, нам нужна жесткая, но гибкая структура.

    Модульность и разделение ответственности

    Исторически Angular был строго модульным фреймворком. Хотя появление Standalone Components изменило техническую реализацию, логическая модульность осталась критически важной. Модульность — это способ разбить сложную систему на управляемые, изолированные части.

    Вертикальное и горизонтальное разделение

    В архитектуре часто используют два подхода к нарезке функционала:

    * Горизонтальное разделение (по техническому слою): Все компоненты в одной папке, все сервисы — в другой, все модели — в третьей. Этот подход плохо работает в больших приложениях, так как связные по смыслу части разбросаны по всему проекту. * Вертикальное разделение (по фичам): Мы группируем код вокруг бизнес-функций (например, «Управление пользователями», «Корзина», «Отчеты»). Внутри каждой такой «вертикали» могут быть свои компоненты, сервисы и модели.

    !Сравнение горизонтальной и вертикальной архитектуры.

    Для Enterprise-приложений мы стремимся к гибридному подходу, часто называемому Feature Slicing или Nx-style architecture (даже без использования Nx). Мы делим приложение на:

  • Feature Libraries (Фичи): Умные компоненты, реализующие конкретные сценарии использования (например, booking-feature-search).
  • UI Libraries (UI-кит): Глупые (презентационные) компоненты, переиспользуемые везде (кнопки, инпуты, карточки).
  • Data Access Libraries (Доступ к данным): Сервисы, стейт-менеджмент, API-клиенты.
  • Utility Libraries (Утилиты): Хелперы, валидаторы, чистые функции.
  • Такое разделение позволяет четко определить границы ответственности и зависимости.

    Эра Standalone Components

    До версии Angular 14 основным строительным блоком был NgModule. Чтобы использовать компонент, его нужно было объявить в модуле, экспортировать, а затем импортировать этот модуль в другой модуль. Это создавало много шаблонного кода (boilerplate) и усложняло ментальную модель.

    Standalone Components (Автономные компоненты) — это компоненты, директивы или пайпы, которые не требуют включения в NgModule. Они сами управляют своими зависимостями.

    Преимущества Standalone API

  • Меньше кода: Исчезают файлы *.module.ts, которые часто просто дублировали списки импортов.
  • Улучшенный Tree-shaking: Сборщику проще понять, какой код действительно используется, а какой можно выкинуть из финального бандла. Tree-shaking — это процесс удаления «мертвого» (неиспользуемого) кода при сборке приложения.
  • Простая ленивая загрузка (Lazy Loading): Загрузка отдельного компонента по роуту стала тривиальной.
  • Пример миграции мышления

    Раньше мы писали так:

    Теперь мы пишем так:

    Обратите внимание на флаг standalone: true и массив imports прямо внутри декоратора компонента. Это делает компонент самодостаточной единицей. В корпоративных приложениях это упрощает рефакторинг: если вы хотите перенести компонент в другую библиотеку, вам не нужно распутывать клубок модульных зависимостей.

    > Standalone API не убивает модульность как концепцию, он убивает NgModules как технический долг.

    Монорепозитории: Nx и масштабирование

    Когда у вас одно приложение, обычная структура папок Angular CLI работает нормально. Но в Enterprise часто бывает так:

    * Основное веб-приложение для клиентов. * Административная панель для менеджеров. * Мобильное приложение (например, на Ionic/Capacitor). * Общая библиотека UI-компонентов.

    Если держать это в разных репозиториях, возникает проблема синхронизации версий и дублирования кода. Решение — Монорепозиторий.

    Монорепозиторий — это стратегия организации кода, при которой код нескольких проектов хранится в одном репозитории.

    Инструментарий: Nx

    Стандартом де-факто для Angular-монорепозиториев является инструмент Nx. Он предоставляет мощные возможности для управления зависимостями и сборкой.

    !Граф зависимостей в монорепозитории, показывающий переиспользование кода.

    Ключевые преимущества монорепозитория в Enterprise:

  • Atomic Commits (Атомарные коммиты): Вы можете изменить API в библиотеке и обновить все приложения, которые её используют, в одном коммите. Никаких проблем с версионированием npm-пакетов внутри компании.
  • Единый Toolchain: Все проекты используют одну версию Angular, TypeScript, ESLint и Prettier. Это гарантирует консистентность кода.
  • Affected Build (Инкрементальная сборка): Nx умеет анализировать граф зависимостей. Если вы изменили кнопку в shared-ui, Nx пересоберет и протестирует только те приложения, которые используют эту кнопку, а не весь репозиторий. Это критически важно для CI/CD.
  • Структура папок в Nx

    Типичная структура выглядит так:

    * apps/ — здесь лежат точки входа (контейнеры). Они должны быть максимально тонкими и просто собирать фичи вместе. * libs/ — здесь живет 95% вашего кода. Библиотеки делятся по типам (Feature, UI, Data-access, Utils) и по доменам (Products, Users, Orders).

    Синтез: Как это работает вместе

    Идеальная архитектура современного Enterprise Angular приложения выглядит следующим образом:

  • Workspace: Монорепозиторий на базе Nx.
  • Libraries: Весь код разбит на небольшие библиотеки в папке libs. Каждая библиотека имеет четкий публичный API (файл index.ts), через который она отдает наружу только то, что нужно.
  • Components: Внутри библиотек используются Standalone Components. Это позволяет библиотекам импортировать друг друга напрямую, минуя промежуточные модули.
  • Lazy Loading: Роутер приложения загружает «лениво» не модули, а точки входа в Feature Libraries (которые экспортируют массив роутов или корневой Standalone компонент).
  • Пример конфигурации роутинга с Standalone компонентами:

    Заключение

    Проектирование архитектуры — это инвестиция. Используя монорепозитории, мы обеспечиваем удобство управления множеством проектов. Применяя Standalone Components, мы снижаем сложность кода и улучшаем производительность. А грамотная модульность (через библиотеки) позволяет команде масштабироваться, не наступая друг другу на ноги.

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

    2. Реактивное программирование и управление состоянием с RxJS, Signals и NgRx

    Реактивное программирование и управление состоянием с RxJS, Signals и NgRx

    В предыдущей статье мы заложили фундамент нашего корпоративного приложения, разобрав модульность, Standalone Components и структуру монорепозитория. Мы создали «скелет» системы. Теперь пришло время запустить по этому скелету «кровь» — данные.

    В Enterprise-разработке управление потоками данных — это одна из самых сложных задач. Пользователь нажимает кнопку, отправляется запрос на сервер, параллельно обновляется счетчик уведомлений, а в другом виджете меняется график. Если пытаться управлять этим хаосом вручную, мы получим систему, которую невозможно отлаживать.

    Сегодня мы разберем три инструмента, которые позволяют обуздать эту сложность: RxJS (для событий), Signals (для локального состояния и рендеринга) и NgRx (для глобального состояния).

    RxJS: Управление событиями во времени

    Angular неразрывно связан с библиотекой RxJS (Reactive Extensions for JavaScript). Если вы пришли из мира React или Vue, концепция RxJS может показаться сложной, но для Angular-разработчика это обязательный навык.

    Что такое Stream (Поток)?

    Представьте себе конвейер на заводе. По нему движутся детали. В разных точках конвейера стоят роботы: один красит деталь, второй отбраковывает сломанные, третий упаковывает.

    В программировании:

    * Конвейер — это Observable (Наблюдаемый объект). * Детали — это данные (числа, объекты, события клика). * Роботы — это Operators (Операторы: map, filter, switchMap). * Конечный склад — это Subscriber (Подписчик), который получает готовый результат.

    !Визуализация принципа работы RxJS: поток данных проходит через операторы трансформации и фильтрации.

    Зачем это нужно?

    В корпоративных приложениях мы постоянно работаем с асинхронностью. RxJS позволяет описывать сложные сценарии декларативно.

    Рассмотрим пример «Живого поиска» (Typeahead). Задача: отправлять запрос на сервер при вводе текста, но не на каждую букву, а только когда пользователь перестал печатать, и игнорировать старые запросы, если пришел новый.

    Без RxJS это потребовало бы множества переменных для таймеров и проверок. С RxJS это выглядит так:

    В современном Angular мы используем RxJS преимущественно для сложных асинхронных событий (HTTP-запросы, WebSockets, сложные формы).

    Signals: Революция в реактивности

    Начиная с Angular 16, в фреймворке появился новый примитив реактивности — Signals (Сигналы).

    Проблема Zone.js

    Раньше Angular использовал библиотеку Zone.js, чтобы узнавать, когда что-то изменилось. Zone.js «патчила» все браузерные события (клики, таймеры), чтобы запускать проверку изменений (Change Detection). Это работало магически, но имело цену в производительности: любое событие могло вызвать перерисовку всего дерева компонентов.

    Как работают Сигналы

    Сигнал — это обертка вокруг значения, которая умеет уведомлять зависимых потребителей об изменении. Это «точечная» реактивность.

    Представьте, что у вас есть ячейка в Excel. Если вы меняете значение в ячейке A1, ячейка B1 (которая равна =A1*2) обновляется автоматически. Сигналы работают именно так.

    Когда меняется count, Angular точно знает, где именно в шаблоне используется doubleCount, и обновляет только этот текстовый узел, не проверяя все дерево компонентов.

    Главное правило: Используйте Signals для синхронного состояния (данные для отображения в шаблоне, флаги видимости, локальные счетчики).

    NgRx: Глобальное управление состоянием

    Когда приложение разрастается до сотен компонентов, передавать данные через @Input() и @Output() становится невозможно. Это называется «Prop Drilling» (сверление пропсов). Нам нужно место, где хранятся данные, доступные всему приложению.

    NgRx — это реализация паттерна Redux для Angular. Она базируется на трех принципах:

  • Single Source of Truth (Единый источник правды): Все состояние приложения хранится в одном объекте (Store).
  • State is Read-Only (Состояние только для чтения): Мы не можем менять данные напрямую (store.users = [] — запрещено).
  • Changes are made with Pure Functions (Изменения через чистые функции): Чтобы изменить состояние, мы отправляем «Action», который обрабатывается «Reducer-ом».
  • Круговорот данных в NgRx

    Этот цикл обеспечивает предсказуемость. Вы всегда знаете, кто и когда изменил данные.

    !Схема однонаправленного потока данных в архитектуре NgRx.

    Разберем элементы:

  • Actions (Действия): Уникальные события. Например: [Auth Page] Login Button Clicked или [Auth API] Login Success.
  • Store (Хранилище): База данных на клиенте.
  • Reducers (Редьюсеры): Чистые функции, которые берут текущее состояние и Action, и возвращают новое состояние.
  • Selectors (Селекторы): Функции, которые «вырезают» нужный кусочек данных из Store для компонента.
  • Effects (Эффекты): Здесь живет бизнес-логика и «грязная» работа (HTTP-запросы). Эффекты слушают Actions и порождают новые Actions.
  • Синтез: Как использовать их вместе?

    В современном Enterprise-приложении на Angular мы комбинируем эти подходы. Это не выбор «или/или», это симбиоз.

    Архитектурный рецепт:

    | Слой приложения | Инструмент | Почему? | | :--- | :--- | :--- | | События (Events) | RxJS | Лучший инструмент для обработки потоков времени (debounce, throttle, retry). | | Глобальное состояние | NgRx Store | Обеспечивает предсказуемость, удобную отладку (Time Travel Debugging) и кэширование данных. | | Локальное состояние | Signals | Упрощает код компонентов, убирает необходимость в ручной отписке (async pipe). | | Связь Store -> View | NgRx Signals | Современный NgRx позволяет получать данные из Store сразу как Сигналы (store.selectSignal(...)). |

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

  • Компонент (View): Пользователь нажимает кнопку «Обновить». Компонент вызывает метод, который отправляет Action в NgRx.
  • NgRx (Logic): Effect перехватывает Action. Он использует RxJS оператор switchMap для вызова API-сервиса.
  • API (Data): Сервис возвращает данные (Observable).
  • NgRx (State): Effect отправляет новый Action Load Success с данными. Reducer сохраняет их в Store.
  • Компонент (View): Компонент подписан на Store через Signal. Как только данные в Store обновились, Сигнал автоматически обновляет шаблон.
  • Заключение

    Реактивное программирование — это сдвиг парадигмы. Вместо того чтобы приказывать компьютеру «сделай А, потом Б», мы описываем связи: «Б всегда зависит от А».

    * Используйте RxJS для оркестрации сложных асинхронных процессов. * Используйте Signals для отображения данных и простых зависимостей. * Используйте NgRx для хранения бизнес-данных, которые нужны во многих местах приложения.

    Освоив эту триаду, вы получите полный контроль над поведением вашего приложения, каким бы сложным оно ни стало. В следующей статье мы поговорим о том, как взаимодействовать с внешним миром: Продвинутая работа с HTTP и перехватчиками (Interceptors).

    3. Сложные формы, валидация и взаимодействие с серверным API

    Сложные формы, валидация и взаимодействие с серверным API

    В предыдущих частях курса мы построили архитектурный каркас приложения и настроили потоки данных с помощью RxJS и Signals. Теперь мы переходим к самой «интерактивной» части Enterprise-разработки. Корпоративное приложение — это, по сути, интерфейс для ввода и обработки данных. Отчеты, анкеты сотрудников, настройки системы, оформление заказов — всё это формы.

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

    Реактивные формы: Стандарт индустрии

    Angular предлагает два подхода к формам: Template-Driven (управляемые шаблоном) и Reactive Forms (реактивные формы). Для простых форм логина первый подход приемлем, но в Enterprise-среде стандартом де-факто являются Reactive Forms.

    Почему? Потому что модель формы создается в коде (TypeScript), а не в HTML. Это дает нам:

  • Тестируемость: Мы можем протестировать логику валидации без рендеринга UI.
  • Масштабируемость: Легко динамически добавлять или удалять поля.
  • Типизация: Начиная с Angular 14, формы стали строго типизированными.
  • Строгая типизация (Typed Forms)

    Раньше FormGroup был «черным ящиком», принимающим любые значения (any). Это приводило к ошибкам в рантайме. Теперь мы точно знаем структуру данных.

    Использование опции nonNullable: true гарантирует, что значение контрола никогда не станет null (при сбросе формы оно вернется к дефолтному значению).

    Управление сложностью: ControlValueAccessor

    Одна из самых частых ошибок новичков — создание гигантского компонента на 500 строк, который управляет огромной формой. В Enterprise мы следуем принципу декомпозиции.

    Если у вас есть сложный блок формы (например, «Адрес проживания» с полями Страна, Город, Улица), его нужно вынести в отдельный компонент. Но как сделать так, чтобы этот компонент вел себя как обычный <input> и мог быть частью родительской FormGroup?

    Для этого используется интерфейс ControlValueAccessor (CVA).

    !ControlValueAccessor служит мостом между API форм Angular и вашим кастомным компонентом.

    Реализуя CVA, ваш компонент учится четырем вещам:

  • writeValue(value): «Родитель сказал мне принять это значение» (данные идут из модели в UI).
  • registerOnChange(fn): «Вот функция, которую я должен вызвать, когда пользователь что-то изменит» (данные идут из UI в модель).
  • registerOnTouched(fn): «Сообщить родителю, что пользователь потрогал поле» (для валидации).
  • setDisabledState(isDisabled): «Меня заблокировали или разблокировали».
  • Пример использования:

    Теперь вы можете использовать <app-address-input formControlName="address"> так же просто, как обычный текстовый инпут.

    Валидация: Синхронная и Асинхронная

    Валидаторы — это просто функции, которые получают AbstractControl и возвращают ошибку (объект) или null, если всё хорошо.

    Асинхронная валидация

    В корпоративных системах часто нужно проверить данные на сервере до отправки формы. Например, занят ли email или существует ли табельный номер сотрудника.

    Асинхронный валидатор возвращает Observable или Promise. Angular автоматически переключает статус контрола в PENDING, пока идет проверка.

    Важно: Асинхронные валидаторы запускаются только после того, как все синхронные валидаторы прошли успешно. Это экономит трафик.

    Взаимодействие с API: HttpClient и Interceptors

    Форма заполнена и валидна. Теперь данные нужно отправить на сервер. Для этого используется сервис HttpClient.

    В больших приложениях мы никогда не вызываем HttpClient прямо в компонентах. Мы используем слой сервисов (Data Access Layer), который мы обсуждали в первой статье.

    Interceptors (Перехватчики)

    Что делать, если к каждому запросу нужно добавить JWT-токен авторизации? Или логировать каждую ошибку? Или показывать спиннер загрузки?

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

    С появлением Standalone API мы используем функциональные интерцепторы.

    !Интерцепторы образуют цепочку обработки, через которую проходят все входящие и исходящие HTTP-сообщения.

    Пример интерцептора, добавляющего токен:

    Обработка ошибок

    Глобальная обработка ошибок тоже делается через интерцептор. Это позволяет централизованно реагировать на статусы 401 Unauthorized (разлогинивать пользователя) или 500 Server Error (показывать уведомление).

    Подключение интерцепторов происходит в конфигурации приложения (app.config.ts):

    Паттерны отправки данных

    При отправке формы важно управлять UX. Есть два основных подхода:

  • Pessimistic UI (Пессимистичный): Пользователь нажимает «Сохранить», мы показываем спиннер, блокируем интерфейс, ждем ответа сервера. Если успех — обновляем список. Это надежно и просто.
  • Optimistic UI (Оптимистичный): Мы сразу обновляем интерфейс, как будто сервер уже ответил «ОК», и фоном шлем запрос. Если вдруг пришла ошибка — мы откатываем изменения и показываем уведомление. Это сложнее в реализации (нужен надежный State Management, например NgRx), но создает ощущение мгновенной работы приложения.
  • Для критически важных данных (финансы, настройки безопасности) в Enterprise обычно используют пессимистичный подход, чтобы исключить рассинхронизацию.

    Заключение

    Работа с формами и API — это рутина Enterprise-разработчика. Использование Typed Reactive Forms защищает от ошибок типов. Паттерн ControlValueAccessor позволяет дробить сложные формы на переиспользуемые кирпичики. А HttpInterceptors берут на себя всю грязную работу по настройке запросов и обработке ошибок.

    В следующей, заключительной статье цикла, мы рассмотрим то, что отличает профессионала от любителя: Тестирование (Unit, E2E) и оптимизация производительности.

    4. Тестирование приложений, CI/CD и обеспечение безопасности данных

    Тестирование приложений, CI/CD и обеспечение безопасности данных

    Мы подошли к финальной части нашего курса по разработке корпоративных приложений на Angular. В предыдущих статьях мы спроектировали надежную архитектуру, настроили реактивные потоки данных и научились работать со сложными формами. Казалось бы, приложение готово. Но в мире Enterprise написание кода — это только половина дела.

    Корпоративное приложение отличается от пет-проекта тем, что оно должно работать стабильно годами, выдерживать атаки злоумышленников и обновляться без страха сломать старый функционал. Сегодня мы поговорим о «страховочной сетке» разработчика: тестировании, автоматизации поставки (CI/CD) и безопасности.

    Пирамида тестирования в Angular

    Главный страх любого разработчика в большой команде — «Я поправил одну кнопку, а в другом модуле отвалилась корзина». Чтобы этого избежать, мы строим стратегию тестирования, основанную на Пирамиде тестирования.

    !Классическая пирамида тестирования, показывающая соотношение разных типов тестов в проекте.

    1. Unit-тестирование (Модульные тесты)

    Это фундамент. Здесь мы тестируем самую мелкую единицу кода: функцию, сервис или класс компонента в изоляции. В Enterprise-проектах на Angular стандартом де-факто стал фреймворк Jest вместо устаревающего Karma/Jasmine. Jest быстрее, не требует браузера для запуска и имеет удобный синтаксис.

    Что тестируем: * Бизнес-логику в сервисах. * Пайпы (Pipes). * Утилитарные функции. * Валидаторы форм.

    Пример теста сервиса на Jest:

    2. Интеграционное тестирование компонентов

    Здесь мы проверяем, как компонент взаимодействует с шаблоном (HTML) и другими компонентами. Angular предоставляет мощный инструмент — TestBed.

    Однако в больших проектах тесты компонентов часто становятся хрупкими: вы изменили CSS-класс или структуру HTML, и тест упал, хотя логика работает. Решение — Angular Component Harness.

    Component Harness — это паттерн, который позволяет взаимодействовать с компонентом через API, а не через CSS-селекторы. Это абстракция над DOM.

    Без Harness (плохо):

    С Harness (хорошо):

    Если вы используете Angular Material, для всех компонентов уже есть готовые Harnesses. Для своих компонентов их стоит написать. Это делает тесты устойчивыми к рефакторингу верстки.

    3. E2E-тестирование (Сквозные тесты)

    Вершина пирамиды. Эти тесты запускают реальный браузер и эмулируют действия пользователя: «Открыть сайт, залогиниться, добавить товар в корзину».

    Лидером в этой области сейчас является Playwright (или Cypress). Они позволяют писать надежные сценарии, которые не падают из-за сетевых задержек.

    > E2E тесты дорогие и медленные. Покрывайте ими только критические бизнес-сценарии (Happy Path).

    CI/CD: Конвейер поставки

    В Enterprise код не попадает в продакшн с локальной машины разработчика. Он проходит через CI/CD Pipeline (Конвейер непрерывной интеграции и доставки).

    CI (Continuous Integration) — это процесс автоматической проверки кода при каждом коммите или пулл-реквесте.

    Типичный пайплайн для Angular-проекта:

  • Lint: Статический анализ кода (ESLint, Prettier). Проверяет стиль кода и потенциальные ошибки (например, неиспользуемые переменные).
  • Test: Запуск Unit и Component тестов. Если хоть один тест упал, сборка останавливается.
  • Build: Сборка приложения (ng build). Здесь проверяется, что приложение вообще компилируется, и работают механизмы Tree-shaking.
  • E2E: Запуск сквозных тестов на тестовом стенде.
  • !Автоматизированный процесс доставки кода от коммита до продакшена.

    Оптимизация в монорепозитории

    В первой статье мы говорили про Nx. В CI/CD монорепозитории раскрываются на полную мощность. Благодаря команде nx affected, система запускает тесты и линтеры только для тех библиотек, которые были изменены в текущем коммите, и зависимых от них.

    Это экономит часы времени. Вместо прогона 5000 тестов всего проекта, система прогонит только 50 тестов, связанных с вашей задачей.

    Budgets (Бюджеты производительности)

    В файле angular.json можно настроить бюджеты на размер бандла. Если кто-то случайно импортирует огромную библиотеку (например, moment.js целиком), билд упадет с ошибкой.

    Безопасность данных в Angular

    Безопасность — это не фича, это процесс. Angular «из коробки» защищает от многих угроз, но разработчики часто сами отключают эту защиту ради удобства.

    XSS (Межсайтовый скриптинг)

    Самая частая уязвимость. Злоумышленник пытается внедрить свой JavaScript-код на вашу страницу (например, через комментарий), чтобы украсть куки или токены пользователей.

    Angular автоматически экранирует все значения, которые вы выводите через интерполяцию {{ value }}. Он превращает <script> в безопасный текст.

    Опасная зона: Использование innerHTML.

    Если вам действительно нужно вывести HTML (например, из редактора статей), используйте DomSanitizer, но будьте крайне осторожны. Никогда не вызывайте bypassSecurityTrustHtml для данных, пришедших от пользователя, без предварительной очистки на бэкенде или использования библиотек вроде DOMPurify.

    CSP (Content Security Policy)

    Это HTTP-заголовок, который говорит браузеру, откуда можно загружать скрипты, стили и картинки. Это второй эшелон защиты после санитайзеров Angular.

    Если злоумышленник все же внедрит скрипт <script src="evil.com/hack.js">, браузер заблокирует его загрузку, так как evil.com нет в белом списке CSP.

    Хранение токенов (JWT)

    Где хранить Access Token?

  • LocalStorage: Удобно, но уязвимо для XSS. Если хакер выполнит JS на вашей странице, он сможет прочитать localStorage.
  • HttpOnly Cookie: Более безопасный вариант. Кука устанавливается сервером, и JavaScript не имеет к ней доступа. Она автоматически отправляется с каждым запросом.
  • Для корпоративных приложений с высокими требованиями к безопасности рекомендуется использовать связку HttpOnly Cookies для токенов и CSRF-токенов для защиты от подделки запросов.

    Route Guards (Защита маршрутов)

    Не забывайте защищать роуты на клиенте. Хотя это не заменяет проверку прав на сервере (API всегда должно проверять права!), это улучшает UX.

    Используйте функциональные гарды:

    Заключение курса

    Поздравляю! Мы прошли путь от проектирования архитектуры до настройки CI/CD.

    Мы узнали, что:

  • Архитектура (Nx, Standalone) позволяет управлять сложностью.
  • Реактивность (RxJS, Signals) делает приложение отзывчивым.
  • Типизированные формы защищают от ошибок ввода.
  • Тесты и CI/CD дают уверенность в завтрашнем дне.
  • Разработка корпоративных приложений на Angular — это марафон, а не спринт. Используйте инструменты, которые мы разобрали, пишите чистый код и не забывайте обновлять зависимости. Удачи в создании надежных систем!

    5. Оптимизация производительности, Server-Side Rendering и стратегии деплоя

    Оптимизация производительности, Server-Side Rendering и стратегии деплоя

    Мы прошли долгий путь. Мы спроектировали архитектуру, настроили реактивные потоки данных, создали сложные формы и покрыли код тестами. Наше корпоративное приложение работает корректно. Но в мире Enterprise «работает» — это только половина успеха. Приложение должно работать быстро и быть доступным для пользователей в любых условиях.

    Медленный интерфейс стоит бизнесу денег: сотрудники тратят больше времени на операции, а клиенты уходят к конкурентам. В этой статье мы разберем, как заставить Angular летать, как настроить Server-Side Rendering (SSR) для мгновенной отрисовки и как правильно упаковать приложение для деплоя в продакшн.

    Оптимизация Runtime-производительности

    Angular — быстрый фреймворк, но неправильное использование может превратить его в неповоротливого гиганта. Главный враг производительности — лишние перерисовки интерфейса.

    Стратегия OnPush

    По умолчанию Angular использует стратегию обнаружения изменений Default. Это значит, что при любом событии (клик, таймер, ответ от сервера) Angular проверяет все компоненты в дереве сверху вниз, чтобы узнать, не изменились ли данные.

    В больших приложениях это катастрофа. Решение — стратегия OnPush.

    Когда включен OnPush, Angular проверяет компонент только в трех случаях:

  • Изменилась ссылка на входной параметр (@Input).
  • Внутри компонента произошло событие (например, клик).
  • Мы вручную пометили компонент как «грязный» через ChangeDetectorRef.markForCheck() (или использовали AsyncPipe/Signals).
  • !Сравнение стратегий обнаружения изменений: Default проверяет всё дерево, OnPush — только затронутую ветку.

    > В современном Angular использование OnPush является стандартом для всех компонентов без исключения.

    Зоны и runOutsideAngular

    Angular использует библиотеку Zone.js для отслеживания асинхронных операций. Иногда нам нужно выполнить тяжелую работу (например, отрисовку графика или обработку скролла), которая не требует обновления UI на каждом шаге.

    Если мы не выведем эту работу из зоны Angular, каждое движение мыши будет запускать цикл обнаружения изменений.

    Ускорение загрузки: Deferrable Views и изображения

    Пользователь не должен ждать загрузки всего приложения, чтобы увидеть первый экран. Мы уже обсуждали ленивую загрузку (Lazy Loading) на уровне роутера. В Angular 17+ появился мощный инструмент для ленивой загрузки на уровне шаблона — Deferrable Views (@defer).

    Блок @defer

    Этот синтаксис позволяет отложить загрузку части шаблона (и кода компонентов внутри него) до наступления определенного условия.

    Это декларативный способ разделить бандл на мелкие части (chunks) без сложной конфигурации роутера.

    Оптимизация изображений

    Картинки часто занимают большую часть трафика. Директива NgOptimizedImage (ngSrc) автоматически решает множество проблем:

    * Выбирает правильный размер изображения для текущего экрана (srcset). * Использует ленивую загрузку (lazy loading) по умолчанию. * Предупреждает, если картинка слишком большая или не имеет заданных размеров (что вызывает сдвиг макета — CLS).

    Атрибут priority важен для LCP (Largest Contentful Paint) — он говорит браузеру загрузить эту картинку в первую очередь.

    Server-Side Rendering (SSR) и Гидратация

    Обычное SPA (Single Page Application) отправляет в браузер пустой HTML. Браузер загружает JS, запускает Angular, и только потом пользователь видит контент. Это плохо для SEO (поисковики видят пустую страницу) и для пользователей с медленным интернетом.

    SSR (Server-Side Rendering) решает эту проблему: сервер выполняет Angular-код, генерирует готовый HTML и отправляет его браузеру. Пользователь видит страницу мгновенно.

    Проблема мерцания и Гидратация

    Раньше (до Angular 16) работала «деструктивная гидратация»: сервер присылал HTML, но когда загружался клиентский Angular, он удалял этот HTML и рисовал всё заново. Это вызывало мерцание экрана.

    Современный Angular использует Non-destructive Hydration (Недеструктивную гидратацию). Клиентский скрипт «подключается» к уже существующему DOM, не перерисовывая его. Он просто навешивает обработчики событий.

    !Процесс гидратации: плавный переход от статического HTML к интерактивному приложению без перерисовки.

    Чтобы включить гидратацию в новом проекте, достаточно добавить провайдер:

    Стратегии деплоя: Docker и Nginx

    В Enterprise-среде приложение не запускается командой ng serve. Оно собирается в статические файлы и раздается веб-сервером. Стандартом упаковки является Docker.

    Multi-stage сборка

    Мы не хотим тащить Node.js и исходный код в продакшн-контейнер. Нам нужны только скомпилированные файлы (dist/) и легкий веб-сервер. Для этого используется многоэтапная сборка Docker.

    Пример Dockerfile:

    Этот подход позволяет получить образ размером 20-30 МБ вместо 1 ГБ.

    Конфигурация Nginx для SPA

    Angular — это SPA. У нас есть только один файл index.html. Если пользователь обновит страницу /dashboard/users, сервер по умолчанию попытается найти такой файл на диске и вернет ошибку 404.

    Нам нужно настроить Nginx так, чтобы на любой неизвестный запрос он отдавал index.html, а Angular Router сам разберется, что показать.

    Также критически важно включить сжатие Gzip или Brotli на уровне Nginx, что может уменьшить размер текстовых файлов (JS, CSS, HTML) на 70%.

    Заключение курса

    Мы завершаем наш курс «Разработка корпоративных приложений на Angular». Мы прошли путь от чистого листа до готового к продакшену Enterprise-решения.

    Вспомним ключевые вехи:

  • Архитектура: Мы разбили монолит на библиотеки и использовали Standalone Components.
  • Данные: Мы обуздали потоки с RxJS и упростили состояние с Signals.
  • Формы: Мы научились валидировать сложные данные.
  • Качество: Мы настроили тесты и CI/CD.
  • Производительность: Мы оптимизировали рендеринг и настроили Docker-контейнер.
  • Angular — это мощный инструмент. Он требует дисциплины и знаний, но взамен дает надежность и масштабируемость, которые так важны в корпоративном секторе. Развивайтесь, следите за обновлениями фреймворка и пишите код, которым можно гордиться.