В одном из проектов был интересный таск: сделать компонент универсальной навигации. Иначе говоря такой, чтобы его можно было просто положить на какой-то роут, и в нем, магическим образом, появились ссылки на дочерние к этому роуту сегменты.
Выходит, компонент будет работать как лейаут. То есть он должен вешаться на роут как компонент, при этом внутри него будет стоять router-outlet для вывода дочерних компонентов.
Маршруты
const routes: Routes = [
{
path: '',
data: {
title: 'layout'
},
component: NavigationLayoutComponent,
children: [
{
path: 'home',
data: {
title: 'Home',
},
component: HomePageComponent,
},
{
path: 'contacts',
data: {
title: 'Contacts',
},
component: ContactsPageComponent,
},
{ path: '**', pathMatch: 'full', redirectTo: 'home' }
],
}
];
Общий принцып такой:
- Получаем сегменты из роутера
- Собираем "базовый путь"
- Рендерим
Итак для начала подпишемся на события роутера
constructor(
private _router: Router,
public activatedRoute: ActivatedRoute,
) {
_router.events.subscribe(e => {
if (e instanceof NavigationEnd) {
this._updateBaseUrl(
this._flattenSegments()
);
}
});
}
Так как изначально сегменты текушего роута существуют в виде дерева, их нужно преобразовать в одномерный массив, чтобы обновить базовый URL. Берем корневой сегмент и рекурсивно "выпрямляем".
private _flattenSegments() {
// По сегментам будем проходить рекурсивно,
// так как они вложены друг в друга
function _flatten(segments: ActivatedRouteSnapshot[], res: ActivatedRouteSnapshot[] = []) {
segments.forEach(s => {
res.push(s);
_flatten(s.children, res);
});
return res;
}
return _flatten([this.activatedRoute.root.snapshot]);
}
И пересобираем базовый путь
private _updateBaseUrl(segments: ActivatedRouteSnapshot[]) {
// Изначально ActivatedRoute хранит сегменты вложеными.
// Ранее мы вызвали "выпрямитель", который преобразовал
// дерево в плоский массив.
const segmentIndex = segments
// Теперь в этом массиве ищем сегмент на который
// мы повесили наш лейаут
.findIndex(s => s.component === this.constructor);
// Собираем базовый URL
this.baseUrl = '/' + segments
// сегменты после нашего нас не интересуют
.splice(0, segmentIndex + 1)
// убираем сегменты с пустым path *
.filter(s => s.routeConfig && s.routeConfig.path)
// получаем path от сегментов
.map(s => s.routeConfig.path)
// склеиваем
.join('/');
}
* Здесь во время вызова .filter, сначала проверяем существование routeConfig и только потом routeConfig.path, при использовании LazyModules, начальный сегмент (куда прописыватся путь к модулю) не имеет свойсива routeConfig.
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router, ActivatedRouteSnapshot, NavigationEnd } from '@angular/router';
@Component({
selector: 'app-navigation-layout',
templateUrl: './navigation-layout.component.html',
styleUrls: ['./navigation-layout.component.css']
})
export class NavigationLayoutComponent implements OnInit {
// Базовый URL от которо будут строится дочерние
baseUrl = '';
constructor(
private _router: Router,
public activatedRoute: ActivatedRoute,
) {
_router.events.subscribe(e => {
if (e instanceof NavigationEnd) {
this._updateBaseUrl(
this._flattenSegments()
);
}
});
}
ngOnInit() {
}
// Возвращает массив сегментов текущего роута
private _flattenSegments() {
// По сегментам будем проходить рекурсивно,
// так как они вложены друг в друга
function _flatten(segments: ActivatedRouteSnapshot[], res: ActivatedRouteSnapshot[] = []) {
segments.forEach(s => {
res.push(s);
_flatten(s.children, res);
});
return res;
}
return _flatten([this.activatedRoute.root.snapshot]);
}
// Обновляет базовый URL
private _updateBaseUrl(segments: ActivatedRouteSnapshot[]) {
// Изначально ActivatedRoute хранит сегменты вложеными.
// Ранее мы вызвали "выпрямитель", который преобразовал
// дерево в плоский массив.
const segmentIndex = segments
// Теперь в этом массиве ищем сегмент на который
// мы повесили наш лейаут
.findIndex(s => s.component === this.constructor);
// Собираем базовый URL
this.baseUrl = '/' + segments
// сегменты после нашего нас не интересуют
.splice(0, segmentIndex + 1)
// убираем сегометы с пустым path
.filter(s => s.routeConfig && s.routeConfig.path)
// получаем path сегментов
.map(s => s.routeConfig.path)
// склеиваем
.join('/');
}
}
Осталось вывести ссылки в шаблоне
<div>
<ng-container *ngFor="let ch of activatedRoute.routeConfig.children">
<a *ngIf="ch.data" [routerLink]="[baseUrl, ch.path]">
{{ ch.data.title }}
</a>
</ng-container>
</div>
Результат