JavaRush /Курсы /Модуль 4: Node.js, Next.js и Angular /Динамические параметры маршрута:

Динамические параметры маршрута: /user/:id, ActivatedRoute

Модуль 4: Node.js, Next.js и Angular
18 уровень , 5 лекция
Открыта

1. Введение

Если бы вы писали интернет-магазин и хотели отображать страницу товара по адресу /product/123, где 123 — идентификатор товара, вам понадобился бы динамический маршрут. То же самое — для пользователей, статей, заказов и всего, что имеет уникальный идентификатор.

В Angular такой маршрут задаётся с помощью синтаксиса /user/:id, где :id — это параметр маршрута (или route parameter). Он работает как переменная: Angular автоматически подставляет в неё значение из URL.

Пример:

  • /user/5 — параметр id равен 5
  • /user/42 — параметр id равен 42

Вам не нужно создавать отдельный маршрут для каждого пользователя — достаточно одного маршрута с параметром.

Как объявить динамический маршрут

Давайте рассмотрим минимальный пример маршрута с параметром:


// app-routing.module.ts

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { UserDetailComponent } from './user-detail/user-detail.component';

const routes: Routes = [
  { path: 'user/:id', component: UserDetailComponent },
  // ... другие маршруты
];

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

Здесь мы определяем маршрут user/:id, где :id — это имя параметра. Оно может быть любым (например, :userId), главное — одинаково использовать его в маршруте и в коде компонента.

Вопрос на засыпку:
Что будет, если вы перейдёте по адресу /user/123?
Ответ: Angular отобразит компонент UserDetailComponent и передаст ему параметр id со значением 123.

2. Переход по динамическому маршруту

Чтобы попасть на страницу пользователя с определённым ID, вы можете использовать:

routerLink

<!-- user-list.component.html -->
<ul>
  <li *ngFor="let user of users">
    <a [routerLink]="['/user', user.id]">{{ user.name }}</a>
  </li>
</ul>

Если у пользователя id=7, Angular сформирует ссылку /user/7.

В коде (программная навигация)

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

constructor(private router: Router) {}

goToUser(userId: number) {
  this.router.navigate(['/user', userId]);
}

3. Получение параметров маршрута в компоненте

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

Подключаем ActivatedRoute

ActivatedRoute — это сервис, который содержит информацию о текущем маршруте, включая параметры.

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';

@Component({
  selector: 'app-user-detail',
  template: `
    <h2>Пользователь №{{ userId }}</h2>
    <!-- Здесь можно вывести другие данные -->
  `
})
export class UserDetailComponent implements OnInit {
  userId!: string; // или number, если преобразуете

  constructor(private route: ActivatedRoute) {}

  ngOnInit() {
    // Получаем параметр id из URL
    this.userId = this.route.snapshot.paramMap.get('id')!;
    // Если параметр обязателен, можно не проверять на null
  }
}

Объяснение:
this.route.snapshot.paramMap.get('id') — достаёт значение параметра id из URL.
Значение всегда строка. Если нужен number, делаем Number(...).

Почему snapshot?

snapshot — это "снимок" параметров на момент инициализации компонента. Если компонент не пересоздаётся при смене параметра (например, переход /user/5/user/6 внутри одного компонента), snapshot не обновится. Тогда нужно подписаться на изменения параметров!

4. Реакция на изменение параметров (Observable)

В реальных приложениях пользователь может находиться на странице пользователя, а затем переходить к другому пользователю (например, через <routerLink>), и Angular не уничтожает компонент, а просто меняет параметр.

Чтобы реагировать на такие изменения, используйте Observable:

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';

@Component({
  selector: 'app-user-detail',
  template: `<h2>Пользователь №{{ userId }}</h2>`
})
export class UserDetailComponent implements OnInit {
  userId!: string;

  constructor(private route: ActivatedRoute) {}

  ngOnInit() {
    this.route.paramMap.subscribe(params => {
      this.userId = params.get('id')!;
      // Здесь можно, например, подгрузить данные пользователя по id
    });
  }
}

