Angular 19: Современная веб-разработка

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

1. Введение в экосистему Angular 19 и архитектура на основе Standalone-компонентов

Введение в экосистему Angular 19 и архитектура на основе Standalone-компонентов

Добро пожаловать в курс «Angular 19: Современная веб-разработка». Мы начинаем погружение в один из самых мощных и зрелых фреймворков для создания веб-приложений. Angular прошел долгий путь эволюции, и версия 19 представляет собой кульминацию усилий команды Google по упрощению разработки, повышению производительности и улучшению опыта разработчика (Developer Experience — DX).

В этой первой статье мы разберем, что представляет собой современная экосистема Angular, почему произошел отказ от модульной системы в пользу Standalone-компонентов и как теперь выглядит архитектура приложения.

Экосистема Angular: Больше, чем просто библиотека

В отличие от многих конкурентов (например, React, который позиционирует себя как библиотека для построения интерфейсов), Angular — это полноценная платформа. Это означает, что «из коробки» вы получаете всё необходимое для создания сложного корпоративного приложения.

Основные столпы экосистемы Angular 19

  • Component Model (Модель компонентов): Основа всего. В Angular 19 компоненты стали более автономными и легкими благодаря архитектуре Standalone.
  • Reactivity (Реактивность): Введение Signals (Сигналов) кардинально изменило способ отслеживания изменений данных, делая приложения быстрее и предсказуемее.
  • Tooling (Инструментарий): Angular CLI (Command Line Interface) — это стандарт индустрии. В 19-й версии сборка проектов происходит молниеносно благодаря использованию esbuild и Vite под капотом.
  • Routing (Маршрутизация): Мощный встроенный роутер, поддерживающий ленивую загрузку (lazy loading), защитников (guards) и вложенные маршруты.
  • Forms (Формы): Две парадигмы работы с формами (Template-driven и Reactive) позволяют обрабатывать пользовательский ввод любой сложности.
  • Dependency Injection (Внедрение зависимостей): Паттерн проектирования, встроенный в ядро фреймворка, который позволяет эффективно управлять сервисами и состоянием приложения.
  • !Визуализация компонентов экосистемы Angular, демонстрирующая полноту платформы.

    Эволюция архитектуры: От NgModules к Standalone

    Долгое время (начиная с Angular 2 и до 14-й версии) главным архитектурным блоком приложения был NgModule. Каждый компонент, директива или пайп (pipe) должны были быть задекларированы в модуле. Это создавало лишний шаблонный код (boilerplate) и повышало порог входа для новичков.

    С приходом Angular 19 (и стабилизацией этой фичи в предыдущих версиях) стандартом де-факто стали Standalone-компоненты (автономные компоненты). Это сдвиг парадигмы, который делает Angular более похожим на нативные веб-стандарты и упрощает ментальную модель разработчика.

    Проблема NgModules

    Раньше, чтобы создать простой компонент «Hello World», вам нужно было:

  • Создать файл компонента.
  • Создать файл модуля (или использовать существующий).
  • Добавить компонент в массив declarations модуля.
  • Если компонент использовал другие фичи (например, ngIf), нужно было импортировать CommonModule в модуль.
  • Это создавало неявные связи. Глядя на код компонента, было невозможно точно сказать, какие у него зависимости, так как они определялись в родительском модуле.

    Решение: Standalone Components

    Standalone-компоненты самодостаточны. Они сами «знают», что им нужно для работы, и сами определяют свои зависимости. Это делает код более читаемым, легким для тестирования и рефакторинга.

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

    Давайте рассмотрим, как выглядит современный компонент в Angular 19. Ключевым отличием является флаг standalone: true в декораторе @Component.

    Разберем ключевые элементы этого кода:

    * standalone: true: Эта строка сообщает компилятору Angular, что компонент не требует включения в NgModule. Он может использоваться самостоятельно. * imports: Это самое важное изменение. Раньше мы импортировали модули в другие модули. Теперь мы импортируем зависимости (другие компоненты, директивы, пайпы или даже старые модули) прямо в компонент, который их использует. В примере выше AppComponent напрямую импортирует UserProfileComponent. * selector: Имя тега HTML, который будет использоваться для вставки компонента (например, <app-root>). * template: HTML-разметка компонента.

    > Standalone-компоненты делают поток данных и зависимостей явным. Вы всегда видите, что использует ваш компонент, просто взглянув на массив imports.

    !Сравнение старой модульной архитектуры и новой архитектуры на основе Standalone-компонентов.

    Запуск приложения (Bootstrapping)

    Изменение архитектуры компонентов повлекло за собой изменение способа запуска приложения. В старых версиях мы загружали корневой модуль (AppModule). В Angular 19 мы загружаем корневой компонент.

    Файл main.ts теперь выглядит лаконично:

    Функция bootstrapApplication принимает два аргумента:

  • Корневой Standalone-компонент.
  • Объект конфигурации (где настраиваются глобальные провайдеры, такие как маршрутизация или HTTP-клиент).
  • Конфигурация приложения (app.config.ts)

    Вместо импорта модулей в AppModule для настройки глобальных сервисов, теперь используется файл конфигурации:

    Здесь мы видим использование функций provide.... Это современный способ регистрации глобальных сервисов. Например, provideRouter подключает маршрутизацию, а provideHttpClient (если бы он был нужен) подключил бы возможность делать HTTP-запросы.

    Преимущества подхода Standalone

    Переход на Standalone-архитектуру в Angular 19 дает ряд существенных преимуществ:

  • Упрощение обучения: Новичкам больше не нужно разбираться в концепции NgModule, которая была уникальна для Angular и не имела аналогов в других фреймворках.
  • Tree-shaking (Удаление мертвого кода): Сборщикам (bundlers) проще анализировать статические импорты в компонентах, чем динамические связи модулей. Это позволяет эффективнее удалять неиспользуемый код, уменьшая размер итогового бандла.
  • Ленивая загрузка (Lazy Loading): Загрузка отдельных компонентов стала тривиальной. Раньше для ленивой загрузки нужно было создавать отдельный модуль с маршрутизацией. Теперь loadComponent в роутере позволяет загрузить любой Standalone-компонент по требованию.
  • Совместимость (Interoperability)

    Важно отметить, что Angular 19 не «ломает» старый код. Standalone-компоненты могут импортировать существующие NgModules, и наоборот — NgModules могут импортировать Standalone-компоненты. Это позволяет мигрировать проекты постепенно, файл за файлом, не переписывая всё приложение разом.

    Структура проекта Angular 19

    Когда вы создаете новый проект командой ng new my-app, вы получаете следующую структуру (упрощенно):

    * src/main.ts: Точка входа в приложение. * src/index.html: Главный HTML-файл. * src/app/app.component.ts: Корневой компонент (Standalone). * src/app/app.config.ts: Глобальная конфигурация. * src/app/app.routes.ts: Определение маршрутов.

    Эта структура плоская, понятная и лишена лишних файлов модулей.

    Заключение

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

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

    Готовы проверить, насколько хорошо вы усвоили концепцию Standalone-компонентов? Переходите к домашнему заданию.

    2. Реактивность нового уровня: Глубокое погружение в Signals, Computed и Effects

    Реактивность нового уровня: Глубокое погружение в Signals, Computed и Effects

    В предыдущей лекции мы разобрали архитектурный фундамент Angular 19 — Standalone-компоненты. Мы научились создавать структуру приложения без лишних модулей. Теперь пришло время вдохнуть в эту структуру жизнь. Мы поговорим о данных и о том, как приложение реагирует на их изменения.

    Долгое время Angular полагался на библиотеку Zone.js и механизм «грязной проверки» (dirty checking). Это работало надежно, но не всегда эффективно: любое событие (клик, таймер, HTTP-запрос) могло запустить проверку всего дерева компонентов. Angular 19 предлагает новую парадигму — Signals (Сигналы). Это не просто новая фича, это фундаментальный сдвиг в сторону мелкозернистой реактивности (fine-grained reactivity).

    Что такое Signal?

    Представьте себе ячейку в таблице Excel. Если в ячейке A1 записано число 10, а в ячейке B1 формула =A1 * 2, то как только вы измените A1, ячейка B1 обновится автоматически. Вам не нужно нажимать кнопку «пересчитать всё». Excel знает, что B1 зависит от A1.

    Signal — это обертка вокруг значения, которая умеет делать две вещи:

  • Хранить значение.
  • Уведомлять всех, кто это значение использует (зависит от него), о том, что оно изменилось.
  • В Angular есть три примитива реактивности:

  • Writable Signal (Записываемый сигнал) — источник данных, который можно менять.
  • Computed Signal (Вычисляемый сигнал) — данные, которые зависят от других сигналов (как формула в Excel).
  • Effect (Эффект) — действие, которое происходит при изменении сигнала (например, логирование или работа с DOM).
  • !Визуализация потока данных: от источника (сигнала) через вычисления к обновлению интерфейса.

    Writable Signals: Источник правды

    Давайте создадим наш первый сигнал. Для этого используется функция signal из пакета @angular/core.

    Чтение значения

    Обратите внимание на синтаксис в шаблоне: {{ count() }}. Сигнал — это функция-геттер. Чтобы получить значение, мы должны «вызвать» сигнал. Это позволяет Angular понять, где именно используется этот сигнал, чтобы обновлять только это место при изменениях.

    Изменение значения

    У записываемого сигнала есть методы: * .set(newValue): Полностью заменяет значение. * .update(fn): Принимает функцию, которая получает текущее значение и возвращает новое. Удобно для инкрементов или работы с коллекциями.

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

    Computed Signals: Умные вычисления

    Часто нам нужно отображать производные данные. Например, у нас есть список товаров (сигнал) и фильтр (сигнал), и нам нужно показать отфильтрованный список.

    Для этого используется computed. Главная особенность computedмемоизация (кеширование).

    Как работает Computed?

  • Ленивость: Функция внутри computed не выполнится, пока кто-то (например, шаблон) не запросит значение total().
  • Кеширование: Если вы вызовете total() десять раз подряд, функция вычисления сработает только один раз. Следующие 9 раз вернется закешированное значение.
  • Реактивность: Кеш сбрасывается только тогда, когда меняется один из сигналов, используемых внутри (price или quantity).
  • Это кардинально отличается от обычных геттеров в классах или вызова функций в шаблоне ({{ calculateTotal() }}), которые в старом Angular вызывались бы при каждом цикле обнаружения изменений.

    Effects: Побочные эффекты

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

    Для этого используется effect.

    Правила использования Effects

  • Автоматическое отслеживание: Вам не нужно указывать массив зависимостей (как в React useEffect). Angular сам видит, какие сигналы вы прочитали внутри функции эффекта (в примере выше — this.isDarkTheme()), и подписывается на них.
  • Когда запускается: Эффект запускается как минимум один раз при инициализации, а затем каждый раз, когда меняются сигналы-зависимости.
  • Осторожно с записью: Избегайте изменения других сигналов внутри effect. Это может привести к бесконечным циклам или непредсказуемому потоку данных. Если вам нужно изменить одни данные на основе других — используйте computed.
  • Сравнение с RxJS

    Многие Angular-разработчики привыкли к RxJS (BehaviorSubject, Observable). Зачем нам Сигналы?

    | Характеристика | RxJS (Observables) | Signals | | :--- | :--- | :--- | | Назначение | События во времени (потоки) | Управление состоянием (State) | | Значение | Может не иметь значения | Всегда имеет текущее значение | | Доступ | Асинхронный (нужен subscribe или async pipe) | Синхронный (вызов функции) | | Сложность | Высокая (множество операторов) | Низкая (простые примитивы) |

    Angular 19 предоставляет отличные инструменты для взаимодействия (Interoperability) между этими мирами: * toSignal(observable): Превращает поток в сигнал. * toObservable(signal): Превращает сигнал в поток.

    Преимущества для производительности

    Самое важное преимущество сигналов — это возможность Zoneless приложений (приложений без Zone.js).

    В классическом Angular, если вы кликнули кнопку, Angular проверял все компоненты на странице: «А не изменилось ли что-нибудь у вас?». С сигналами Angular точно знает: «Изменился сигнал count в компоненте CounterComponent. Мне нужно обновить только текстовый узел в этом компоненте».

    Это делает приложения на Angular 19 невероятно быстрыми, особенно на мобильных устройствах и в больших корпоративных системах.

    Заключение

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

    Ключевые выводы: * Используйте signal() для хранения изменяемого состояния. * Используйте computed() для данных, которые зависят от других данных. * Используйте effect() для синхронизации с внешним миром (DOM, API, Storage). * Читайте значение сигнала как функцию: value().

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

    А теперь давайте проверим, как вы усвоили материал о сигналах.

    3. Обновленный синтаксис шаблонов: Встроенный Control Flow и отложенная загрузка @defer

    Обновленный синтаксис шаблонов: Встроенный Control Flow и отложенная загрузка @defer

    В предыдущих статьях мы заложили прочный фундамент: разобрались с архитектурой Standalone-компонентов и освоили реактивность на базе Signals. Теперь, когда мы умеем хранить и обрабатывать данные, настало время научиться эффективно отображать их в пользовательском интерфейсе.

    Angular 19 предлагает революционные изменения в синтаксисе шаблонов. Мы прощаемся с привычными структурными директивами ngIf, ngFor и *ngSwitch, которые служили нам верой и правдой много лет, и переходим на встроенный управляющий поток (Built-in Control Flow). Кроме того, мы рассмотрим мощнейший инструмент для оптимизации производительности — @defer.

    Почему Angular отказался от структурных директив?

    Долгое время логика отображения в шаблонах строилась на директивах. Вспомните этот синтаксис:

    У этого подхода были недостатки:

  • Зависимость от модулей: Чтобы использовать *ngIf, нужно было импортировать CommonModule или NgIf в imports вашего Standalone-компонента.
  • Сложность синтаксиса: Использование else требовало создания отдельного <ng-template>, что усложняло чтение кода.
  • Производительность: Директивы — это классы TypeScript, которые Angular должен инстанцировать и проверять. Встроенный синтаксис компилируется напрямую в инструкции JavaScript, что работает быстрее.
  • Новый синтаксис, начинающийся с символа @, встроен прямо в компилятор шаблонов. Ему не нужны импорты.

    Условный рендеринг: @if

    Новый блок @if выглядит гораздо чище и понятнее, напоминая обычный JavaScript.

    Обратите внимание на ключевые отличия: * Нет необходимости в контейнерах <ng-container> или <ng-template> для блоков else. * Скобки {} четко очерчивают границы контента. * Поскольку мы используем Signals (как изучили в прошлой лекции), мы вызываем их как функции: user.isLoggedIn().

    > Новый синтаксис обеспечивает лучшее сужение типов (type narrowing) в TypeScript. Если внутри @if вы проверяете наличие поля, Angular точно знает, что внутри блока это поле существует.

    Циклы: @for

    Рендеринг списков — одна из самых частых задач. Новый блок @for заменяет *ngFor и приносит значительные улучшения производительности и удобства.

    Основной синтаксис

    Обязательный track

    Вы могли заметить выражение track item.id. В старом *ngFor функция trackBy была опциональной (хотя и крайне рекомендуемой). В новом @for выражение track является обязательным.

    Зачем это нужно? Когда массив данных меняется (например, вы добавили элемент в середину списка), Angular должен понять, какие элементы DOM нужно перерисовать. Без track фреймворку пришлось бы пересоздавать весь список заново, что очень дорого для браузера. Указывая уникальный идентификатор (например, id), вы помогаете Angular переместить существующий DOM-элемент, а не создавать новый.

    Если у ваших объектов нет уникальных ID, вы можете использовать сам объект (хотя это менее эффективно) или индекс:

    Встроенные переменные

    Внутри @for доступны удобные переменные контекста (без необходимости использовать ключевое слово let, как раньше): * first: true, если элемент первый. * even / count: общее количество элементов.

    Блок @empty

    Больше не нужно писать отдельный @if для проверки длины массива. Блок @for поддерживает секцию @empty, которая отображается, если массив пуст.

    !Визуализация механизма Reconciliation (сверки) в цикле @for, демонстрирующая эффективность использования track.

    Выбор вариантов: @switch

    Блок @switch работает аналогично оператору switch в JavaScript и заменяет ngSwitch.

    Здесь также не требуются импорты, и работает проверка типов. Если userRole может быть только 'admin' | 'user', а вы напишете @case ('guest'), TypeScript может выдать предупреждение (в зависимости от настроек строгости).

    Отложенная загрузка: @defer

    Теперь перейдем к одной из самых мощных возможностей Angular 19 — Deferrable Views (Откладываемые представления).

    Раньше для ленивой загрузки (Lazy Loading) нам приходилось использовать Роутер. Мы дробили приложение на маршруты, и код загружался только при переходе на новую страницу. Но что, если мы хотим лениво загрузить тяжелый компонент (например, график или карту) внутри одной страницы?

    Блок @defer позволяет декларативно указать часть шаблона, которая должна быть загружена позже. Angular автоматически выделит компоненты внутри этого блока в отдельный JavaScript-файл (chunk) и загрузит его только тогда, когда это нужно.

    Анатомия @defer

    Блок @defer имеет несколько состояний:

  • @defer: Основной контент, который будет загружен лениво.
  • @placeholder: То, что показывается до начала загрузки (например, пустой прямоугольник).
  • @loading: То, что показывается во время загрузки чанка по сети (например, спиннер).
  • @error: То, что показывается, если загрузка не удалась.
  • Триггеры (Triggers)

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

    * on idle (по умолчанию): Загрузить, когда браузер будет в состоянии простоя (не занят важными задачами). * on viewport: Загрузить, когда плейсхолдер появится в видимой области экрана (Intersection Observer). Идеально для длинных страниц. * on interaction: Загрузить, когда пользователь взаимодействует с плейсхолдером (клик или фокус). * on hover: Загрузить при наведении мыши. * on immediate: Загрузить сразу же, как только отрендерится шаблон (но асинхронно). * on timer(5s): Загрузить через 5 секунд. * when condition: Загрузить, когда логическое условие (например, сигнал) станет true.

    Вы можете комбинировать триггеры: @defer (on viewport; on timer(2s)) — загрузить, если элемент стал виден ИЛИ прошло 2 секунды.

    Prefetching (Предзагрузка)

    Иногда мы хотим загрузить код заранее, но показать его только по действию пользователя. Для этого есть prefetch.

    В этом примере код компонента комментариев загрузится, когда браузер будет свободен (prefetch on idle). Но сам компонент отобразится и инициализируется только тогда, когда пользователь нажмет на кнопку (on interaction). Это создает мгновенный отклик интерфейса.

    !Схема жизненного цикла блока @defer, показывающая переходы между состояниями placeholder, loading, content и error.

    Миграция

    Вам не нужно переписывать все шаблоны вручную. Команда Angular предоставила автоматическую миграцию через CLI:

    Эта команда просканирует ваш проект и автоматически заменит ngIf, ngFor и *ngSwitch на новый синтаксис.

    Заключение

    Обновленный синтаксис шаблонов в Angular 19 — это огромный шаг вперед. Он делает код чище, удаляет лишние зависимости от CommonModule, улучшает типизацию и, благодаря @defer, открывает невероятные возможности для оптимизации производительности (Fine-grained Lazy Loading).

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

    Переходите к домашнему заданию, чтобы закрепить материал!

    4. Работа с данными: Внедрение зависимостей, HTTP-клиент и взаимодействие с RxJS

    Работа с данными: Внедрение зависимостей, HTTP-клиент и взаимодействие с RxJS

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

    В этой статье мы разберем «святую троицу» работы с данными в Angular 19:

  • Dependency Injection (DI) — как организовать код и доставлять сервисы в компоненты.
  • HTTP Client — как делать запросы к API.
  • RxJS Interoperability — как подружить потоки данных (Observables) с нашими любимыми Сигналами.
  • Внедрение зависимостей (Dependency Injection): Современный подход

    Внедрение зависимостей (DI) — это паттерн проектирования, который лежит в самом сердце Angular. Если объяснять просто: компоненты не должны сами создавать свои зависимости (сервисы), они должны их запрашивать.

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

    Сервисы в Angular

    Сервис — это обычный класс, который содержит бизнес-логику, не связанную напрямую с отрисовкой интерфейса. Чтобы Angular мог управлять этим классом, мы помечаем его декоратором @Injectable.

    Настройка providedIn: 'root' означает, что Angular создаст один экземпляр этого сервиса на всё приложение. Это позволяет легко обмениваться данными между разными, не связанными друг с другом компонентами.

    Функция inject()

    Долгое время стандартом в Angular было внедрение через конструктор. Однако в Angular 19 и эру Standalone-компонентов предпочтительным способом стала функция inject().

    Старый подход (Constructor Injection):

    Новый подход (inject function):

    Почему inject() лучше?

  • Чистота кода: Не нужно писать конструктор, если он не делает ничего, кроме внедрения.
  • Типизация: TypeScript отлично выводит типы автоматически.
  • Наследование: При наследовании классов вам не нужно пробрасывать зависимости через super() в родительский конструктор.
  • !Визуализация того, как Angular Injector предоставляет экземпляры сервисов компонентам.

    Настройка HTTP Client

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

    Нам больше не нужно импортировать HttpClientModule. Вместо этого мы используем провайдер-функцию provideHttpClient.

    Использование withFetch() — это современная практика, которая позволяет Angular использовать нативный браузерный fetch API вместо старого XMLHttpRequest, что полезно для SSR (Server-Side Rendering).

    HTTP-запросы и RxJS

    Angular HttpClient возвращает данные в виде Observables (наблюдаемых объектов) из библиотеки RxJS.

    Почему не Promise? Потому что Observables мощнее: * Их можно отменить (например, если пользователь ушел со страницы до завершения запроса). * Их можно модифицировать с помощью операторов (фильтровать, трансформировать). * Они представляют собой поток событий, что удобно для WebSockets или событий прогресса загрузки.

    Давайте создадим сервис для получения пользователей:

    Мост между мирами: RxJS Interop

    Здесь мы сталкиваемся с дилеммой. С одной стороны, HttpClient отдает нам Observable. С другой стороны, в компонентах Angular 19 мы хотим использовать Signals, так как они проще, не требуют ручной отписки и работают без Zone.js.

    Нам нужен мост. И этот мост — функция toSignal из пакета @angular/core/rxjs-interop.

    Использование toSignal

    Функция toSignal подписывается на Observable, получает из него значения и помещает их в Signal. Она также автоматически отписывается от потока, когда компонент уничтожается, предотвращая утечки памяти.

    Обработка начального значения

    Сигналы, в отличие от потоков, всегда должны иметь значение. Но HTTP-запрос занимает время. Что будет в сигнале users до того, как придет ответ от сервера?

    По умолчанию toSignal вернет undefined в качестве начального значения. Если вы хотите избежать типа undefined, вы можете задать начальное значение явно:

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

    Если HTTP-запрос завершится ошибкой, toSignal выбросит исключение, которое можно перехватить. Однако более надежный паттерн — обработать ошибку на уровне RxJS до превращения в сигнал.

    Реактивность на основе действий

    Часто нам нужно загружать данные не сразу при старте, а при изменении другого сигнала (например, поискового запроса или id в фильтре). Здесь нам помогут computed (для простых зависимостей) или комбинация RxJS и Signals.

    Но самый мощный паттерн — превратить сигнал-источник (например, searchQuery) в Observable, обработать его операторами RxJS (например, debounceTime для задержки), сделать запрос, и результат превратить обратно в сигнал.

    Этот код демонстрирует всю мощь Angular 19: мы используем Сигналы для состояния UI, RxJS для сложной асинхронной логики событий, и toSignal/toObservable для бесшовного переключения между ними.

    !Схема преобразования сигнала в поток для обработки HTTP-запроса и обратного преобразования в сигнал.

    Resource API (Экспериментально)

    Стоит упомянуть, что в Angular 19 появляется новый примитив resourcerxResource), который призван еще больше упростить работу с асинхронными данными, делая код похожим на computed, но для асинхронных операций. Однако на данный момент связка HttpClient + toSignal является самым стабильным и распространенным промышленным стандартом.

    Заключение

    Работа с данными в Angular 19 стала более декларативной и лаконичной.

  • Мы используем provideHttpClient в app.config.ts для настройки.
  • Мы используем inject() для получения сервисов.
  • Мы используем HttpClient для выполнения запросов, получая Observables.
  • Мы используем toSignal, чтобы превратить асинхронный поток данных в удобный для шаблона Сигнал.
  • Этот подход позволяет избавиться от множества subscribe в коде компонентов и AsyncPipe в шаблонах, делая код чище и понятнее.

    5. Производительность и рендеринг: SSR, Гидратация и Zoneless Change Detection

    Производительность и рендеринг: SSR, Гидратация и Zoneless Change Detection

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

    Но в мире веб-разработки «работает» — это только половина успеха. Вторая половина — это скорость. Как быстро пользователь увидит контент? Как быстро приложение отреагирует на нажатие кнопки? Насколько плавным будет взаимодействие?

    В этой статье мы погрузимся в продвинутые техники оптимизации производительности в Angular 19. Мы разберем три кита современного рендеринга: Server-Side Rendering (SSR), Non-destructive Hydration (Неразрушающая гидратация) и Zoneless Change Detection (Обнаружение изменений без Zone.js).

    Server-Side Rendering (SSR): Скорость первого впечатления

    Традиционные SPA (Single Page Applications) работают по принципу Client-Side Rendering (CSR). Когда пользователь заходит на сайт, сервер отдает ему пустой HTML-файл (обычно с одним тегом <app-root>) и большой JavaScript-бандл. Браузер должен скачать этот JS, распарсить его, выполнить, и только потом нарисовать интерфейс.

    Проблемы CSR:

  • Долгая первая отрисовка (FCP): Пользователь видит белый экран или спиннер, пока грузится JS.
  • SEO (Search Engine Optimization): Поисковые роботы (особенно старые или простые) могут не дождаться выполнения JS и увидеть пустую страницу.
  • Решение: SSR Server-Side Rendering означает, что Angular запускается на сервере (обычно в среде Node.js). Сервер генерирует полноценный HTML с данными и отправляет его браузеру. Пользователь видит готовую страницу практически мгновенно.

    !Сравнение жизненного цикла загрузки страницы при клиентском и серверном рендеринге.

    SSR в Angular 19

    В Angular 19 поддержка SSR стала нативной и бесшовной. Если раньше для этого требовался отдельный проект Angular Universal, то теперь это часть ядра.

    Чтобы добавить SSR в существующий проект, достаточно одной команды:

    Эта команда:

  • Создаст файл server.ts (точка входа для сервера).
  • Обновит angular.json.
  • Настроит процесс гидратации (о которой мы поговорим ниже).
  • Теперь при сборке (ng build) Angular создаст две версии приложения: одну для браузера и одну для сервера.

    Гидратация (Hydration): Оживление статики

    Итак, сервер прислал нам красивый HTML. Пользователь видит список товаров, меню и кнопки. Но если он нажмет на кнопку прямо сейчас — ничего не произойдет. Почему? Потому что JavaScript, отвечающий за логику (обработчики событий), еще не загрузился или не инициализировался. Страница «мертва».

    Процесс превращения статического HTML (пришедшего с сервера) в полностью интерактивное приложение называется Гидратацией.

    Проблема старой гидратации (Destructive Hydration)

    До Angular 16 гидратация работала грубо. Когда JS загружался, Angular видел существующий HTML, но не мог его использовать. Он удалял весь DOM, созданный сервером, и рисовал его заново с нуля. Это вызывало: * Мерцание экрана: Пользователь видел контент, потом он исчезал на долю секунды и появлялся снова. * Лишнюю работу: Браузер тратил ресурсы на перерисовку того, что уже было на экране.

    Non-destructive Hydration в Angular 19

    Современный Angular использует неразрушающую гидратацию. Вместо того чтобы удалять DOM-узлы, Angular находит их по специальным маркерам и просто «подключает» к ним слушатели событий и внутреннее состояние.

    Это происходит мгновенно и незаметно для глаза. DOM-дерево сохраняется, сохраняется позиция скролла и фокус ввода.

    Включается гидратация в app.config.ts:

    Event Replay (Воспроизведение событий)

    Представьте ситуацию: HTML загрузился, пользователь видит кнопку «Купить» и нажимает на неё. Но JS-бандл еще в пути (медленный 3G). В старом вебе клик бы просто пропал.

    Angular 19 внедряет технологию Event Replay. С помощью маленького скрипта в <head>, Angular «записывает» все действия пользователя (клики, ввод текста), которые произошли до гидратации. Как только гидратация завершается, Angular «проигрывает» эти события заново. Клик пользователя не теряется, товар добавляется в корзину.

    Чтобы включить эту функцию, нужно добавить конфигурацию:

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

    Zoneless Change Detection: Жизнь без Zone.js

    Теперь перейдем к производительности во время работы приложения. Долгое время «магией» Angular была библиотека Zone.js.

    Что такое Zone.js?

    Zone.js — это библиотека, которая «патчит» (перехватывает) все асинхронные API браузера: setTimeout, setInterval, Promise, addEventListener и другие. Зачем? Чтобы знать, когда могло что-то измениться.

    Логика была такой: «Если сработал таймер или пришел ответ от сервера, возможно, данные в компоненте изменились. Давайте запустим проверку изменений (Change Detection) во всем дереве компонентов».

    Недостатки Zone.js:

  • Размер: Библиотека весит около 10-15 КБ (gzipped), что немало для мобильных устройств.
  • Оверхед: Перехват всех событий имеет свою цену производительности.
  • Лишние проверки: Часто событие происходит, но данные не меняются. Zone.js все равно запускает проверку Angular.
  • Сложность отладки: Стек вызовов (Stack Trace) с Zone.js выглядит пугающе огромным.
  • Эра Zoneless

    С появлением Signals (которые мы изучили во второй статье), Angular получил возможность точно знать, где и что изменилось. Если вы обновили сигнал count, Angular знает, что нужно обновить только то место в шаблоне, где используется count().

    Ему больше не нужно гадать и полагаться на Zone.js. Это открывает путь к Zoneless приложениям.

    Как включить Zoneless в Angular 19

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

  • Удалить zone.js из polyfills в angular.json.
  • Включить провайдер в app.config.ts:
  • Что меняется для разработчика?

    В 99% случаев — ничего, если вы используете Signals и AsyncPipe. Angular сам перерисует интерфейс при изменении сигналов.

    Однако, если вы меняете обычную переменную (не сигнал) внутри setTimeout или промиса, Angular не узнает об этом без Zone.js. Вам придется либо перевести переменную на Signal (рекомендуется), либо вручную вызвать ChangeDetectorRef.markForCheck().

    Преимущества Zoneless: * Уменьшение бандла: Минус тяжелая библиотека. * Быстрый старт: Браузеру не нужно инициализировать зоны. * Точечные обновления: Рендеринг происходит только там, где реально изменились данные.

    !Концептуальное различие между глобальным перехватом событий через Zone.js и точечной реактивностью сигналов.

    Заключение

    Angular 19 предоставляет мощнейший инструментарий для создания высокопроизводительных приложений:

  • SSR обеспечивает мгновенное отображение контента и отличное SEO.
  • Non-destructive Hydration делает переход от статики к интерактивности плавным и эффективным.
  • Event Replay гарантирует, что ни один клик пользователя не будет потерян во время загрузки.
  • Zoneless архитектура на основе Сигналов убирает лишние абстракции, уменьшает размер приложения и повышает скорость рендеринга.
  • Комбинируя эти технологии, вы создаете веб-приложения, которые не просто работают, а «летают», обеспечивая лучший пользовательский опыт (UX).

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