All files / breadcrumb-router si-breadcrumb-router.component.ts

90.19% Statements 46/51
77.27% Branches 17/22
90% Functions 9/10
90% Lines 45/50

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124                                  1x           2x   2x   2x 2x 2x 2x 2x       2x       2x 2x       2x 2x 45x   3x   3x     3x 3x 3x 3x       2x               3x         3x     3x   3x   3x 3x         3x                 10x 10x 10x 10x 3x 3x   7x 7x 7x       10x       10x 10x 22x 22x 11x   22x   10x      
/**
 * Copyright (c) Siemens 2016 - 2025
 * SPDX-License-Identifier: MIT
 */
import { Component, inject, input, OnDestroy, OnInit, signal } from '@angular/core';
import { ActivatedRoute, ActivatedRouteSnapshot, NavigationEnd, Router } from '@angular/router';
import { BreadcrumbItem, SiBreadcrumbComponent } from '@siemens/element-ng/breadcrumb';
import { Observable, Subject, Subscription } from 'rxjs';
import { filter, takeUntil } from 'rxjs/operators';
 
import { SI_BREADCRUMB_RESOLVER_SERVICE } from './si-breadcrumb-router.model';
 
@Component({
  selector: 'si-breadcrumb-router',
  imports: [SiBreadcrumbComponent],
  templateUrl: './si-breadcrumb-router.component.html'
})
export class SiBreadcrumbRouterComponent implements OnInit, OnDestroy {
  /**
   * Aria label for the main breadcrumb navigation. Needed for a11y.
   *
   * @defaultValue 'breadcrumb'
   */
  readonly ariaLabel = input('breadcrumb');
 
  protected readonly items = signal<BreadcrumbItem[]>([]);
 
  private readonly currentCalcUrl = signal<string | undefined>(undefined);
  private nextRoute = new Subject<void>();
  private resolverService = inject(SI_BREADCRUMB_RESOLVER_SERVICE);
  private route? = inject(ActivatedRoute, { optional: true });
  private router? = inject(Router, { optional: true });
  private routerSubscription?: Subscription;
 
  ngOnInit(): void {
    this.checkItems();
  }
 
  ngOnDestroy(): void {
    this.routerSubscription?.unsubscribe();
    this.nextRoute.next();
  }
 
  private checkItems(): void {
    if (!this.routerSubscription && this.route && this.router) {
      this.routerSubscription = this.router.events
        .pipe(filter(e => e instanceof NavigationEnd))
        .subscribe(navigationEvent => {
          const event = navigationEvent as NavigationEnd;
          // Get the new url
          const newUrl = event.urlAfterRedirects || event.url;
          // Only update when url differs from previous url
 
          if (this.currentCalcUrl() !== newUrl) {
            this.currentCalcUrl.set(newUrl);
            this.nextRoute.next();
            this.computePath();
          }
        });
 
      Iif (this.router.navigated) {
        this.currentCalcUrl.set(this.router.url);
        this.computePath();
      }
    }
  }
 
  private computePath(): void {
    Iif (!this.route || !this.resolverService) {
      return;
    }
 
    // Get a snapshot of the all current activate routes
    const pathFromRoot: ActivatedRouteSnapshot[] = this.route.snapshot.pathFromRoot;
 
    // Find the child/leaf route that fits to the url
    const route = this.findRouteWithUrl(pathFromRoot, this.currentCalcUrl()?.split('?')[0] ?? '');
 
    if (route) {
      // Workaround to fix a bug that the route is null, in some cases
      const links$ = this.resolverService.resolve(route);
      Iif (links$ instanceof Observable) {
        links$.pipe(takeUntil(this.nextRoute)).subscribe(links => {
          this.items.set([{ link: '/', title: '/' }, ...links]);
        });
      } else {
        this.items.set([{ link: '/', title: '/' }, ...links$]);
      }
    }
  }
 
  private findRouteWithUrl(
    routes: ActivatedRouteSnapshot[],
    url: string
  ): ActivatedRouteSnapshot | null {
    let result: ActivatedRouteSnapshot | null = null;
    for (const route of routes) {
      const routeUrl = this.getUrl(route);
      if (url === routeUrl && !route.data?.siBreadcrumbIgnore) {
        result = route;
        break;
      } else {
        result = this.findRouteWithUrl(route.children, url);
        if (result != null) {
          break;
        }
      }
    }
    return result;
  }
 
  private getUrl(route: ActivatedRouteSnapshot): string {
    let url = '';
    for (const routeSegment of route.pathFromRoot) {
      const myUrl: string = routeSegment.url.map(o => o.toString()).join('/');
      if (!url.endsWith('/')) {
        url = url + '/';
      }
      url = url + myUrl;
    }
    return url;
  }
}