Маршрутизация: страницы без перезагрузки
Роутер превращает одностраничное приложение в многостраничное на вид: URL меняется, контент подменяется, а перезагрузки нет.
«Пользователь видит разные страницы. Браузер видит одну. Между ними стоит роутер и виртуозно жонглирует компонентами».
SPA живёт в одной HTML-странице, но пользователю нужны разделы: каталог, корзина, профиль. Роутер Angular сопоставляет URL с компонентами и подменяет их без перезагрузки страницы. Настраивается он списком маршрутов, который передаётся при старте приложения:
// app.routes.ts
import { Routes } from '@angular/router';
import { HomeComponent } from './home.component';
import { CartComponent } from './cart.component';
export const routes: Routes = [
{ path: '', component: HomeComponent },
{ path: 'cart', component: CartComponent },
{ path: '**', component: NotFoundComponent }, // 404
];
// main.ts — подключаем роутер
import { bootstrapApplication } from '@angular/platform-browser';
import { provideRouter } from '@angular/router';
import { AppComponent } from './app.component';
import { routes } from './app.routes';
bootstrapApplication(AppComponent, {
providers: [provideRouter(routes)],
});
Где рисуется текущий маршрут? В месте, помеченном <router-outlet>. А переходы делаются не через <a href> (она перезагрузит страницу), а через директиву routerLink:
template: `
<nav>
<a routerLink="">Главная</a>
<a routerLink="/cart">Корзина</a>
</nav>
<router-outlet />
`
Как работает под капотом
Роутер слушает изменения URL (через History API браузера). При смене адреса он перебирает массив маршрутов сверху вниз, ищет первое совпадение пути и создаёт соответствующий компонент внутри <router-outlet>. Звёздочка ** ловит всё, что не совпало, — это маршрут 404. Порядок важен: более конкретные пути идут выше общих.
URL: /cart
|
роутер перебирает routes сверху вниз:
'' ............ нет
'cart' ........ СОВПАЛО -> создать CartComponent
|
вставить в <router-outlet> (без перезагрузки страницы)
Запускаемая врезка: роутер-матчер на JS
Сердце роутера — сопоставление пути с маршрутом. «Попробуй сам ▶».
// упрощённый матчер: ищет первый подходящий маршрут
function matchRoute(routes, url) {
for (const route of routes) {
if (route.path === '**') return route; // ловит всё
if (route.path === url) return route; // точное совпадение
}
return null;
}
const routes = [
{ path: '', component: 'Home' },
{ path: 'cart', component: 'Cart' },
{ path: '**', component: 'NotFound' },
];
console.log(matchRoute(routes, '').component); // Home
console.log(matchRoute(routes, 'cart').component); // Cart
console.log(matchRoute(routes, 'xyz').component); // NotFound
Частые ошибки
- Использовать
<a href>для внутренних ссылок. Это перезагрузит всё приложение; нуженrouterLink. - Поставить
**не последним. Тогда он перехватит все пути, и остальные маршруты не сработают. - Забыть
<router-outlet>. Без него роутеру некуда вставлять компоненты.
Best practices
- Располагайте маршруты от конкретных к общим,
**— в самом конце. - Для навигации всегда используйте
routerLink, а программно —Router.navigate(). - Выносите маршруты в отдельный файл
app.routes.tsдля читаемости.
Итоги. Роутер сопоставляет URL с компонентами и рисует их в <router-outlet>; переходы — через routerLink. provideRouter(routes) включает всё это. Дальше — параметры маршрута и ленивая загрузка.
Закрепляем
Роутер создаёт иллюзию многостраничности поверх одностраничного приложения. Он слушает изменения URL, сопоставляет адрес с массивом маршрутов и рисует подходящий компонент в <router-outlet> — всё без перезагрузки страницы. Три вещи, которые должны стать рефлексом: маршруты задаются массивом и подключаются через provideRouter, текущий маршрут рендерится в <router-outlet>, а переходы делаются через routerLink, а не через обычный href.
Порядок маршрутов важен, и это частый источник недоумения. Роутер перебирает массив сверху вниз и берёт первое совпадение, поэтому более конкретные пути должны стоять выше общих, а перехватывающий всё подстановочный маршрут ** — обязательно последним. Если поставить ** в начало, он проглотит все адреса, и ни один реальный маршрут не сработает. Этот же **-маршрут — удобное место для страницы 404. Держите маршруты в отдельном файле app.routes.ts, чтобы навигация приложения читалась с одного взгляда.
| Элемент | Роль |
|---|---|
| provideRouter(routes) | Подключить роутер |
| <router-outlet> | Место рендера маршрута |
| routerLink | Навигация без перезагрузки |
| path: '**' | Маршрут 404 (всегда последний) |