Angular router link active nested menu

五迷三道 提交于 2019-12-12 11:50:40

问题


I'm trying to make a nested menu with angular routes.

What I need is to apply class to a nested route if it's active and apply class to a parent component if its child is active.

How do I achieve this? For now I'm doing recursive menu building for ease of use when I need multilevel nesting.

component.html

<a (click)="toggleActive()" [class.active]="isActive" [routerLink]="menuItem.link" *ngIf="menuItem.link; else noLink">
  <i *ngIf="menuItem.faClass" class="fa fa-{{menuItem.faClass}}"></i>
  {{menuItem.name}} <span *ngIf="menuItem.children && menuItem.children.length > 0" class="fa fa-chevron-down"></span>
</a>

<ng-container *ngIf="menuItem.children && menuItem.children.length > 0">
  <ul class="nav child_menu" [class.active]="isActive" routerLinkActive="active"
      *ngFor="let item of menuItem.children">
    <li menu-item [menuItem]="item" (checkActive)="updateActiveState()"></li>
  </ul>
</ng-container>

<ng-template #noLink>
  <a (click)="toggleActive()" [class.active]="isActive">
    <i *ngIf="menuItem.faClass" class="fa fa-{{menuItem.faClass}}"></i>
    {{menuItem.name}} <span *ngIf="menuItem.children && menuItem.children.length > 0" class="fa fa-chevron-down"></span>
  </a>
</ng-template>

component.ts

@Component({
  selector: '[menu-item]',
  templateUrl: './menu-item.component.html',
  styleUrls: ['./menu-item.component.scss'],
  host: {
    '[class.active]': 'hostActive && isActive',
    '[class.active-sm]': 'hostActiveSm && isActive'
  }
})
export class MenuItemComponent implements OnInit, AfterViewInit, OnChanges {

  public menuSize;
  public isActive = false;

  @Input() menuItem: MenuItem;
  @Output() checkActive: EventEmitter<void> = new EventEmitter<void>();
  @ViewChild(MenuItemComponent) menuComponent: MenuItemComponent;

  private hostActive = true;
  private hostActiveSm = false;

  @HostBinding('class') hostClass = this.hostActive && this.isActive ? 'active' : this.hostActiveSm && this.isActive ? 'active-sm' : '';

  constructor(
    private router: Router,
    private uss: UiStateService,
  ) {
  }

  ngOnInit() {
    this.uss.menuSubject.subscribe(msg => {
      this.menuSize = msg;
      if (this.menuSize === MenuSizes.sm) {
        this.hostActive = false;
        this.hostActiveSm = true;
      } else {
        this.hostActive = true;
        this.hostActiveSm = false;
      }
      this.updateActiveState();
    });

    this.router.events.subscribe((e: Event) => {
      if (e instanceof NavigationEnd) {
        this.updateActiveState();
      }
    });
  }

  ngOnChanges() {
  }

  ngAfterViewInit() {
    this.updateActiveState();
  }

  public toggleActive(): void {
    this.isActive = !this.isActive;
    // if (this.menuComponent) {
    //   this.menuComponent.isActive = true;
    // }
  }

  private updateActiveState(): void {
    // reset previous state
    this.isActive = false;
    if (this.menuComponent) {
      this.menuComponent.isActive = false;
    }

    // Check state of item with no els
    const url = this.router.url;
    console.log('URL', url, 'Menu link', this.menuItem.link);
    console.log(url.match('/' + this.menuItem.link + '$/'));
    if (this.menuItem && this.menuItem.link && url.match('/' + this.menuItem.link + '$/')) {
      this.isActive = true;
      this.checkActive.emit();
    }

    if (this.menuComponent) {
      console.log('Menu component');
      console.log(this.menuComponent, this.menuComponent.menuItem.link);
      this.isActive = true;
    }
  }

}

Is there a way of knowing from a component whether its route is active? The trick is that I'm using [routerLink] and not a routerLink so that I can pass . as a link to the root page.

Update

The best I could recreate stackblitz Try to visit "Dasboard". It should add an "active" class to parent li. If you add it yourself you would see the applied class on it's element and a colored bar on the right.


回答1:


Maybe you can try access ActivatedRoute in any component

import { Router, ActivatedRoute} from '@angular/router';    

 constructor(private router: Router, private activatedRoute:ActivatedRoute) {
    console.log(activatedRoute.snapshot.url)  // array of states
    console.log(activatedRoute.snapshot.url[0].path) 
 }

Hope that will help!




回答2:


StackBlitz

All of this functionality is built inside Angular's router module natively. This is the whole idea behind child routes. If a route is truly a parent component, it should be a parent in the routing. By doing this, you can simply check if the parent route is active instead of any of the routes arbitrarily assigned in the app component.

Additionally, this has the huge benefit of allowing dashboard to simply route to ['../sibling']

In my child routes specifically /home/home.routes, you will see a commented line that would enable the dashboard to automatically load if that was the desired behaviour.

You are already using routerLinkActive, in your dynamic menu-item, but to have the most straightforward approach, it should be on the item bound to the routerLink as outlined here.

The biggest issue you will likely encounter is that nested routerLink elements do not play nicely in the DOM as discussed in this question so you need to stop propagation.

app-component.html

<ul app-menu-item 
  class="nav side-menu"
  [menu]="menu">
</ul>

menu-item.html:

<li
  *ngFor="let item of menu"
  [routerLink]="item.link"
  routerLinkActive="active"
  (click)="stop($event)">

    {{item.name}}

    <ul app-menu-item
      *ngIf="item.children"
      class="nav side-menu"
      [menu]="item.children">
    </ul>
</li>

I will add, though it doesn't answer the title in your question, that this problem is most easily solved by using parent routes, and then setting up menu-item.html as follows:

<li *ngFor="let item of menu">
  <a [routerLink]="item.link" routerLinkActive="active">
    {{item.name}}
  </a>
  <ul class="nav side-menu"
  *ngIf="item.children"
  app-menu-item [menu]="item.children">
  </ul>
</li>

and then using a sibling selector in your scss to show or hide the menu.




回答3:


I've managed to workaround it. StackBlitz

Mainly it's achieved by using setTimeOut without a time in navigateEnd event so that I can check the state of isActive and get the value(if there is no setTimeout the value is gotten "from the past" meaning it's not updated when it's taken). It works in a bit tricky way but it works



来源:https://stackoverflow.com/questions/53299952/angular-router-link-active-nested-menu

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!