The Angular CDK Scrolling module provides advanced scrolling utilities including virtual scrolling for large datasets, global scroll event management, and viewport measurements.
Component for efficiently rendering large lists by only creating DOM elements for visible items.
/**
* Viewport component for virtual scrolling
*/
@Component({
selector: 'cdk-virtual-scroll-viewport'
})
class CdkVirtualScrollViewport implements OnInit, OnDestroy {
/**
* Size of each item in pixels
*/
@Input() itemSize: number;
/**
* Minimum buffer size in pixels before viewport
*/
@Input() minBufferPx: number = 100;
/**
* Maximum buffer size in pixels after viewport
*/
@Input() maxBufferPx: number = 200;
/**
* Scroll orientation
*/
@Input() orientation: 'horizontal' | 'vertical' = 'vertical';
/**
* Event emitted when scrolled index changes
*/
@Output() scrolledIndexChange: EventEmitter<number> = new EventEmitter<number>();
/**
* Element reference for the viewport
*/
elementRef: ElementRef<HTMLElement>;
/**
* Get the size of the viewport
* @returns Viewport size in pixels
*/
getViewportSize(): number;
/**
* Get the range of items currently rendered
* @returns Range of visible items
*/
getRenderedRange(): ListRange;
/**
* Get the total number of data items
* @returns Data length
*/
getDataLength(): number;
/**
* Measure the current scroll offset
* @param from - Direction to measure from
* @returns Scroll offset in pixels
*/
measureScrollOffset(from?: 'top' | 'left' | 'right' | 'bottom' | 'start' | 'end'): number;
/**
* Scroll to a specific item index
* @param index - Index to scroll to
* @param behavior - Scroll behavior
*/
scrollToIndex(index: number, behavior?: ScrollBehavior): void;
/**
* Scroll to a specific offset
* @param offset - Offset in pixels
* @param behavior - Scroll behavior
*/
scrollToOffset(offset: number, behavior?: ScrollBehavior): void;
/**
* Measure the size of a range of items
* @param range - Range to measure
* @returns Size in pixels
*/
measureRangeSize(range: ListRange): number;
/**
* Check the viewport size and update if needed
*/
checkViewportSize(): void;
}
/**
* Range of items in a list
*/
interface ListRange {
/** Start index */
start: number;
/** End index */
end: number;
}Directive for repeating templates in virtual scroll viewports.
/**
* Directive for repeating items in virtual scroll viewport
* @template T Type of items being repeated
*/
@Directive({
selector: '[cdkVirtualFor][cdkVirtualForOf]'
})
class CdkVirtualForOf<T> implements OnChanges, OnDestroy {
/**
* Data source for the virtual for loop
*/
@Input() cdkVirtualForOf: DataSource<T> | Observable<T[]> | NgIterable<T> | null | undefined;
/**
* Track by function for item identity
*/
@Input() cdkVirtualForTrackBy: TrackByFunction<T>;
/**
* Template to use for each item
*/
@Input() cdkVirtualForTemplate: TemplateRef<CdkVirtualForOfContext<T>>;
/**
* Number of templates to cache
*/
@Input() cdkVirtualForTemplateCacheSize: number = 20;
}
/**
* Context for virtual for template
* @template T Type of data item
*/
interface CdkVirtualForOfContext<T> {
/** The data item */
$implicit: T;
/** The data source */
cdkVirtualForOf: DataSource<T> | Observable<T[]> | NgIterable<T>;
/** Index of the item */
index: number;
/** Total count of items */
count: number;
/** Whether this is the first item */
first: boolean;
/** Whether this is the last item */
last: boolean;
/** Whether the index is even */
even: boolean;
/** Whether the index is odd */
odd: boolean;
}Usage Example:
<cdk-virtual-scroll-viewport itemSize="50" class="viewport">
<div *cdkVirtualFor="let item of items">{{ item }}</div>
</cdk-virtual-scroll-viewport>@Component({
selector: 'app-virtual-list',
template: `
<cdk-virtual-scroll-viewport itemSize="50" class="viewport">
<div *cdkVirtualFor="let item of items; trackBy: trackByFn" class="item">
{{ item.name }}
</div>
</cdk-virtual-scroll-viewport>
`,
styles: [`
.viewport {
height: 400px;
width: 100%;
}
.item {
height: 50px;
display: flex;
align-items: center;
padding: 0 16px;
}
`]
})
export class VirtualListComponent {
items = Array.from({length: 100000}, (_, i) => ({ name: `Item ${i}` }));
trackByFn(index: number, item: any) {
return item.name;
}
}Strategies for determining how virtual scrolling behaves.
/**
* Base interface for virtual scroll strategies
*/
interface VirtualScrollStrategy {
/** Observable that emits when scrolled index changes */
scrolledIndexChange: Observable<number>;
/** Attach strategy to a viewport */
attach(viewport: CdkVirtualScrollViewport): void;
/** Detach strategy from viewport */
detach(): void;
/** Called when content is scrolled */
onContentScrolled(): void;
/** Called when data length changes */
onDataLengthChanged(): void;
/** Called when content is rendered */
onContentRendered(): void;
/** Called when rendered offset changes */
onRenderedOffsetChanged(): void;
/** Scroll to a specific index */
scrollToIndex(index: number, behavior: ScrollBehavior): void;
}
/**
* Virtual scroll strategy for fixed-size items
*/
class FixedSizeVirtualScrollStrategy implements VirtualScrollStrategy {
/**
* Create fixed size strategy
* @param itemSize - Size of each item in pixels
* @param minBufferPx - Minimum buffer size
* @param maxBufferPx - Maximum buffer size
*/
constructor(itemSize: number, minBufferPx: number, maxBufferPx: number);
scrolledIndexChange: Observable<number>;
attach(viewport: CdkVirtualScrollViewport): void;
detach(): void;
onContentScrolled(): void;
onDataLengthChanged(): void;
onContentRendered(): void;
onRenderedOffsetChanged(): void;
scrollToIndex(index: number, behavior: ScrollBehavior): void;
}
/**
* Virtual scroll strategy for variable-size items
*/
class AutoSizeVirtualScrollStrategy implements VirtualScrollStrategy {
/**
* Create auto size strategy
* @param minBufferPx - Minimum buffer size
* @param maxBufferPx - Maximum buffer size
*/
constructor(minBufferPx: number, maxBufferPx: number);
scrolledIndexChange: Observable<number>;
attach(viewport: CdkVirtualScrollViewport): void;
detach(): void;
onContentScrolled(): void;
onDataLengthChanged(): void;
onContentRendered(): void;
onRenderedOffsetChanged(): void;
scrollToIndex(index: number, behavior: ScrollBehavior): void;
}
/**
* Injection token for virtual scroll strategy
*/
const VIRTUAL_SCROLL_STRATEGY: InjectionToken<VirtualScrollStrategy>;Service for global scroll event management.
/**
* Service for managing scroll events globally
*/
class ScrollDispatcher implements OnDestroy {
/**
* Observable that emits when any scrollable area is scrolled
* @param auditTimeInMs - Time to throttle events
* @returns Observable of scroll events
*/
scrolled(auditTimeInMs?: number): Observable<CdkScrollable | void>;
/**
* Observable that emits when ancestors of an element are scrolled
* @param element - Element to check ancestors for
* @param auditTimeInMs - Time to throttle events
* @returns Observable of ancestor scroll events
*/
ancestorScrolled(
element: ElementRef | Element,
auditTimeInMs?: number
): Observable<CdkScrollable | void>;
/**
* Register a scrollable area
* @param scrollable - Scrollable to register
*/
register(scrollable: CdkScrollable): void;
/**
* Deregister a scrollable area
* @param scrollable - Scrollable to deregister
*/
deregister(scrollable: CdkScrollable): void;
/**
* Clean up resources
*/
ngOnDestroy(): void;
}Directive for making elements report scroll events to the ScrollDispatcher.
/**
* Directive for registering scrollable elements
*/
@Directive({
selector: '[cdk-scrollable], [cdkScrollable]'
})
class CdkScrollable implements OnInit, OnDestroy {
/**
* Event emitted when element is scrolled
*/
@Output() elementScrolled: EventEmitter<void> = new EventEmitter<void>();
/**
* Get the element reference
* @returns ElementRef for the scrollable element
*/
getElementRef(): ElementRef<HTMLElement>;
/**
* Scroll to a position
* @param options - Scroll options
*/
scrollTo(options: ExtendedScrollToOptions): void;
/**
* Measure scroll offset from a direction
* @param from - Direction to measure from
* @returns Scroll offset in pixels
*/
measureScrollOffset(from: 'top' | 'left' | 'right' | 'bottom' | 'start' | 'end'): number;
}
/**
* Extended scroll options interface
*/
interface ExtendedScrollToOptions extends ScrollToOptions {
left?: number;
top?: number;
behavior?: ScrollBehavior;
}Service for measuring viewport dimensions and scroll position.
/**
* Service for measuring viewport properties
*/
class ViewportRuler implements OnDestroy {
/**
* Get the viewport size
* @returns Viewport dimensions
*/
getViewportSize(): Readonly<{width: number; height: number}>;
/**
* Get the viewport scroll position
* @returns Scroll position
*/
getViewportScrollPosition(): Readonly<{top: number; left: number}>;
/**
* Get the viewport rectangle
* @returns Viewport rectangle with position and dimensions
*/
getViewportRect(): Readonly<{
top: number;
left: number;
bottom: number;
right: number;
height: number;
width: number;
}>;
/**
* Observable that emits when viewport size or scroll position changes
* @param throttleTime - Time to throttle change events
* @returns Observable of viewport change events
*/
change(throttleTime?: number): Observable<Event>;
/**
* Clean up resources
*/
ngOnDestroy(): void;
}/**
* Angular module for scrolling functionality
*/
@NgModule({
declarations: [
CdkScrollable,
CdkVirtualScrollViewport,
CdkVirtualForOf
],
exports: [
CdkScrollable,
CdkVirtualScrollViewport,
CdkVirtualForOf
]
})
class ScrollingModule {}interface ListItem {
id: number;
title: string;
content: string;
}
@Component({
selector: 'app-large-list',
template: `
<cdk-virtual-scroll-viewport
itemSize="72"
class="list-viewport"
(scrolledIndexChange)="onScrolledIndexChange($event)">
<div
*cdkVirtualFor="let item of items; trackBy: trackByFn; templateCacheSize: 50"
class="list-item">
<h3>{{ item.title }}</h3>
<p>{{ item.content }}</p>
</div>
</cdk-virtual-scroll-viewport>
`,
styles: [`
.list-viewport {
height: 500px;
width: 100%;
border: 1px solid #ccc;
}
.list-item {
height: 72px;
padding: 12px;
border-bottom: 1px solid #eee;
display: flex;
flex-direction: column;
justify-content: center;
}
`]
})
export class LargeListComponent {
items: ListItem[] = Array.from({length: 50000}, (_, i) => ({
id: i,
title: `Item ${i}`,
content: `Content for item ${i}`
}));
trackByFn(index: number, item: ListItem): number {
return item.id;
}
onScrolledIndexChange(index: number): void {
console.log('Scrolled to index:', index);
}
}import { VIRTUAL_SCROLL_STRATEGY } from '@angular/cdk/scrolling';
class CustomVirtualScrollStrategy implements VirtualScrollStrategy {
scrolledIndexChange = new Subject<number>();
constructor(private itemHeight: number) {}
attach(viewport: CdkVirtualScrollViewport): void {
// Custom attach logic
}
detach(): void {
// Custom detach logic
}
onContentScrolled(): void {
// Custom scroll handling
}
// ... implement other required methods
}
@Component({
selector: 'app-custom-virtual-scroll',
providers: [{
provide: VIRTUAL_SCROLL_STRATEGY,
useValue: new CustomVirtualScrollStrategy(50)
}],
template: `
<cdk-virtual-scroll-viewport class="viewport">
<div *cdkVirtualFor="let item of items">{{ item }}</div>
</cdk-virtual-scroll-viewport>
`
})
export class CustomVirtualScrollComponent {
items = Array.from({length: 10000}, (_, i) => `Item ${i}`);
}import { ScrollDispatcher } from '@angular/cdk/scrolling';
@Component({
selector: 'app-scroll-monitor'
})
export class ScrollMonitorComponent implements OnInit, OnDestroy {
private scrollSubscription?: Subscription;
constructor(private scrollDispatcher: ScrollDispatcher) {}
ngOnInit(): void {
// Monitor all scroll events with 100ms throttling
this.scrollSubscription = this.scrollDispatcher.scrolled(100)
.subscribe((scrollable) => {
if (scrollable) {
console.log('Scrollable element scrolled:', scrollable);
} else {
console.log('Window scrolled');
}
});
}
ngOnDestroy(): void {
this.scrollSubscription?.unsubscribe();
}
}