CDK Scrollable Container

Scrollable Container

Overview API Examples

The Scroll Watcher feature provides a centralized system for monitoring and reacting to scroll events occurring on the main browser window or within specific designated scrollable container elements in an Angular application. It allows developers to subscribe to a unified stream of scroll notifications, identify the source of the scroll, and apply performance optimizations like audit time.

Technical Overview

This feature is composed of two primary parts: the LmnScrollWatcherService and the LmnScrollableContainerDirective.

LmnScrollWatcherService is an injectable root service that acts as the central hub for all scroll event monitoring. It manages subscriptions to native scroll events from the browser window and from any elements marked by the LmnScrollableContainerDirective. The service exposes a primary method, listenScroll(), which returns an Observable. This Observable emits either the instance of the LmnScrollableContainerDirective that was scrolled or void if the scroll event originated from the window. The service intelligently adds or removes the window scroll listener based on whether there are active subscribers, and it manages the registration and unregistration of individual scrollable containers. State management for scroll watchers (like active containers and subscription counts) is handled internally using the getFeatureState utility.

LmnScrollableContainerDirective is an attribute directive ([lmnScrollableContainer]) used to designate specific HTML elements as scrollable areas that should be monitored by the LmnScrollWatcherService. Upon initialization, the directive automatically registers itself with the service, providing its unique ID (either auto-generated or specified via input) and an Observable of its native scroll events. When the directive is destroyed, it automatically unregisters itself from the service to prevent memory leaks.

Key Features

  • Unified Scroll Event Stream: The LmnScrollWatcherService provides a single listenScroll() method to subscribe to scroll events from both the window and any registered LmnScrollableContainerDirective instances.
  • Source Identification: Emitted values from listenScroll() specify the source: either the LmnScrollableContainerDirective instance or void for window scrolls.
  • Specific Container Filtering: The listenScroll() method can optionally take an id parameter to only receive notifications for a specific registered scrollable container.
  • Performance Optimization:
    • An auditTimeInMS parameter can be passed to listenScroll() to limit the rate of emitted scroll events.
    • The native window scroll listener is only active when there are subscribers to listenScroll().
    • Scroll event listeners on individual elements (within the directive) are run outside Angular's zone (NgZone.runOutsideAngular) to prevent unnecessary change detection cycles.
  • Easy Element Registration: The LmnScrollableContainerDirective simplifies the process of marking and registering elements for scroll monitoring.
  • Automatic Lifecycle Management: The directive handles its own registration with the service on init and unregistration on destroy.
  • Observable for Element-Specific Scrolls: The directive itself provides an elementScrolled() method and a streamScrollEvent output that gives direct access to the scroll event observable of its host element.

When to Use

Use the Scroll Watcher feature when:

  • You need to implement features that react to scrolling, such as infinite scrolling, lazy-loading images/components, parallax effects, or dynamically changing UI elements based on scroll position (e.g., sticky headers, "back to top" buttons).
  • You require a centralized way to listen to scroll events from multiple potential sources (window and various elements).
  • You need to identify which specific registered container has scrolled.
  • Performance is a concern, and you need to control the frequency of scroll event handling using auditTime.
  • You want a declarative way (using the directive) to mark elements as scrollable and have them automatically integrate with a scroll monitoring system.

Implementation

LmnScrollableContainerDirective

Apply the [lmnScrollableContainer] directive to an HTML element to mark it as a scrollable container that should be monitored by the LmnScrollWatcherService.

Inputs:

  • lmnScrollableContainerId (alias id): Optional (string, default: auto-generated unique ID like 'lmn-scrollable-container-X'). A unique identifier for the scrollable container. This ID is used when registering with the LmnScrollWatcherService and can be used to filter events in listenScroll().

Outputs:

  • streamScrollEvent: Emits an Observable<Event> once, providing the stream of native scroll events from the host element. This can be used for direct, element-specific scroll handling if needed.

Public Properties (on directive instance):

  • element: HTMLElement: The native HTML element to which the directive is applied.
  • id(): string: A signal accessor for the directive's unique ID.

Public Methods (on directive instance):

  • elementScrolled(): Observable<Event>: Returns an Observable that emits native Event objects whenever the host element is scrolled. The underlying observable is shared and replayed.

LmnScrollWatcherService

The LmnScrollWatcherService is the central service for listening to all monitored scroll events.

Key Public Methods

  • listenScroll(auditTimeInMS?: number): Observable<LmnScrollableContainerDirective | void>
    • Subscribes to scroll events from both the window and all registered LmnScrollableContainerDirective instances.
    • auditTimeInMS?: Optional (number, default: 0). If greater than 0, scroll events are emitted at most once per this interval.
    • Returns an Observable. When the window scrolls, it emits void. When a registered container scrolls, it emits the instance of that LmnScrollableContainerDirective.
  • listenScroll(auditTimeInMS?: number, id?: string): Observable<LmnScrollableContainerDirective | void>
    • Overload that allows listening only to scroll events from a specific registered container (by its id) or the window.
    • id?: Optional (string). If provided, only events from the container with this ID or window scroll events (if no container matches) will be emitted.
    • If a container with the given id scrolls, its directive instance is emitted. Window scrolls still emit void.
  • register(scrollableContainer: LmnScrollableContainerDirective): void
    • Called by the LmnScrollableContainerDirective during its initialization to register itself with the service. Developers typically do not call this directly.
  • unregister(id: string): void:
    • Called by the LmnScrollableContainerDirective when it's destroyed to unregister itself. Developers typically do not call this directly.