Unable to navigate to another component’s HTML anchor (ngbNav)

I have two components in my Angular application, I’m trying to navigate from the first component over to the second, but I want to navigate to a specific pills tab, which isn’t working.

Initially, I show the following:

<!-- Error Modal -->
<div id="errorModal" class="modal modal-show" *ngIf="showModal">
  <div class="modal-content">
    <div class="modal-header">
      <h5 class="modal-title">Access Denied</h5>
    </div>
    <div class="modal-body">
      <p>{{ modalErrorMessage }}</p>
      <div class="mt-4 button-container">
        <button class="btn btn-lg btn-primary" (click)="navigateToMyAccount(); $event.preventDefault()">Go Back</button>
      </div>
    </div>
  </div>
</div>

After the click event is fired, we execute the following:

navigateToMyAccount(): void {
    this.router.navigate(['/my-account'], { queryParams: { tab: 'subscription' } });
}

Now within the second component, the component we want to route to, I do this:

export class MyAccountComponent implements OnInit {
...
@ViewChild('nav', { static: true }) nav!: NgbNav;
async ngOnInit(): Promise<void> {
    this.route.queryParams.subscribe(params => {
      if (params['tab'] === 'subscription') {
        this.gotoSubscriptionTab();
      }
    });
...

  public gotoSubscriptionTab() {
    console.log('gotoSubscriptionTab called');
    if (this.nav) {
      console.log('nav is defined', this.nav);
      this.nav.select(2);
    } else {
      console.error('nav is not defined');
    }
  }

Within the Template of the second component, I then do this:

<ul ngbNav class="nav-pills dark-nav-pills mb-3" #nav="ngbNav">
...

<li id="account" [ngbNavItem]="1">
...
<li id="subscription" [ngbNavItem]="2">

No matter what I try, the navigation does not work, and I always have:

nav is not defined

in my browser’s console.

How can I properly route from the first component over to the ngbNavItem of the second component?

I first thought this might be a lifecycle issue, but even trying with AfterViewInit, it isn’t working …

Thanks in advance

For some reason I currently don’t fully understand, but my guess is that it has something to do with the lifecycle management in angular, the following solution works, even while it’s not that kinda pretty:

ngAfterViewInit(): void {
  const interval = setInterval(() => {
    if (this.nav) {
      clearInterval(interval);
      this.route.queryParams.subscribe(params => {
        if (params['tab'] === 'subscription') {
          this.gotoSubscriptionTab();
        }
      });
    }
  }, 500); // Check every 500ms

  this.cdr.detectChanges();
}

We basically wait until the nav element is really available at the DOM, I would actually expect that ngAfterViewInit would handle such scenarios for me, but sadly it does not in that case. It is also possible to directly run this at ngOnInit, as ngAfterViewInit seems to have no direct influence here.

Your code seems to be valid, so I suspect that ngbNav is inside some element with *ngIf or *ngFor. From docs:

By setting static: true, you guarantee to Angular that the target of
this query is always present and is not conditionally rendered. This
makes the result available earlier, in the ngOnInit lifecycle method.

So you need to remove { static: true } and put your code inside ngAfterViewInit.

@ViewChild('nav') nav!: NgbNav;

ngAfterViewInit(): void {
  //
}

Leave a Comment