Pull to refresh

Генерация меню средствами роутов Angular

В одном из проектов был интересный таск: сделать компонент универсальной навигации. Иначе говоря такой, чтобы его можно было просто положить на какой-то роут, и в нем, магическим образом, появились ссылки на дочерние к этому роуту сегменты.


Выходит, компонент будет работать как лейаут. То есть он должен вешаться на роут как компонент, при этом внутри него будет стоять 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' }
    ],
  }
];

Общий принцып такой:


  1. Получаем сегменты из роутера
  2. Собираем "базовый путь"
  3. Рендерим

Итак для начала подпишемся на события роутера


  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>

Результат


GitHub

Tags:
Hubs:
You can’t comment this publication because its author is not yet a full member of the community. You will be able to contact the author only after he or she has been invited by someone in the community. Until then, author’s username will be hidden by an alias.