web-dev-qa-db-fra.com

Angular lien imbriqué actif menu imbriqué

J'essaie de faire un menu imbriqué avec des itinéraires angulaires.

Ce dont j'ai besoin est d'appliquer la classe à une route imbriquée si elle est active et d'appliquer la classe à un composant parent si son enfant est actif.

Comment est-ce que je réalise ceci? Pour le moment, je suis en train de créer des menus récursifs pour faciliter leur utilisation lorsque j'ai besoin d'imbrication à plusieurs niveaux.

composant.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>

composant.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;
    }
  }

}

Existe-t-il un moyen de savoir à partir d'un composant si son itinéraire est actif? Le truc, c’est que j’utilise [routerLink] et non une routerLink pour pouvoir transmettre . en tant que lien vers la page racine.

Mettre à jour

Le meilleur que je pourrais recréer stackblitz Essayez de visiter "Dasboard". Il convient d’ajouter une classe "active" au parent li. Si vous l'ajoutez vous-même, vous verrez la classe appliquée sur son élément et une barre de couleur à droite. 

4
Sergey

J'ai réussi à contourner le problème. StackBlitz

Cela est principalement réalisé en utilisant setTimeOut sans heure dans l'événement navigateEnd afin que je puisse vérifier l'état de isActive et obtenir la valeur (s'il n'y a pas de setTimeout, la valeur est "du passé", ce qui signifie qu'il n'est pas mis à jour lorsqu'il est pris). Cela fonctionne de manière un peu délicate mais cela fonctionne

0
Sergey

StackBlitz

Toutes ces fonctionnalités sont construites de manière native dans le module de routeur d’Angular. C’est toute l’idée qui se cache derrière les itinéraires pour enfants. Si une route est vraiment un composant parent, il doit s'agir d'un parent dans le routage. En faisant cela, vous pouvez simplement vérifier si la route parente est active au lieu des routes attribuées arbitrairement dans le composant d'application. 

De plus, cela présente l’énorme avantage de permettre au tableau de bord d’acheminer simplement vers ['../sibling'].

Dans mes itinéraires enfants spécifiquement /home/home.routes, vous verrez une ligne commentée qui permettrait au tableau de bord de se charger automatiquement si tel était le comportement souhaité.

Vous utilisez déjà routerLinkActive, dans votre élément de menu dynamique, mais pour que l'approche soit la plus simple possible, il convient de placer l'élément lié au routerLink comme indiqué ici

Le plus gros problème que vous rencontrerez probablement est que les éléments routerLink imbriqués ne fonctionnent pas correctement dans le DOM, comme indiqué dans cette question . Vous devez donc arrêter la propagation. 

app-composant.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>

J'ajouterai, bien que cela ne corresponde pas le titre de votre question, que ce problème est résolu plus facilement en utilisant les itinéraires parents, puis en configurant menu-item.html comme suit:

<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>

puis utilisez un sélecteur de frères et sœurs dans votre scss pour afficher ou masquer le menu.

0
Murphy4

Peut-être que vous pouvez essayer d'accéder à ActivatedRoute dans n'importe quel composant

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) 
 }

J'espère que ça va aider!

0
freepowder