Skip to content

Routing в Angular

Angular Router позволяет строить одностраничные приложения (SPA) с навигацией между представлениями без перезагрузки страницы.

1. Подключение Router

typescript
import { RouterModule, Routes } from '@angular/router';

const routes: Routes = [
  { path: '', component: HomeComponent },
  { path: 'about', component: AboutComponent },
  { path: 'contact', component: ContactComponent },
  { path: '**', component: NotFoundComponent }  // wildcard — 404
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule {}

Standalone-подход (Angular 14+)

typescript
// app.routes.ts
export const routes: Routes = [
  { path: '', component: HomeComponent },
  { path: 'about', component: AboutComponent },
];

// main.ts
import { provideRouter } from '@angular/router';
import { routes } from './app/app.routes';

bootstrapApplication(AppComponent, {
  providers: [provideRouter(routes)]
});

2. RouterOutlet

<router-outlet> — точка вставки, куда Router рендерит компонент текущего маршрута.

html
<!-- app.component.html -->
<nav>
  <a routerLink="/" routerLinkActive="active" [routerLinkActiveOptions]="{ exact: true }">
    Главная
  </a>
  <a routerLink="/about" routerLinkActive="active">О нас</a>
  <a routerLink="/contact" routerLinkActive="active">Контакты</a>
</nav>

<router-outlet></router-outlet>

Именованные outlet

html
<router-outlet></router-outlet>
<router-outlet name="sidebar"></router-outlet>
typescript
const routes: Routes = [
  {
    path: 'dashboard',
    component: DashboardComponent,
    children: [
      { path: 'stats', component: StatsComponent, outlet: 'sidebar' }
    ]
  }
];

3. Навигация

html
<!-- Статическая ссылка -->
<a routerLink="/products">Товары</a>

<!-- Динамическая ссылка с параметрами -->
<a [routerLink]="['/products', product.id]">{{ product.name }}</a>

<!-- С query parameters -->
<a [routerLink]="['/products']" [queryParams]="{ sort: 'price', page: 1 }">
  Товары по цене
</a>

<!-- С фрагментом -->
<a [routerLink]="['/about']" fragment="team">Наша команда</a>
<!-- Результат: /about#team -->

routerLinkActive

html
<!-- Класс 'active' добавляется когда маршрут совпадает -->
<a routerLink="/home" routerLinkActive="active">Главная</a>

<!-- Точное совпадение (без этого '/' будет active для всех маршрутов) -->
<a routerLink="/"
   routerLinkActive="active"
   [routerLinkActiveOptions]="{ exact: true }">
  Главная
</a>

<!-- Несколько CSS-классов -->
<a routerLink="/admin" routerLinkActive="active highlighted">Админ</a>

router.navigate (программная навигация)

typescript
import { Router } from '@angular/router';

@Component({ /* ... */ })
export class ProductComponent {
  constructor(private router: Router) {}

  goToProducts() {
    this.router.navigate(['/products']);
  }

  goToProduct(id: number) {
    this.router.navigate(['/products', id]);
  }

  goWithQuery() {
    this.router.navigate(['/products'], {
      queryParams: { sort: 'name', order: 'asc' },
      fragment: 'list'
    });
  }

  // Относительная навигация
  goToChild() {
    this.router.navigate(['details'], { relativeTo: this.route });
  }

  // Замена текущей записи в history (без добавления в стек)
  goReplace() {
    this.router.navigate(['/login'], { replaceUrl: true });
  }
}

4. Route Parameters

Параметры маршрута (path params)

typescript
const routes: Routes = [
  { path: 'products/:id', component: ProductDetailComponent },
  { path: 'users/:userId/posts/:postId', component: PostComponent }
];

Чтение параметров

typescript
import { ActivatedRoute } from '@angular/router';

@Component({ /* ... */ })
export class ProductDetailComponent implements OnInit {
  constructor(private route: ActivatedRoute) {}

  ngOnInit() {
    // Способ 1: snapshot (одноразовое чтение)
    const id = this.route.snapshot.paramMap.get('id');
    console.log('Product ID:', id);

    // Способ 2: Observable (реактивное — обновляется при смене параметра)
    this.route.paramMap.subscribe(params => {
      const id = params.get('id');
      this.loadProduct(Number(id));
    });
  }

  loadProduct(id: number) {
    // загрузка продукта
  }
}

Query Parameters

typescript
// URL: /products?category=electronics&page=2

@Component({ /* ... */ })
export class ProductListComponent implements OnInit {
  constructor(private route: ActivatedRoute) {}

  ngOnInit() {
    // Snapshot
    const category = this.route.snapshot.queryParamMap.get('category');

    // Observable
    this.route.queryParamMap.subscribe(params => {
      const category = params.get('category');
      const page = Number(params.get('page')) || 1;
      this.loadProducts(category, page);
    });
  }
}

Передача данных через state

typescript
// Отправка
this.router.navigate(['/result'], {
  state: { data: { score: 95, passed: true } }
});

// Получение
const navigation = this.router.getCurrentNavigation();
const state = navigation?.extras.state as { data: any };
// Или через window.history
const state2 = history.state;

5. Route Data

Статические данные, прикреплённые к маршруту:

typescript
const routes: Routes = [
  {
    path: 'admin',
    component: AdminComponent,
    data: { title: 'Панель администратора', role: 'admin' }
  }
];
typescript
@Component({ /* ... */ })
export class AdminComponent implements OnInit {
  constructor(private route: ActivatedRoute) {}

  ngOnInit() {
    const title = this.route.snapshot.data['title'];
    console.log(title); // 'Панель администратора'
  }
}

6. Дочерние маршруты (Children)

typescript
const routes: Routes = [
  {
    path: 'admin',
    component: AdminLayoutComponent,
    children: [
      { path: '', redirectTo: 'dashboard', pathMatch: 'full' },
      { path: 'dashboard', component: DashboardComponent },
      { path: 'users', component: UsersComponent },
      { path: 'users/:id', component: UserDetailComponent },
      { path: 'settings', component: SettingsComponent }
    ]
  }
];
html
<!-- admin-layout.component.html -->
<div class="admin-layout">
  <aside>
    <nav>
      <a routerLink="dashboard" routerLinkActive="active">Дашборд</a>
      <a routerLink="users" routerLinkActive="active">Пользователи</a>
      <a routerLink="settings" routerLinkActive="active">Настройки</a>
    </nav>
  </aside>

  <main>
    <router-outlet></router-outlet>
  </main>
</div>

7. Redirects

typescript
const routes: Routes = [
  { path: '', redirectTo: '/home', pathMatch: 'full' },
  { path: 'home', component: HomeComponent },
  { path: 'old-page', redirectTo: '/new-page' },
  { path: '**', redirectTo: '/home' }  // или component: NotFoundComponent
];

pathMatch

ЗначениеОписание
'full'URL должен полностью совпасть с path
'prefix'URL начинается с path (по умолчанию)

8. History и навигация назад

typescript
import { Location } from '@angular/common';

@Component({ /* ... */ })
export class DetailComponent {
  constructor(private location: Location) {}

  goBack() {
    this.location.back();
  }

  goForward() {
    this.location.forward();
  }

  // Изменить URL без навигации
  replaceUrl() {
    this.location.replaceState('/new-url');
  }
}

9. Router Events

typescript
import { Router, NavigationStart, NavigationEnd, NavigationError } from '@angular/router';

@Component({ /* ... */ })
export class AppComponent implements OnInit {
  isLoading = false;

  constructor(private router: Router) {}

  ngOnInit() {
    this.router.events.subscribe(event => {
      if (event instanceof NavigationStart) {
        this.isLoading = true;
      }
      if (event instanceof NavigationEnd || event instanceof NavigationError) {
        this.isLoading = false;
      }
    });
  }
}

Основные события

СобытиеКогда
NavigationStartНачало навигации
RouteConfigLoadStartНачало lazy loading модуля
RouteConfigLoadEndМодуль загружен
RoutesRecognizedМаршрут распознан
GuardsCheckStartНачало проверки guards
GuardsCheckEndGuards проверены
ResolveStartНачало resolve
ResolveEndResolve завершён
NavigationEndНавигация завершена
NavigationCancelНавигация отменена
NavigationErrorОшибка навигации

Сводная таблица

ФункцияСинтаксис
Ссылка в шаблонеrouterLink="/path"
Динамическая ссылка[routerLink]="['/path', id]"
Active классrouterLinkActive="class"
Программная навигацияrouter.navigate(['/path'])
Path параметр:id в пути, paramMap.get('id')
Query параметрqueryParams, queryParamMap.get('key')
Назадlocation.back()
Точка вставки<router-outlet>
Wildcardpath: '**'