Search code examples
angularangular-router

Using ContentChildren to get all the router links on a page


If I add a content children to my component

@ContentChildren(RouterLink, { descendants: true })
links: QueryList<RouterLink>;

and have a template

<a [routerLink]="''">Link 1</a><br>
<a [routerLink]="''">Link 2</a><br>
<a [routerLink]="''">Link 3</a><br>
Number of links: {{links.length}}

Links length is always 0.

I have tried with a directive as well

@Directive({
  selector: '[appRouterLinkWatch]'
})
export class RouterLinkWatchDirective implements AfterContentInit {
  @ContentChildren(RouterLink, { descendants: true })
  links: QueryList<RouterLink>;

  constructor(@Optional() private link: RouterLink) {}

  ngAfterContentInit() {
    console.log(this.links.length, this.link);
  }
}

Both the links content children and the link dependency are not populated. What am I doing wrong to get a list of router links in my component?

I have been looking at the sorce code for the routerLinkActive directive https://github.com/angular/angular/blob/master/packages/router/src/directives/router_link_active.ts I cannot see what I am doing differently.

I have a stack blitz here https://stackblitz.com/edit/angular-ivy-bvpyhd?file=src%2Fapp%2Fapp.component.html


Solution

  • It appears the RouterLink directive utilizes two separate types internally.

    RouterLink and RouterLinkWithHref

    Counting routerLink directly in component

    If we want to count the routerLinks directly in the component, and expect no projected links, then we must use @ViewChildren() rather than @ContentChildren().

    @Component({...})
    export class AppComponent implements AfterViewInit {
      @ViewChildren(RouterLink) routeWithoutHref!: QueryList<RouterLink>;
      @ViewChildren(RouterLinkWithHref) routeWithHref!: QueryList<RouterLinkWithHref>;
    
      ngAfterViewInit() {
        console.log(this.routeWithHref.length); // expect 2
        console.log(this.routeWithoutHref.length); // expect 1
      }
    }
    
    <a routerLink>link</a>
    <a routerLink="a">link</a>
    <div routerLink="b">link</div>
    

    The above component produces a length of 2 for RouterLinkWithHref and a length of 1 for RouterLink from which I summize it depends on whether the RouterLink is attached to an anchor or not.

    Whether the directive has any input does not appear to bear any influence.

    Counting RouterLink from a directive

    Much the same as before, except we now utilize ContentChildren instead of ViewChildren

    @Directive({selector: '[countRoutes]'})
    export class RouteCountDirective implements AfterContentInit{
      @ContentChildren(RouterLink, {descendants: true}) routerLinks!: QueryList<RouterLink>
      @ContentChildren(RouterLinkWithHref, {descendants: true}) routerLinksWithHref!: QueryList<RouterLinkWithHref>
    
      ngAfterContentInit() {
        console.log(this.routerLinks.length) // 1
        console.log(this.routerLinksWithHref.length) // 2
      }
    }
    
    <div countRoutes>
      <a routerLink>link</a>
      <a routerLink="a">link</a>
      <div routerLink="b">link</div>
    </div>
    

    Notice

    When using the {descendants: true} option for the ContentChildren only routerLinks nested inside the element on which the countRoutes directive is attached will be selected.

    <div countRoutes>
      <a routerLink>Counted</a>
      <a routerLink="a">Counted</a>
      <div routerLink="b">Counted</div>
    </div>
    <a routerLink="c">Not Counted</a>