<link rel="stylesheet" href="styles-3GBANQL6.css">
← Back to blog

Angular

Manage Active Navigation Links with Angular Directives

Manage Active Navigation Links with Angular Directives

Effortlessly Highlight Your Active Navigation Link with the SectionVisibleDirective

Navigating a website should be a smooth and intuitive experience. A key part of this is providing clear visual cues to users about their current location on the page. One common way to achieve this is by highlighting the active navigation link as they scroll through different sections.

The SectionVisibleDirective offers a simple and elegant solution for this in your Angular applications. This directive leverages the power of the Intersection Observer API to detect when a section comes into view and updates the navigation menu accordingly.

How it Works

The SectionVisibleDirective works by observing a specific HTML element, typically a section or div, and triggering an action when that element becomes visible within the viewport. Here's a breakdown of the process:

  1. Attach the Directive: You apply the SectionVisibleDirective to the desired HTML element using the sectionVisible attribute. You also provide a unique sectionName as an input to the directive.
  2. Intersection Observer: The directive utilizes the Intersection Observer API to monitor the visibility of the target element. When the element enters the viewport (or reaches a specified threshold), the observer triggers a callback function.
  3. Update Navigation: Inside the callback function, the directive communicates with your navigation service (e.g., NavService) and passes the sectionName. The navigation service then uses this information to highlight the corresponding link in your navigation menu.

Directive Code Example


import {
  AfterViewInit,
  Directive,
  ElementRef,
  inject,
  Inject,
  Input,
  OnDestroy,
  PLATFORM_ID,
} from '@angular/core';
import { isPlatformBrowser } from '@angular/common';

@Directive({
  selector: '[sectionVisible]',
  standalone: true,
})
export class SectionVisibleDirective implements AfterViewInit, OnDestroy {
  readonly navService: NavService = inject(NavService);
  readonly elementRef: ElementRef = inject(ElementRef);
  private obs!: IntersectionObserver;

  @Input() sectionName!: string;

  constructor(@Inject(PLATFORM_ID) private platform: Object) {}

  ngAfterViewInit() {
    if (isPlatformBrowser(this.platform)) {
      this.hasIntersectionObserver()
        ? this.observerIntersection()
        : this.updateNavMenu();
    }
  }

  private hasIntersectionObserver() {
    return window && 'IntersectionObserver' in window;
  }

  private observerIntersection() {
    this.obs = new IntersectionObserver((entries) => {
      entries.forEach(({ isIntersecting }) => {
        if (isIntersecting) {
          this.updateNavMenu();
        }
      });
    }, { threshold: 0.5 });
    this.obs.observe(this.elementRef.nativeElement);
  }

  private updateNavMenu() {
    this.navService.setMenuSignal(this.sectionName);
  }

  ngOnDestroy(): void {
    this.obs?.disconnect();
  }
}

Fragment-based Routes for One-Pagers

One of the most common use cases for this directive is in one-page websites, where different sections of the page are navigated using fragment-based URLs such as #about, #services, or #contact. With the SectionVisibleDirective, you can seamlessly highlight the correct navigation link when the user scrolls to the corresponding section, making it clear which part of the page they are currently viewing.

HTML Example:

<nav>
  <a routerLink="/home#home" sectionVisible sectionName="home">Home</a>
  <a routerLink="/home#about" sectionVisible sectionName="about">About</a>
  <a routerLink="/home#contact" sectionVisible sectionName="contact">Contact</a>
</nav>

<section id="home" sectionVisible sectionName="home">
  <h2>Home Section</h2>
  <!-- Content for the home section -->
</section>

<section id="about" sectionVisible sectionName="about">
  <h2>About Section</h2>
  <!-- Content for the about section -->
</section>

<section id="contact" sectionVisible sectionName="contact">
  <h2>Contact Section</h2>
  <!-- Content for the contact section -->
</section>

Explanation:

  • The sectionVisible directive is applied to each <a> tag in the navigation and to each <section> on the page.
  • The sectionName input is set to a unique identifier for each section (e.g., "home", "about", "contact").
  • When a section becomes visible, the directive notifies the NavService, which then highlights the corresponding navigation link.

Usage of Threshold in IntersectionObserver

The threshold in the Intersection Observer API determines how much of an element must be visible before the observer callback is executed. It is set to a value between 0 and 1, representing the percentage of the element's visibility in the viewport. In the SectionVisibleDirective, we set the threshold to 0.5, meaning the callback will trigger when at least 50% of the section is visible.

Adjusting the threshold allows for greater control over when an action occurs. For example, setting a threshold of 1.0 would mean the callback only fires when the entire section is visible, while a threshold of 0.0 would trigger the callback as soon as the section starts to appear.

Importantly, IntersectionObserver operates asynchronously, meaning it doesn't block the main thread, unlike traditional scroll event listeners that require constant monitoring. This results in improved performance, especially for larger or more complex websites.

Enhancements for Other Use Cases

While the SectionVisibleDirective is perfect for one-page applications and fragment-based routes, its use can easily be expanded to other purposes. For instance, you could adapt it to:

  • Lazy Loading Images: Trigger the loading of images only when they come into view, reducing the initial page load time and improving overall performance.
  • Tracking User Behavior: Use it to log analytics events when users reach certain sections of your page, allowing you to gather insights about how they interact with your content.
  • Dynamic Animations: Start animations or other interactive elements only when they are about to come into the viewport, creating a more engaging user experience.

Example: Lazy Loading with IntersectionObserver

To demonstrate how easily this directive can be adapted, here's an example where we extend it to lazy-load images. The directive would load the image only when it comes into the viewport, improving performance for image-heavy pages.

<img [src]="placeholder" [data-src]="actualSrc" sectionVisible sectionName="image">
  

In this example, the data-src attribute contains the actual image source, which will be set as the src attribute when the image comes into view, replacing the placeholder.

Benefits

  • Automatic Highlighting: No need for manual event listeners or complex scroll calculations.
  • Improved User Experience: Provides clear visual feedback to users, enhancing navigation clarity.
  • Performance Optimization: The Intersection Observer runs asynchronously, ensuring that the main thread remains unblocked.
  • Easy Integration: Simple to implement and integrate with your existing navigation structure or for other use cases like lazy loading and analytics tracking.

The SectionVisibleDirective is a powerful tool for creating a more user-friendly and engaging website experience. By automatically highlighting active navigation links, lazy-loading resources, or tracking user interactions, you can guide users seamlessly through your content and improve their overall browsing experience.

LET'S BUILD WHAT'S NEXT

Ready to turn your idea into a real solution?

Let's discuss how we can help you create value with technology.

Let's Talk