0

I want to select <a> element by className when its router link is active, but it returns null

navbar.component.html code:

<nav class="profile-navbar">
  <ul>
    <li class="posts-item">
      <a
        [routerLink]="['/app/profile', username, 'posts']"
        routerLinkActive="active-link"
        >Posts</a
      >
    </li>
    <li class="images-item">
      <a
        [routerLink]="['/app/profile', username, 'images']"
        routerLinkActive="active-link"
        >Images</a
      >
    </li>
  </ul>
</nav>

navbar.component.ts code:

import { AfterViewInit, Component, OnInit } from '@angular/core';

@Component({
  selector: 'profile-navbar',
  templateUrl: './navbar.component.html',
  styleUrls: ['./navbar.component.scss']
})
export class NavbarComponent implements OnInit, AfterViewInit {
  
  constructor() { 
  }
  
  ngOnInit(): void {
  }
  
  ngAfterViewInit(): void {
    const activeLink = document.querySelector('.active-link');
    console.log(activeLink)
  }

}

How can I select the element the element based on routerLinkActive class??

2 Answers2

1

To get a series of "divs" you can use a template reference variable and ViewChildren. When you has ViewChildren you can find the element with a class or anopther property.

Case of routerLinks the approach is similar, but you can to have in ViewChildren the [routerLinksActive seeing as "ElementRef" and seeing as "RouterLinkActive"

Some like

  //links are the "RouterLinkActive"
  @ViewChildren(RouterLinkActive) links:QueryList<RouterLinkActive>
  
  //lisk are the "RouterLinkAvtive" but seeing as ElementRef
  @ViewChildren(RouterLinkActive,{read:ElementRef}) linksElements:QueryList<ElementRef>
  @ViewChild('indicator') indicator:ElementRef
  constructor(private router: Router) {}
  ngOnInit() {
    this.router.events.pipe(
       filter(x=>x instanceof NavigationEnd),
       startWith(null)).subscribe(res=>{

      //we need enclosed in a setTimeout because when the event NavigationEnd
      //happens, Angular are not add the class "active-links"
      // nor link isActive=true
      setTimeout(()=>{

        //first find the "index" in the QueryList
        const index=(this.links.map(
          (x:RouterLinkActive,i:number)=>({isActive:x.isActive,index:i}))          
          .find(x=>x.isActive) || {index:-1}).index
  
        //it's the same index in the ElementRef
        if (index>=0)
        {
          const el=this.linksElements.find((_,i)=>i==index)
          
          //We use the "el" to change the style.top of the indicator
           this.indicator.nativeElement.style.top=
                       el.nativeElement.offsetTop+'px'
        }
        else
        {
          this.indicator.nativeElement.style.top='-100px'
        }
      })
    })
  }

Where

<nav class="profile-navbar">
<ul>
  <li class="posts-item">
    <a
      [routerLink]="['/hello']"
      routerLinkActive="active-link"
      >Posts</a
    >
  </li>
  <li class="images-item">
    <a
      [routerLink]="['/by']"
      routerLinkActive="active-link"
      >Images</a
    >
  </li>
</ul>
<div #indicator class="indicator"></div>
</nav>

like is showed in this stackblitz

Eliseo
  • 50,109
  • 4
  • 29
  • 67
0

You can't use this LifeCycleHook try ngDoCheck() instead or listen to the route changes (How to detect a route change in Angular?) and do your selection based on the event or even better just use in your .css file:

a.active {
/* your style */
}

If you just want to style it, there is usually no need to select it, but in your case with the calculation of translate3d and scaleX I'm not sure...

Perry45
  • 66
  • 7
  • It returns the first link that has routerLinkActive attribute with value 'active-link' – Asmaa Mahmoud Mar 20 '22 at 07:00
  • Sorry I missed that, I just updated my answer! – Perry45 Mar 20 '22 at 08:35
  • No problem, I just used querySellectorAll and it works fine, but I was wondering if there is a way to get only the active link. – Asmaa Mahmoud Mar 20 '22 at 09:08
  • And why selecting the element with class name not working? – Asmaa Mahmoud Mar 20 '22 at 09:09
  • Sorry again, I misunderstood your question. The class should be applied but only after ngAfterViewInit. So just use ngAfterViewChecked or just use a css selector. – Perry45 Mar 20 '22 at 20:24
  • Thanks a lot, that's it `ngAfterViewChecked()`, but I think it's not good to call the function of positioning the indicator inside `ngAfterViewChecked()`, isn't it? – Asmaa Mahmoud Mar 21 '22 at 08:45
  • @AsmaaMahmoud You should use ngDoCheck() and call your method from there or even better use the router event in a way like Eliseo suggested. – Perry45 Mar 21 '22 at 17:37