Universal Router — это лёгкая библиотека для маршрутизации, основанная на принципе сопоставления URL с маршрутами через объекты и функции. В отличие от встроенного Angular Router, Universal Router предоставляет более гибкую модель маршрутов и позволяет легко интегрироваться с серверным рендерингом, SPA и различными состояниями приложения.
При использовании Universal Router с Angular важно учитывать архитектуру Angular-приложения: компоненты, сервисы и жизненный цикл Angular. Universal Router работает как чистая логика сопоставления пути и результата, поэтому его интеграция в Angular чаще всего осуществляется через сервисы и RxJS для реактивного обновления состояния.
Для управления маршрутизацией удобно создать отдельный сервис:
import { Injectable } from '@angular/core';
import UniversalRouter, { Route } from 'universal-router';
import { BehaviorSubject } from 'rxjs';
@Injectable({ providedIn: 'root' })
export class RouterService {
private router: UniversalRouter;
private currentRouteSubject = new BehaviorSubject<string>(window.location.pathname);
public currentRoute$ = this.currentRouteSubject.asObservable();
constructor() {
const routes: Route[] = [
{ path: '/', action: () => 'HomeComponent' },
{ path: '/about', action: () => 'AboutComponent' },
{ path: '/users/:id', action: (context) => `UserComponent with ID: ${context.params.id}` },
{ path: '(.*)', action: () => 'NotFoundComponent' }
];
this.router = new UniversalRouter(routes);
}
async navigate(path: string) {
const result = await this.router.resolve(path);
this.currentRouteSubject.next(result);
window.history.pushState({}, '', path);
}
async resolveCurrent() {
const result = await this.router.resolve(window.location.pathname);
this.currentRouteSubject.next(result);
}
}
Ключевые моменты:
В Angular компоненты могут подписываться на поток маршрута и отображать соответствующий контент:
import { Component, OnInit } from '@angular/core';
import { RouterService } from './router.service';
@Component({
selector: 'app-root',
template: `
<nav>
<a (click)="go('/')">Home</a>
<a (click)="go('/about')">About</a>
<a (click)="go('/users/42')">User 42</a>
</nav>
<div>
<ng-container [ngSwitch]="currentComponent">
<app-home *ngSwitchCase="'HomeComponent'"></app-home>
<app-about *ngSwitchCase="'AboutComponent'"></app-about>
<app-user *ngSwitchCase="'UserComponent with ID: 42'"></app-user>
<app-not-found *ngSwitchDefault></app-not-found>
</ng-container>
</div>
`
})
export class AppComponent implements OnInit {
currentComponent: string = '';
constructor(private routerService: RouterService) {}
ngOnInit() {
this.routerService.currentRoute$.subscribe(component => {
this.currentComponent = component;
});
this.routerService.resolveCurrent();
}
go(path: string) {
this.routerService.navigate(path);
}
}
Особенности интеграции:
currentRoute$ позволяет автоматически
обновлять UI при навигации.Universal Router поддерживает динамические сегменты URL через
:param. Для Angular-компонентов параметры можно передавать
через сервис:
const routes: Route[] = [
{ path: '/users/:id', action: (context) => ({ component: 'UserComponent', params: context.params }) }
];
Компонент для отображения пользователя:
import { Component, Input } from '@angular/core';
@Component({
selector: 'app-user',
template: `<p>User ID: {{ userId }}</p>`
})
export class UserComponent {
@Input() userId!: string;
}
В AppComponent параметры маршрута извлекаются из
объекта:
this.routerService.currentRoute$.subscribe(result => {
if (typeof result === 'object' && result.component === 'UserComponent') {
this.currentComponent = 'UserComponent';
this.userId = result.params.id;
}
});
Такой подход позволяет отделять отображение данных от логики маршрутизации.
Для SPA важно поддерживать корректную работу кнопок браузера «назад» и «вперед». Universal Router совместим с History API:
window.addEventListener('popstate', () => {
this.routerService.resolveCurrent();
});
pushState и replaceState
позволяет полностью контролировать историю URL.Universal Router поддерживает асинхронные функции
action, что позволяет реализовать динамическую загрузку
модулей:
const routes: Route[] = [
{
path: '/about',
action: async () => {
const module = await import('./about/about.module');
return 'AboutComponent';
}
}
];
Преимущества:
action,
что упрощает интеграцию с API.В Angular это особенно полезно для больших приложений с множеством ленивых модулей.
Universal Router не имеет встроенных Guard, как Angular Router, но
аналогичное поведение реализуется через проверку в
action:
const routes: Route[] = [
{
path: '/admin',
action: async (context) => {
const isAuthenticated = await authService.checkAuth();
if (!isAuthenticated) return '/login';
return 'AdminComponent';
}
}
];
Такой подход сохраняет гибкость маршрутизатора и возможность централизованного контроля доступа.
Для серверного рендеринга важно предварительно вычислить маршрут на сервере и передать компонент с данными в шаблон. Universal Router отлично подходит для Angular Universal, так как не зависит от браузерного History API:
const routeResult = await router.resolve(req.url);
res.render('index', { component: routeResult });
ngSwitch или
ngComponentOutlet.action,
заменяя стандартные Guard Angular.Такой подход позволяет комбинировать преимущества Angular и легковесной гибкости Universal Router, сохраняя централизованную и легко тестируемую архитектуру приложения.