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

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

1. Введение в экосистему Angular: установка, структура проекта и создание первых компонентов

Введение в экосистему Angular: установка, структура проекта и создание первых компонентов

Добро пожаловать в курс «Разработка современных веб-приложений на Angular». Мы начинаем наше путешествие в мир одного из самых мощных и востребованных фреймворков для создания веб-приложений. Если вы когда-либо задумывались, как создаются сложные корпоративные системы, динамичные панели управления или быстрые одностраничные приложения (SPA), то вы попали по адресу.

В этой первой статье мы не просто напишем «Hello World». Мы разберем фундамент, на котором строится Angular, настроим профессиональное рабочее окружение и создадим свои первые архитектурные блоки.

Что такое Angular и почему он особенный?

Angular — это платформа и фреймворк для создания клиентских приложений на HTML и TypeScript. Он разрабатывается и поддерживается компанией Google. Важно понимать различие: это не просто библиотека (как, например, ранние версии React), которая решает одну задачу отрисовки интерфейса. Angular — это платформа.

Это означает, что «из коробки» вы получаете всё необходимое для разработки:

* Мощный инструмент командной строки (CLI) * Систему маршрутизации (Router) * Инструменты для работы с формами * Клиент для HTTP-запросов * Систему тестирования

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

Подготовка рабочего окружения

Прежде чем мы сможем создать наше первое приложение, нам нужно подготовить почву. Angular требует наличия Node.js.

Шаг 1: Установка Node.js

Node.js — это среда выполнения JavaScript вне браузера. Angular использует её для работы своих инструментов сборки и разработки. Вам необходимо скачать и установить LTS (Long Term Support) версию с официального сайта Node.js. Она наиболее стабильна.

Чтобы проверить, что установка прошла успешно, откройте терминал (командную строку) и введите:

Вы должны увидеть версию Node.js (например, v18.13.0 или выше).

Шаг 2: Установка Angular CLI

Angular CLI (Command Line Interface) — это ваш главный помощник. Это утилита, которая позволяет создавать проекты, генерировать компоненты, запускать сервер разработки и собирать приложение для продакшена одной строкой кода.

Для установки CLI выполните в терминале следующую команду:

Здесь npm (Node Package Manager) — это менеджер пакетов, который устанавливается вместе с Node.js. Флаг -g означает, что мы устанавливаем инструмент глобально на весь компьютер.

Проверим установку:

Если вы видите красивый логотип Angular и список версий пакетов — поздравляю, вы готовы к работе!

Создание первого проекта

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

