16. Маршрутизация: основы
🗺️ Маршрутизация в Angular: Основы
Заголовок раздела «🗺️ Маршрутизация в Angular: Основы»Маршрутизация (routing) — это система навигации между “страницами” в SPA (Single Page Application). Представь, что твоё приложение — это книга 📖, а роутер — это содержание: он знает, какую страницу открыть по адресу. При этом сама книга (браузер) не перезагружается — просто меняется содержимое!
📦 Настройка RouterModule и Routes
Заголовок раздела «📦 Настройка RouterModule и Routes»В Angular маршруты описываются массивом конфигурации. Каждый маршрут — объект с path и component:
import { Routes } from '@angular/router';import { HomeComponent } from './home/home.component';import { ProductsComponent } from './products/products.component';import { ProductDetailComponent } from './products/product-detail.component';import { AboutComponent } from './about/about.component';import { NotFoundComponent } from './not-found/not-found.component';
export const routes: Routes = [ { path: '', component: HomeComponent }, // / { path: 'products', component: ProductsComponent }, // /products { path: 'products/:id', component: ProductDetailComponent }, // /products/42 { path: 'about', component: AboutComponent }, // /about { path: '**', component: NotFoundComponent }, // любой другой путь = 404];Подключение в app.config.ts (Standalone, Angular 17+):
import { ApplicationConfig } from '@angular/core';import { provideRouter } from '@angular/router';import { routes } from './app.routes';
export const appConfig: ApplicationConfig = { providers: [ provideRouter(routes) // С дополнительными опциями: // provideRouter(routes, withDebugTracing(), withPreloading(PreloadAllModules)) ]};Или классически через NgModule:
@NgModule({ imports: [RouterModule.forRoot(routes)], exports: [RouterModule]})export class AppModule {}🪟 RouterOutlet: точка рендеринга
Заголовок раздела «🪟 RouterOutlet: точка рендеринга»<router-outlet> — это “дырка” в шаблоне, куда Angular вставляет активный компонент маршрута. Как слот в конструкторе Lego 🧱:
@Component({ selector: 'app-root', template: ` <nav> <a routerLink="/">Главная</a> <a routerLink="/products">Продукты</a> <a routerLink="/about">О нас</a> </nav>
<!-- Сюда Angular рендерит компонент активного маршрута --> <router-outlet />
<footer>© 2025 MyApp</footer> `, imports: [RouterOutlet, RouterLink]})export class AppComponent {}🔗 RouterLink: навигация без перезагрузки
Заголовок раздела «🔗 RouterLink: навигация без перезагрузки»RouterLink — директива для создания ссылок без перезагрузки страницы. Это не просто <a href>, это умная навигация Angular:
<!-- Простая строка --><a routerLink="/products">Продукты</a>
<!-- Массив сегментов (рекомендуется!) --><a [routerLink]="['/products']">Продукты</a><a [routerLink]="['/products', productId]">Товар</a><a [routerLink]="['/products', productId, 'reviews']">Отзывы</a>
<!-- С query params и fragment --><a [routerLink]="['/products']" [queryParams]="{ page: 2, sort: 'name' }" fragment="top"> Страница 2</a><!-- Результат: /products?page=2&sort=name#top -->
<!-- routerLinkActive — добавляет класс активной ссылке --><a routerLink="/products" routerLinkActive="active">Продукты</a>
<!-- Точное совпадение (не активируется на /products/1) --><a routerLink="/" routerLinkActive="active" [routerLinkActiveOptions]="{ exact: true }"> Главная</a>🚀 Программная навигация
Заголовок раздела «🚀 Программная навигация»Иногда нужно перейти на страницу из TypeScript кода (после сохранения формы, после логина и т.д.):
import { Router } from '@angular/router';
@Component({...})export class LoginComponent { constructor(private router: Router) {}
onLogin() { // Навигация с массивом сегментов this.router.navigate(['/dashboard']);
// С параметрами this.router.navigate(['/products', 42]);
// С query params this.router.navigate(['/products'], { queryParams: { category: 'phones', sort: 'price' } });
// По строке URL (менее гибко) this.router.navigateByUrl('/products?sort=name#top');
// Относительная навигация (от текущего маршрута) this.router.navigate(['../sibling'], { relativeTo: this.route }); }}📍 ActivatedRoute: читаем параметры маршрута
Заголовок раздела «📍 ActivatedRoute: читаем параметры маршрута»ActivatedRoute — это сервис, который содержит информацию о текущем маршруте: параметры, query params, данные и т.д.:
import { ActivatedRoute } from '@angular/router';
@Component({...})export class ProductDetailComponent implements OnInit { constructor(private route: ActivatedRoute) {}
ngOnInit() { // === Snapshot (статический — работает если компонент переиспользуется редко) === const id = this.route.snapshot.paramMap.get('id'); console.log('ID:', id); // '42'
// === Observable (реактивный — при навигации между /products/1 и /products/2) === this.route.paramMap.subscribe(params => { const id = params.get('id'); this.loadProduct(id!); });
// Современный подход с signal const params = this.route.snapshot.params; console.log(params['id']); // '42' }}🔍 Query Params: дополнительные параметры
Заголовок раздела «🔍 Query Params: дополнительные параметры»Query params идут после ? в URL: /products?page=2&sort=name. Они не обязательны и не влияют на маршрут:
// Чтение query params@Component({...})export class ProductsComponent implements OnInit { currentPage = 1; sortBy = 'name';
constructor(private route: ActivatedRoute, private router: Router) {}
ngOnInit() { // Snapshot const page = this.route.snapshot.queryParamMap.get('page') ?? '1'; this.currentPage = +page;
// Observable (обновляется при изменении URL) this.route.queryParamMap.subscribe(params => { this.currentPage = +(params.get('page') ?? '1'); this.sortBy = params.get('sort') ?? 'name'; this.loadProducts(); }); }
changePage(page: number) { this.router.navigate([], { queryParams: { page }, queryParamsHandling: 'merge' // сохраняем остальные params }); }}⚓ Fragment: якоря в URL
Заголовок раздела «⚓ Fragment: якоря в URL»Fragment — это часть URL после #. Используется для прокрутки к определённому месту на странице:
<!-- Навигация с fragment --><a [routerLink]="['/docs']" fragment="installation">Установка</a><!-- Результат: /docs#installation -->// Чтение fragmentthis.route.fragment.subscribe(fragment => { if (fragment) { const el = document.getElementById(fragment); el?.scrollIntoView({ behavior: 'smooth' }); }});🚦 Wildcard маршрут и редиректы
Заголовок раздела «🚦 Wildcard маршрут и редиректы»Порядок маршрутов важен! Angular проверяет их сверху вниз и берёт первое совпадение:
export const routes: Routes = [ // Редирект: / → /home { path: '', redirectTo: '/home', pathMatch: 'full' }, // ^^^^^^^^^ ОБЯЗАТЕЛЬНО для ''! // pathMatch: 'full' — совпадает только точный путь '' // pathMatch: 'prefix' (по умолчанию) — совпадает любой путь начинающийся с ''
{ path: 'home', component: HomeComponent }, { path: 'products', component: ProductsComponent }, { path: 'products/:id', component: ProductDetailComponent },
// Устаревшие URL → новые { path: 'old-products', redirectTo: '/products', pathMatch: 'full' },
// ⚠️ Wildcard ДОЛЖЕН быть последним! { path: '**', component: NotFoundComponent },];🏗️ Полный пример: SPA с несколькими страницами
Заголовок раздела «🏗️ Полный пример: SPA с несколькими страницами»export const routes: Routes = [ { path: '', redirectTo: '/home', pathMatch: 'full' }, { path: 'home', component: HomeComponent }, { path: 'products', component: ProductsComponent, title: 'Продукты' // <title> в браузере }, { path: 'products/:id', component: ProductDetailComponent, title: 'Детали товара' }, { path: 'about', component: AboutComponent }, { path: '**', component: NotFoundComponent },];
// product-detail.component.ts@Component({ template: ` <div *ngIf="product$ | async as product"> <h1>{{ product.name }}</h1> <p>{{ product.description }}</p> <button (click)="goBack()">← Назад</button> </div> `})export class ProductDetailComponent { product$ = this.route.paramMap.pipe( map(params => params.get('id')!), switchMap(id => this.productsService.getById(+id)) );
constructor( private route: ActivatedRoute, private router: Router, private productsService: ProductsService ) {}
goBack() { this.router.navigate(['/products']); // Или: this.location.back(); }}Практика
Заголовок раздела «Практика»Попробуйте концепцию в интерактивном редакторе: