Implement Angular CDK ScrollSpy
This challenge requires you to implement a scroll-spy functionality using Angular's CDK. Scroll-spy is a common UI pattern where navigation links highlight or become active as the user scrolls to the corresponding content section on the page. This enhances user experience by providing visual feedback on their current position.
Problem Description
Your task is to create a reusable Angular component that utilizes the ScrollingModule from the Angular CDK to implement a scroll-spy directive. This directive will monitor the scroll position of a designated container and activate corresponding navigation elements based on which content section is currently in view.
Key Requirements:
- Directive Creation: Create a directive that can be applied to any scrollable container (e.g., a
divwithoverflow: autooroverflow: scroll). - Content Section Identification: The directive should be able to identify distinct content sections within the scrollable container. These sections should be marked by a specific attribute or a defined CSS selector.
- Navigation Link Association: Each content section should have an associated navigation link (e.g., an
<a>tag in a separate navigation area). The directive needs a way to map these navigation links to their corresponding content sections. - Active State Management: When a content section scrolls into view (partially or fully, depending on configuration), its associated navigation link should receive an "active" class. When that section scrolls out of view, the "active" class should be removed.
- Configurability: Allow for basic configuration, such as the CSS selector for content sections and the CSS class to apply for the active state.
- Performance: The implementation should be performant, especially for pages with many scrollable sections.
Expected Behavior:
As the user scrolls the container, the navigation link corresponding to the section that is most prominently visible in the viewport should gain an "active" class. As the user scrolls past that section, the "active" class should be removed and applied to the next relevant section.
Edge Cases:
- What happens when multiple sections are in view simultaneously? (Consider a strategy, e.g., the highest section in the viewport).
- What happens when the scrollable container is very short or has no content?
- What happens when navigation links are not present or not correctly associated?
- Initial state: Which link should be active when the page loads?
Examples
Example 1: Basic Scroll Spy
Consider the following HTML structure within an Angular component:
<div class="scroll-container" appScrollSpySectionSelector="[data-section]" appScrollSpyActiveClass="active-link">
<nav>
<a href="#section1" data-link-to="section1">Section 1</a>
<a href="#section2" data-link-to="section2">Section 2</a>
<a href="#section3" data-link-to="section3">Section 3</a>
</nav>
<div class="content">
<div id="section1" data-section="section1">Content for Section 1...</div>
<div id="section2" data-section="section2">Content for Section 2...</div>
<div id="section3" data-section="section3">Content for Section 3...</div>
</div>
</div>
And some basic CSS:
.scroll-container {
height: 300px;
overflow-y: scroll;
position: relative; /* Important for some intersection observer implementations */
}
nav a {
margin: 5px;
text-decoration: none;
color: black;
}
.active-link {
font-weight: bold;
color: blue;
}
.content div {
height: 200px; /* Make content sections scrollable */
margin-bottom: 50px;
}
Input:
The user scrolls the .scroll-container such that the #section2 div is primarily in view.
Output:
The <a> tag with data-link-to="section2" will have the class active-link applied to it. The other links will not have this class.
Explanation:
The appScrollSpySectionSelector directive identifies the divs with the data-section attribute as content sections. The appScrollSpyActiveClass directive applies the active-link class. The directive observes the scroll position and, based on the data-link-to attribute on the <a> tags and the data-section attribute on the content divs, activates the correct link.
Example 2: Different Section Selector and Navigation Mapping
<div class="scroll-viewport" appScrollSpySectionSelector=".content-block" appScrollSpyActiveClass="highlight">
<div class="navigation">
<button data-nav-target="intro">Intro</button>
<button data-nav-target="features">Features</button>
</div>
<div class="scrollable-content">
<div class="content-block" id="intro">Introduction...</div>
<div class="content-block" id="features">Features...</div>
</div>
</div>
Input:
The user scrolls the .scroll-viewport such that the #features div is in view.
Output:
The <button> with data-nav-target="features" will have the class highlight applied.
Explanation:
This example demonstrates using a different selector (.content-block) for sections and a different attribute (data-nav-target) for linking to navigation elements. The core logic of tracking scroll position and applying an active class remains the same.
Constraints
- The solution should be implemented in TypeScript.
- You must utilize the Angular CDK
ScrollingModule. Specifically, exploreCdkScrollableand potentiallyCdkObserveContentor related functionalities. - The directive should be designed to be declarative and applied to existing DOM elements.
- Avoid direct DOM manipulation outside of Angular's rendering lifecycle where possible.
- The solution should be reasonably performant, handling a moderate number of scrollable sections (e.g., up to 50) without significant lag.
Notes
- Consider how you will map navigation elements to content sections. A common approach is using data attributes.
- Think about the most efficient way to detect when a section is in view. The Angular CDK might provide utilities for this.
- The "active" state should ideally be managed by adding/removing a CSS class.
- Pay attention to the initial state of the active link when the component loads.
- Feel free to define your own attribute names for section selectors and navigation targets if you prefer, but document them clearly.