CLI задаст вам несколько вопросов. Для начала выберем следующие варианты:

  • Which stylesheet format would you like to use? — Выберите CSS (или SCSS, если вы с ним знакомы, но в курсе мы будем ориентироваться на стандартные стили).
  • Do you want to enable Server-Side Rendering (SSR) and Static Site Generation (SSG/Prerendering)? — Для начала выберите No (N). Мы сосредоточимся на клиентской части.
  • После этого начнется процесс установки зависимостей. Это может занять пару минут, так как npm скачивает необходимые библиотеки в папку node_modules.

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

    Откройте созданную папку my-first-app в вашем любимом редакторе кода (рекомендуется VS Code). Давайте разберем, что находится внутри. Понимание структуры критически важно для дальнейшей работы.

    Корневые файлы

    * angular.json: Главный файл конфигурации. Здесь описано, как собирать проект, какие файлы стилей подключать глобально и другие настройки сборщика. * package.json: Список всех библиотек (зависимостей), которые использует ваш проект, а также скрипты для запуска (например, start, build). * tsconfig.json: Настройки компилятора TypeScript. Angular написан на TypeScript, и ваш код тоже будет на нем.

    Папка src

    Здесь живет исходный код вашего приложения.

    * index.html: Единственная HTML-страница вашего приложения. Поскольку мы делаем SPA (Single Page Application), пользователь физически находится на этой странице, а Angular подменяет содержимое внутри неё. * main.ts: Точка входа в приложение. Этот скрипт запускает Angular и загружает главный модуль или компонент. * styles.css: Глобальные стили, которые применяются ко всему приложению.

    Папка src/app

    Это сердце вашего приложения. Здесь будут лежать все ваши компоненты, сервисы и модули.

    Компоненты: Строительные блоки

    Главная концепция Angular — это компоненты. Всё, что вы видите на экране в Angular-приложении, является компонентом.

    Компонент состоит из трех основных частей:

  • HTML-шаблон (Template): Отвечает за то, как выглядит компонент (структура).
  • Класс TypeScript (Class): Отвечает за то, как работает компонент (логика, данные).
  • Стили (Styles): Отвечает за оформление (красота).
  • !Схематичное изображение анатомии компонента Angular, показывающее связь между логикой, шаблоном и стилями.

    Давайте посмотрим на файл app.component.ts. Это главный (корневой) компонент, который создается автоматически.

    Разберем этот код:

    @Component: Это декоратор*. Он говорит Angular, что следующий за ним класс — это не просто класс, а компонент. Внутри декоратора мы передаем объект с метаданными. * selector: 'app-root': Это имя тега, по которому мы будем обращаться к этому компоненту в HTML. Если вы заглянете в index.html, вы увидите там <app-root></app-root>. * templateUrl: Ссылка на файл с HTML-разметкой этого компонента. * styleUrl: Ссылка на файл со стилями. * standalone: true: Начиная с последних версий Angular, компоненты по умолчанию являются автономными (standalone), что упрощает структуру приложения, избавляя от необходимости использовать NgModules для каждого чиха. * class AppComponent: Это обычный класс на TypeScript, где мы пишем логику. Сейчас там есть одно свойство title.

    Создание нового компонента

    Давайте создадим свой собственный компонент. Допустим, мы хотим сделать «шапку» (Header) для нашего сайта. Вручную создавать файлы долго и чревато ошибками, поэтому мы используем CLI.

    Откройте терминал (убедитесь, что вы находитесь в папке проекта) и введите:

    Или сокращенно:

    CLI создаст папку src/app/header с четырьмя файлами:

  • header.component.html (шаблон)
  • header.component.ts (логика)
  • header.component.css (стили)
  • header.component.spec.ts (файл для тестов, пока его игнорируем)
  • Использование компонента

    Теперь у нас есть компонент Header, но он нигде не отображается. Чтобы его увидеть, нужно добавить его в родительский компонент — AppComponent.

  • Откройте header.component.ts и посмотрите на свойство selector. Скорее всего, это 'app-header'.
  • Откройте app.component.ts. Так как мы используем Standalone компоненты, нам нужно явно импортировать наш новый компонент.
  • Теперь откройте app.component.html. Удалите всё содержимое (это шаблон по умолчанию от Angular) и добавьте:
  • Запустите сервер разработки командой:
  • Откройте браузер по адресу http://localhost:4200. Вы увидите текст «header works!» (стандартный текст нового компонента) и наш заголовок.

    Интерполяция: Связь данных и шаблона

    Одной из самых сильных сторон Angular является Data Binding (привязка данных). Самый простой вид привязки — это интерполяция.

    Интерполяция позволяет вывести значение переменной из класса (TypeScript) прямо в HTML. Для этого используются двойные фигурные скобки {{ }}.

    Давайте изменим header.component.ts:

    А теперь используем эту переменную в header.component.html:

    Сохраните файлы. Браузер автоматически обновит страницу, и вы увидите «Мой Супер Магазин». Если вы измените значение переменной siteName в классе, Angular мгновенно обновит текст на странице. Вам не нужно писать код для поиска элемента в DOM и обновления его innerHTML — Angular берет это на себя.

    Заключение

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

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

    2. Шаблоны и данные: использование директив, пайпов и механизмов привязки данных (Data Binding)

    Шаблоны и данные: использование директив, пайпов и механизмов привязки данных (Data Binding)

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

    В этой лекции мы вдохнем жизнь в наши компоненты. Мы разберем, как Angular управляет потоком данных между логикой (TypeScript) и отображением (HTML), научимся управлять структурой DOM-дерева с помощью нового синтаксиса Control Flow и узнаем, как красиво форматировать данные с помощью пайпов.

    Механизмы привязки данных (Data Binding)

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

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

    1. Интерполяция (Interpolation)

    С этим мы уже знакомы. Интерполяция используется для вывода текстовых значений из класса в HTML. Синтаксис: двойные фигурные скобки {{ value }}.

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

    2. Привязка свойств (Property Binding)

    Интерполяция работает только с текстом. Но что, если нам нужно динамически изменить атрибут HTML-тега, например, ссылку на картинку src или состояние кнопки disabled?

    Для этого используется синтаксис квадратных скобок [].

    Важное отличие: В HTML есть атрибуты, а в DOM (Document Object Model) есть свойства. Angular через [] работает именно со свойствами DOM-элементов. Это позволяет менять значения, которые не имеют прямого атрибута в HTML (например, textContent или innerHTML).

    > Если вы хотите передать строку как есть, без вычислений, кавычки не нужны. Но если вы используете [], Angular ожидает выражение (переменную) внутри кавычек.

    3. Привязка событий (Event Binding)

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

    Внутри кавычек мы указываем метод класса, который нужно выполнить при наступлении события. Объект события можно получить, передав специальную переменную event)"> typescript // app.component.ts import { Component } from '@angular/core'; import { FormsModule } from '@angular/forms'; // Импорт обязателен

    @Component({ selector: 'app-root', standalone: true, imports: [FormsModule], // Добавляем в imports template: <input [(ngModel)]="userName"> <p>Привет, {{ userName }}!</p> }) export class AppComponent { userName = 'Студент'; } typescript export class UserComponent { isLoggedIn = false; } html @if (isLoggedIn) { <button>Выйти</button> } @else { <button>Войти</button> } typescript export class ProductListComponent { products = [ { id: 1, name: 'Ноутбук', price: 50000 }, { id: 2, name: 'Мышка', price: 1500 }, { id: 3, name: 'Клавиатура', price: 3000 } ]; } html <ul> @for (product of products; track product.id) { <li> {{ product.name }} — {{ product.price }} </li> } @empty { <li>Список товаров пуст</li> } </ul> css / component.css / .active { color: green; font-weight: bold; } .error { color: red; } html <!-- Если isActive == true, добавится класс 'active' --> <div [ngClass]="{ 'active': isActive, 'error': hasError }"> Статус системы </div> html <div [ngStyle]="{ 'font-size': isLarge ? '24px' : '12px' }"> Динамический текст </div> typescript export class ProductComponent { product = { name: 'iphone 15', price: 999.99, releaseDate: new Date(2023, 8, 22) }; } html <div> <!-- Выведет: IPHONE 15 --> <h2>{{ product.name | uppercase }}</h2> <!-- Выведет: index) { <li [class.completed]="task.completed" (click)="toggleTask(i)" > <!-- Interpolation и Pipes --> <span class="task-name">{{ task.name | uppercase }}</span> <span class="task-date">{{ task.date | date:'shortTime' }}</span> </li> } @empty { <p>Задач пока нет. Отдыхайте!</p> } </ul> </div> css .completed { text-decoration: line-through; color: gray; } `

    В этом примере мы использовали:

  • [(ngModel)] для связи поля ввода с переменной newTask.
  • (click) и (keydown.enter) для обработки событий добавления.
  • [disabled] для блокировки кнопки, если поле пустое.
  • @for для вывода списка.
  • [class.completed] (сокращенная запись ngClass) для зачеркивания выполненных задач.
  • Пайпы uppercase и date для форматирования.
  • Заключение

    Сегодня мы научились управлять данными и отображением в Angular. Мы освоили четыре типа привязки данных, познакомились с современным синтаксисом Control Flow (@if, @for`) и научились использовать пайпы для трансформации значений.

    Эти инструменты делают ваши шаблоны динамичными и отзывчивыми. В следующей статье мы перейдем к более сложной архитектурной теме — Сервисы и Внедрение зависимостей (Dependency Injection). Мы узнаем, как правильно хранить данные и делить логику между компонентами.

    3. Архитектура сервисов: Dependency Injection и взаимодействие с API через HTTP Client

    Архитектура сервисов: Dependency Injection и взаимодействие с API через HTTP Client

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

    Представьте, что у вас есть интернет-магазин. Список товаров нужен в каталоге, в корзине, в поиске и в админ-панели. Если мы будем копировать массив товаров в каждый компонент, то при изменении цены нам придется править код в четырех местах. А если данные приходят с сервера? Не будем же мы делать четыре одинаковых запроса?

    Здесь на сцену выходят Сервисы и паттерн Dependency Injection (DI).

    Что такое Сервис?

    Сервис в Angular — это обычный класс, который содержит бизнес-логику и данные, не связанные напрямую с отображением (UI). Компоненты должны отвечать только за то, как показать данные, а сервисы — за то, как их получить, сохранить или обработать.

    Главные задачи сервисов:

  • Хранение состояния: Обмен данными между компонентами, которые не связаны напрямую.
  • Взаимодействие с API: Отправка запросов на сервер.
  • Бизнес-логика: Сложные вычисления, валидация, логирование.
  • Магия Dependency Injection (DI)

    Dependency Injection (Внедрение зависимостей) — это сердце архитектуры Angular. Это паттерн проектирования, при котором классы не создают свои зависимости сами, а получают их извне.

    Аналогия из жизни

    Представьте, что вы хотите построить дом.

    * Без DI: Вы сами изготавливаете кирпичи, сами пилите доски, сами куете гвозди. Это долго, сложно, и если вы захотите заменить кирпич на газоблок, вам придется переучиваться. * С DI: Вы нанимаете прораба (Angular Injector). Вы говорите ему: «Мне нужен дом, для этого мне понадобятся кирпичи и доски». Прораб сам находит поставщиков, закупает материалы и привозит их вам на стройку. Вы просто используете готовые материалы.

    !Диаграмма показывает, как Angular Injector предоставляет экземпляры сервисов компонентам.

    Создание сервиса

    Давайте вынесем логику нашего списка задач в сервис. Используем Angular CLI:

    Angular создаст файл src/app/services/todo.service.ts:

    Обратите внимание на декоратор @Injectable. Он помечает класс как участника системы DI.

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

    Добавим логику в сервис:

    Внедрение сервиса в компонент

    Теперь научим компонент использовать этот сервис. Для этого мы запрашиваем его в конструкторе компонента.

    Когда Angular создает TodoListComponent, он видит, что в конструкторе нужен TodoService. Он проверяет, есть ли уже созданный экземпляр сервиса. Если нет — создает его. Если есть — отдает готовый.

    Взаимодействие с сервером: HTTP Client

    В реальном мире данные не хранятся в массиве в памяти, они приходят с бэкенда. Для общения с сервером в Angular есть мощный инструмент — HttpClient.

    Настройка HttpClient

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

    Observables и RxJS

    Здесь нам придется коснуться темы, которая часто пугает новичков — RxJS (Reactive Extensions for JavaScript).

    Методы HttpClient (get, post и др.) возвращают не данные и не Promise, а Observable (Наблюдаемый объект).

    Представьте разницу: Promise — это заказ пиццы. Вы сделали заказ, ждете, и вам один раз* привозят пиццу (или говорят, что печь сломалась). * Observable — это подписка на журнал. Вы подписываетесь, и вам могут присылать журналы много раз в течение времени. Или не прислать ни одного. Или прислать ошибку.

    В случае с HTTP-запросом поток обычно выпускает одно значение (ответ сервера) и завершается. Но Observable позволяет нам легко отменять запросы, делать повторные попытки при ошибках и трансформировать данные на лету.

    Создание API-сервиса

    Давайте создадим сервис для получения пользователей с бесплатного тестового API JSONPlaceholder.

    Получение данных в компоненте

    Чтобы получить данные из Observable, на него нужно подписаться (метод .subscribe()).

    Метод ngOnInit — это идеальное место для начальной загрузки данных. Мы вызываем subscribe, передавая объект с двумя функциями: next (успех) и error (ошибка).

    Async Pipe: Лучшая практика

    Ручная подписка (.subscribe()) имеет недостаток: если компонент будет уничтожен (пользователь уйдет со страницы), подписка может остаться висеть в памяти, что приведет к утечкам ресурсов. Нам нужно не забывать делать unsubscribe.

    Angular предлагает элегантное решение — Async Pipe (| async). Он сам подписывается на Observable в шаблоне и сам отписывается, когда компонент уничтожается.

    Перепишем компонент:

    ``typescript import { Component, inject } from '@angular/core'; import { UserService, User } from '../services/user.service'; import { AsyncPipe } from '@angular/common'; // Обязательно импортируем import { Observable } from 'rxjs';

    @Component({ selector: 'app-user-list', standalone: true, imports: [AsyncPipe], // Добавляем в imports template: <h2>Список пользователей (Async Pipe)</h2> <!-- Используем | async; as users) { <ul> @for (user of users; track user.id) { <li>{{ user.name }}</li> } </ul> } @else { <p>Загрузка...</p> } }) export class UserListComponent { private userService = inject(UserService); // Мы не храним данные, мы храним ПОТОК данных users, возьми из него данные, когда они придут».

    Заключение

    Сегодня мы разобрали фундаментальные принципы архитектуры Angular:

  • Сервисы позволяют вынести бизнес-логику из компонентов.
  • Dependency Injection автоматически управляет созданием и доставкой этих сервисов.
  • HttpClient обеспечивает связь с внешним миром.
  • Async Pipe упрощает работу с асинхронными данными в шаблонах.
  • В следующей статье мы углубимся в работу с формами, научимся валидировать пользовательский ввод и отправлять данные обратно на сервер.

    4. Маршрутизация и навигация: создание многостраничного опыта в SPA и защита маршрутов

    Маршрутизация и навигация: создание многостраничного опыта в SPA и защита маршрутов

    В предыдущих статьях мы прошли большой путь: от установки Angular до взаимодействия с сервером через HttpClient. Наше приложение уже умеет загружать и отображать данные. Однако, до сих пор всё действие происходило на одной «странице». В реальном мире приложения состоят из множества разделов: «Главная», «Каталог», «Профиль», «Корзина».

    Сегодня мы превратим наше приложение в полноценную систему с навигацией. Мы разберем, как работает маршрутизация в Single Page Application (SPA), научимся передавать параметры через адресную строку и защищать приватные разделы от неавторизованных пользователей.

    Иллюзия навигации в SPA

    Традиционные сайты работают просто: вы кликаете по ссылке, браузер отправляет запрос на сервер, сервер собирает новую HTML-страницу и отправляет её обратно. Экран моргает, страница перезагружается.

    В Angular (и других SPA-фреймворках) всё иначе. У нас есть только один файл index.html. Когда пользователь кликает по ссылке «Контакты», мы не идем на сервер за новой страницей. Angular перехватывает это событие, смотрит на URL и подменяет одни компоненты на другие прямо в браузере. Это происходит мгновенно и без перезагрузки.

    За эту магию отвечает Angular Router.

    !Схема работы маршрутизатора: подмена компонентов на основе URL

    Настройка маршрутов

    В современных версиях Angular (с использованием Standalone компонентов) маршруты настраиваются в файле app.routes.ts. Этот файл содержит массив объектов типа Routes.

    Каждый маршрут — это объект, который говорит: «Если в адресной строке написано это, покажи тот компонент».

    Предположим, у нас есть три компонента: HomeComponent, AboutComponent и ContactComponent. Настроим навигацию между ними.

    Разберем свойства: * path: Часть URL после домена. Для главной страницы это пустая строка ''. * component: Класс компонента, который нужно отобразить.

    Подключение маршрутизатора

    Чтобы эти маршруты заработали, их нужно передать в конфигурацию приложения. В файле app.config.ts мы используем функцию provideRouter.

    Отображение маршрутов: RouterOutlet

    Мы настроили правила, но не сказали Angular, где именно рисовать эти компоненты. Для этого используется специальная директива <router-outlet>.

    Обычно она размещается в главном компоненте AppComponent. Представьте, что это рамка для картины, содержимое которой меняется динамически.

    routerLink vs href

    Обратите внимание: мы используем атрибут routerLink, а не стандартный href.

    * href: Заставляет браузер выполнить полную перезагрузку страницы. Состояние приложения (переменные, заполненные формы) сбрасывается. В SPA это плохая практика. * routerLink: Директива Angular, которая меняет URL через History API и сообщает маршрутизатору, что нужно обновить вид. Перезагрузки не происходит.

    routerLinkActive

    Эта директива автоматически добавляет указанный CSS-класс (в нашем примере active) к ссылке, если её маршрут совпадает с текущим URL. Это идеально подходит для подсветки активного пункта меню.

    > Для корневого маршрута (/) часто нужно добавить параметр [routerLinkActiveOptions]="{exact: true}", иначе ссылка «Главная» будет подсвечиваться всегда, так как любой путь начинается со слэша.

    Динамические маршруты и параметры

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

    В конфигурации маршрутов параметр обозначается двоеточием :.

    Теперь URL вида /user/42 или /user/100 будут обрабатываться компонентом UserProfileComponent.

    Получение параметров (Input Binding)

    Начиная с Angular 16, появился элегантный способ получать параметры маршрута — через @Input компонента. Это называется Component Input Binding.

    Сначала нужно включить эту функцию в app.config.ts:

    Теперь в компоненте мы просто создаем входное свойство с тем же именем, что и параметр в маршруте (id).

    Это значительно упрощает код по сравнению со старым способом через сервис ActivatedRoute.

    Программная навигация

    Иногда перенаправление должно происходить не по клику, а в результате выполнения кода (например, после успешного входа в систему или сохранения формы). Для этого используется сервис Router.

    Обработка несуществующих страниц (404)

    Что будет, если пользователь введет в адресную строку абракадабру? Приложение сломается или покажет пустой экран. Чтобы этого избежать, нужно настроить маршрут-ловушку (Wildcard route).

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

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

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

    В любом серьезном приложении есть страницы, доступные только определенным пользователям (например, «Личный кабинет» или «Админка»). Angular позволяет запретить переход на маршрут с помощью Guards (Защитников).

    В современном Angular используются функциональные гарды (CanActivateFn).

    Создадим простой гард, который проверяет, авторизован ли пользователь. Допустим, у нас есть AuthService с методом isLoggedIn().

    Теперь применим этот гард к маршруту в app.routes.ts:

    Если authGuard вернет false или перенаправление, пользователь не сможет попасть на страницу /admin, даже если введет адрес вручную.

    Заключение

    Маршрутизация превращает набор разрозненных компонентов в цельное приложение. Сегодня мы узнали:

  • Как настраивать таблицу маршрутов в app.routes.ts.
  • Зачем нужен <router-outlet> и почему routerLink лучше href.
  • Как принимать параметры из URL через @Input.
  • Как защищать секретные страницы с помощью функциональных гардов.
  • Теперь ваше приложение похоже на настоящий веб-сайт. В следующей статье мы займемся одной из самых сложных, но интересных тем — Реактивные формы и валидация данных.

    5. Управление вводом и потоками данных: реактивные формы, валидация и основы RxJS

    Управление вводом и потоками данных: реактивные формы, валидация и основы RxJS

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

    В этой статье мы разберем, как Angular обрабатывает пользовательский ввод на профессиональном уровне. Мы откажемся от простых двусторонних привязок [(ngModel)] в пользу мощных Реактивных форм (Reactive Forms), научимся валидировать данные и погрузимся в мир RxJS — библиотеки, которая превращает поток данных в управляемый конвейер.

    Два подхода к формам в Angular

    Angular предлагает два способа работы с формами:

  • Template-driven forms (Формы на основе шаблона): Логика сосредоточена в HTML. Используется ngModel. Подходит для очень простых форм (например, один инпут подписки на рассылку).
  • Reactive forms (Реактивные формы): Логика сосредоточена в классе компонента (TypeScript). Модель формы создается явно в коде. Это дает полный контроль, упрощает тестирование и позволяет строить сложные сценарии валидации.
  • В современной разработке стандартом де-факто являются Реактивные формы, поэтому мы сосредоточимся именно на них.

    Основы Reactive Forms

    Чтобы начать, нам нужно подключить модуль. Откройте app.component.ts (или файл вашего компонента) и добавьте ReactiveFormsModule в массив imports.

    Строительные блоки формы

    Реактивные формы строятся из трех основных классов:

    * FormControl: Управляет значением и статусом валидации одного поля ввода (атом). * FormGroup: Управляет группой контролов (молекула). Например, форма логина — это группа из двух контролов: email и пароль. * FormArray: Управляет динамическим списком контролов (например, список тегов или телефонов).

    !Схематичное изображение структуры формы: FormGroup объединяет отдельные FormControl.

    Создание первой формы

    Давайте создадим форму входа. В классе компонента мы создаем экземпляр FormGroup.

    Теперь свяжем эту модель с HTML-шаблоном:

    Обратите внимание на директивы: * [formGroup]: Связывает тег <form> с переменной loginForm в классе. * formControlName: Связывает конкретный <input> с полем внутри группы по имени (email, password).

    Валидация данных

    Валидация в реактивных формах добавляется прямо при создании контролов. Angular предоставляет набор встроенных валидаторов в классе Validators.

    Изменим наш код:

    Второй аргумент FormControl — это массив валидаторов (или один валидатор).

    Отображение ошибок

    У каждого контрола есть свойства, позволяющие узнать его состояние: * .invalid: true, если есть ошибки валидации. * .touched: true, если пользователь «трогал» поле (фокусировался и ушел). * .dirty: true, если пользователь изменил значение.

    Давайте выведем сообщение об ошибке, если email некорректен и пользователь уже взаимодействовал с полем.

    Кнопку отправки можно заблокировать, пока вся форма не станет валидной:

    FormBuilder: Синтаксический сахар

    Создавать new FormGroup и new FormControl вручную бывает утомительно, особенно в больших формах. Angular предлагает сервис FormBuilder, который делает код чище.

    Результат тот же, но кода меньше.

    Основы RxJS: Реактивное программирование

    Теперь перейдем к самой интересной части. Вы могли заметить, что HttpClient возвращает Observable. У форм тоже есть свойство valueChanges, которое возвращает Observable. Что это такое?

    RxJS (Reactive Extensions for JavaScript) — это библиотека для работы с асинхронными потоками данных.

    Представьте себе конвейер на заводе:

  • Observable (Наблюдаемый объект) — это сам конвейер, по которому едут детали (данные).
  • Observer (Наблюдатель) — это рабочий в конце конвейера, который забирает готовые детали.
  • Subscription (Подписка) — это процесс включения конвейера. Пока вы не подпишетесь (.subscribe()), конвейер стоит и ничего не делает.
  • Operators (Операторы) — это роботы, стоящие вдоль конвейера. Они могут отбраковывать детали (filter), перекрашивать их (map) или задерживать (debounceTime).
  • !Иллюстрация потока данных в RxJS: от источника через операторы к подписчику.

    Зачем это нужно в формах?

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

    С помощью RxJS мы можем элегантно решить эту проблему.

    Практика: «Умный» поиск

    Допустим, у нас есть поле поиска:

    Мы хотим:

  • Слушать изменения текста.
  • Ждать 300 мс после того, как пользователь перестал печатать (чтобы не слать запросы на «а», «аб», «абв»).
  • Игнорировать, если текст не изменился (например, пользователь нажал пробел и стер его).
  • Отправлять запрос.
  • Реализуем это с помощью операторов RxJS:

    Разберем операторы: * pipe(): Метод, в который мы передаем список операторов для обработки потока. * filter: Пропускает данные дальше, только если условие истинно. * debounceTime(ms): Самый полезный оператор для ввода. Он «замораживает» поток. Если в течение указанного времени приходят новые данные, таймер сбрасывается. Данные пройдут дальше только тогда, когда поток «замолчит» на указанное время. * distinctUntilChanged: Сравнивает текущее значение с предыдущим. Если они равны, поток останавливается.

    Отписка от потоков

    Важное правило RxJS: всегда отписывайтесь от потоков, которые вы создали вручную через .subscribe(). Иначе возникнут утечки памяти.

    В Angular компонентах для этого используется метод жизненного цикла ngOnDestroy.

    > Совет: В современных версиях Angular (v16+) появился оператор takeUntilDestroyed, который делает отписку автоматической, но понимание ручной отписки всё ещё важно для собеседований и поддержки старого кода.

    Заключение

    Сегодня мы сделали огромный шаг вперед. Мы перешли от простых шаблонов к профессиональному управлению данными.

  • Reactive Forms дали нам контроль над валидацией и состоянием полей.
  • FormBuilder упростил создание форм.
  • RxJS позволил нам обрабатывать поток ввода пользователя как конвейер, отсеивая лишнее и оптимизируя запросы.
  • Эти навыки критически важны. Любое сложное приложение — это, по сути, набор форм и потоков данных. В следующей статье мы объединим всё, что узнали, и поговорим о Модульности и оптимизации: как делить приложение на части, использовать Lazy Loading и ускорять загрузку.