Table of Contents
Overview
This document explains why we chose to implement manual virtual scrolling instead of using the existing VirtualScrollDirective in the ng-vui-grid component. Updated for Angular 20+ standalone architecture.
Architecture Update
With the migration to standalone components and signal-based state management, our virtual scrolling implementation has been further optimized for:
- Better change detection performance
- Reduced memory footprint
- Improved TypeScript type safety
- Enhanced compatibility with Angular 20+ features
The VirtualScrollDirective
What It Is
The VirtualScrollDirective is a custom directive located at:
/lib/common-lib/src/ng-vui-grid/directives/virtual-scroll.directive.ts
How It's Designed to Work
@Directive({
selector: '[ngVirtualScroll]',
standalone: true
})
export class VirtualScrollDirective {
@Input() items: any[] = [];
@Input() itemHeight: number | ((item: any, index: number) => number) = 40;
@Input() bufferSize = 5;
@Input() enableSmoothScrolling = true;
@Output() viewportChange = new EventEmitter<VirtualScrollViewport>();
}
Intended Usage
<div ngVirtualScroll
[items]="dataArray"
[itemHeight]="48"
[bufferSize]="5"
(viewportChange)="onViewportChange($event)">
</div>
Why We Didn't Use VirtualScrollDirective
1. Grid-Specific Requirements
Problem
The directive was designed for generic virtualization, but grids have unique needs.
Our Requirements
- Column-based rendering - Grids render rows with multiple columns
- Header synchronization - Headers must stay aligned with data columns
- Row selection - Complex selection state management
- Cell templates - Custom cell rendering with templates
- Horizontal scrolling - Both vertical AND horizontal virtualization
// The directive assumes simple item rendering
// It doesn't handle complex grid structures like:
<div class="grid-row">
<div class="grid-cell" *ngFor="let column of columns">
<!-- Complex cell content with templates -->
</div>
</div>
2. Integration with Data Streaming
Problem
The directive doesn't integrate with our data streaming service.
Our Streaming Integration
onVirtualScroll(event: Event): void {
const target = event.target as HTMLElement;
const scrollTop = target.scrollTop;
const rowHeight = this.getRowHeight();
// Calculate visible range
const startIndex = Math.floor(scrollTop / rowHeight);
const endIndex = Math.ceil((scrollTop + viewportHeight) / rowHeight);
// 🎯 Critical: Update streaming service
if (this._isStreamingEnabled()) {
this.streamingService.updateViewport(startIndex, endIndex);
}
}
- No hooks for streaming integration
- Can't trigger chunk loading based on scroll position
- No coordination with server-side operations
3. Performance Control
Problem
The directive abstracts away performance-critical details.
What We Need to Control
- Exact DOM element count - Limit to 15-20 elements max for 10M records
- Transform calculations - Hardware acceleration with
translate3d - Render cycle timing - When to trigger re-renders
- Memory management - Coordinate with chunk cleanup
getVirtualVisibleItems(): VirtualScrollItem[] {
const scrollTop = this._virtualScrollTop();
const viewportHeight = this._virtualViewportHeight();
const rowHeight = this.getRowHeight();
const bufferSize = this.getVirtualBufferSize();
// 🎯 Precise control over what gets rendered
const startIndex = Math.max(0, Math.floor(scrollTop / rowHeight) - bufferSize);
const endIndex = Math.min(
this._rowData().length - 1,
Math.ceil((scrollTop + viewportHeight) / rowHeight) + bufferSize
);
// Return only exactly what we need
return this.createVirtualItems(startIndex, endIndex);
}
4. Template Flexibility
Problem
The directive controls the rendering internally.
Our Grid Template Needs
<!-- ✅ Our flexible approach -->
@for (virtualItem of getVirtualVisibleItems(); track virtualItem.index) {
<div class="grid-row" [style.transform]="getTransform(virtualItem.index)">
<!-- Selection column -->
<div class="grid-cell" *ngIf="enableSelection">
<input type="checkbox" [checked]="isRowSelected(virtualItem.data)">
</div>
<!-- Dynamic columns -->
<div class="grid-cell"
*ngFor="let column of visibleColumns(); trackBy: trackByField">
<!-- Custom templates -->
@if (column.cellTemplate) {
<ng-container [ngTemplateOutlet]="column.cellTemplate"
[ngTemplateOutletContext]="getContext(virtualItem, column)">
</ng-container>
} @else {
{{ formatCellValue(virtualItem.data, column) }}
}
</div>
</div>
}
<!-- ❌ Limited template control --> <div ngVirtualScroll [items]="data"> <!-- Directive controls rendering - can't customize grid structure --> </div>
5. Server-Side Integration
Problem
The directive doesn't understand server-side operations.
Our Integration
// Manual implementation allows deep integration
private setupVirtualScrolling(): void {
if (this._isVirtualScrollEnabled()) {
// 🎯 Coordinate with multiple services
// 1. Server-side operations
if (this._isServerSideEnabled()) {
this.serverSideService.configure({...});
}
// 2. Data streaming
if (this._isStreamingEnabled()) {
this.streamingService.configure({...});
}
// 3. Memory management
this.setupMemoryMonitoring();
}
}
6. Debugging and Maintenance
Problem
Directive abstracts away implementation details.
Benefits of Manual Implementation
- Full visibility into render cycles
- Performance profiling at each step
- Custom optimizations for specific use cases
- Easier debugging when issues occur
// 🎯 Detailed performance monitoring
getVirtualScrollStats() {
return {
visibleItems: this.getVirtualVisibleItems().length,
scrollTop: this._virtualScrollTop(),
renderTime: this.lastRenderTime,
memoryUsage: this.getMemoryUsage(),
fps: this.getCurrentFPS()
};
}
Feature Comparison
| Aspect | VirtualScrollDirective | Manual Implementation |
|---|---|---|
| Ease of Use | ✅ Simple to implement | ⚠️ More complex setup |
| Grid Integration | ❌ Generic, limited | ✅ Perfect for grids |
| Performance Control | ❌ Abstracted away | ✅ Full control |
| Streaming Integration | ❌ No support | ✅ Native integration |
| Template Flexibility | ❌ Limited | ✅ Complete freedom |
| Debugging | ❌ Black box | ✅ Full visibility |
| Memory Management | ❌ Basic | ✅ Advanced coordination |
| Server-Side Ops | ❌ No integration | ✅ Seamless integration |
| Enterprise Features | ❌ Limited | ✅ All features supported |
Alternative Approach: Enhanced Directive
If we wanted to use the directive approach, we would need to significantly enhance it:
@Directive({
selector: '[ngGridVirtualScroll]', // Grid-specific
standalone: true
})
export class GridVirtualScrollDirective {
// Grid-specific inputs
@Input() columns: ColumnDefinition[] = [];
@Input() enableSelection = false;
@Input() streamingService?: DataStreamingService;
@Input() serverSideService?: ServerSideOperationsService;
// Enhanced outputs
@Output() rowClick = new EventEmitter<any>();
@Output() cellClick = new EventEmitter<any>();
@Output() streamingUpdate = new EventEmitter<any>();
// Grid-specific methods
renderGridRow(item: any, columns: ColumnDefinition[]): HTMLElement {
// Complex grid row rendering
}
integrateWithStreaming(): void {
// Streaming service integration
}
handleServerSideOperations(): void {
// Server-side coordination
}
}
But this would essentially recreate what we built manually, with added abstraction overhead.
Conclusion
We chose manual virtual scrolling implementation because:
- Grid-Specific Optimization - Designed specifically for data grid needs
- Performance Control - Direct control over every aspect of rendering
- Integration Capabilities - Seamless integration with streaming and server-side services
- Enterprise Requirements - Supports all advanced features needed for 10M+ records
- Debugging & Maintenance - Full visibility for troubleshooting and optimization
The VirtualScrollDirective is a good general-purpose solution, but for enterprise-grade data grids handling massive datasets, manual implementation provides the control and integration capabilities we need.
Usage Recommendations
Use VirtualScrollDirective for:
- Simple lists
- Basic virtualization needs
- Rapid prototyping
- Non-grid components
Use Manual Implementation for:
- Data grids
- Complex rendering
- Streaming integration
- Enterprise applications
- Performance-critical scenarios