Комментарий:
paramMap — это Observable, который уведомляет о каждом изменении параметров маршрута.
Не забудьте отписаться, если компонент может быть уничтожен до конца жизни приложения (иначе утечка памяти).

5. Применение: загрузка данных по параметру

Давайте свяжем наш пример с реальной задачей: загрузим данные пользователя из массива или сервиса по id.

// user-detail.component.ts
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';

const USERS = [
  { id: 1, name: 'Алиса' },
  { id: 2, name: 'Боб' },
  { id: 3, name: 'Чарли' }
];

@Component({
  selector: 'app-user-detail',
  template: `
    <div *ngIf="user">
      <h2>Пользователь: {{ user.name }}</h2>
      <p>ID: {{ user.id }}</p>
    </div>
    <div *ngIf="!user">
      <p>Пользователь не найден</p>
    </div>
  `
})
export class UserDetailComponent implements OnInit {
  user: { id: number, name: string } | undefined;

  constructor(private route: ActivatedRoute) {}

  ngOnInit() {
    this.route.paramMap.subscribe(params => {
      const id = Number(params.get('id'));
      this.user = USERS.find(u => u.id === id);
    });
  }
}

Теперь если вы перейдёте по /user/2, отобразится "Пользователь: Боб".

6. Полезные нюансы

Несколько параметров маршрута

Можно использовать несколько параметров в одном маршруте:

{ path: 'order/:orderId/item/:itemId', component: OrderItemComponent }

В компоненте:

ngOnInit() {
  this.route.paramMap.subscribe(params => {
    const orderId = params.get('orderId');
    const itemId = params.get('itemId');
    // Используйте оба параметра!
  });
}

Параметры как часть вложенных маршрутов

const routes: Routes = [
  { path: 'user/:userId', component: UserComponent, children: [
      { path: 'profile', component: UserProfileComponent },
      { path: 'settings', component: UserSettingsComponent }
    ]
  }
];

В дочерних компонентах (UserProfileComponent и др.) можно получить параметр userId через цепочку ActivatedRoute:

constructor(private route: ActivatedRoute) {}

ngOnInit() {
  // Получить параметр из родительского маршрута:
  const userId = this.route.parent?.snapshot.paramMap.get('userId');
}

Отличие route параметров от query-параметров

Route-параметры (/user/:id) — часть пути, обязательны для перехода.
Query-параметры (/user/42?tab=settings) — идут после ?, опциональны, используются для фильтрации, пагинации и т.д.

Query-параметры получаются через this.route.queryParamMap.

7. Типичные ошибки при работе с динамическими маршрутами

Ошибка №1: Неправильное имя параметра.
Если в маршруте написано :id, а в коде вы обращаетесь к params.get('userId'), ничего не найдётся. Имена должны совпадать.

Ошибка №2: Использование snapshot вместо подписки.
Если вы используете только snapshot.paramMap.get(), а компонент не пересоздаётся при смене параметра, данные не обновятся. Для динамических переходов используйте подписку на paramMap.

Ошибка №3: Преобразование типов.
Параметры всегда приходят как строки. Если вам нужен number, не забудьте преобразовать: Number(params.get('id')).

Ошибка №4: Отсутствие проверки на null.
Если параметр не найден, params.get() вернёт null. Лучше подстраховаться, особенно если параметр не обязателен.

Ошибка №5: Отсутствие отписки от Observable.
Если компонент может быть уничтожен до конца жизни приложения, не забудьте отписаться от подписки на paramMap, чтобы избежать утечек памяти. В простых случаях Angular сам всё почистит, но в сложных компонентах лучше явно использовать takeUntil или аналогичные методы.

Ошибка №6: Одинаковые маршруты без параметра и с параметром.
Если у вас есть маршруты /user и /user/:id, порядок их объявления в массиве routes важен! Сначала должен идти более специфичный маршрут (/user/:id), иначе Angular всегда будет попадать на первый подходящий.

Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