Виртуальный скроллинг — это техника рендеринга только видимых элементов списка, что значительно улучшает производительность при работе с большими наборами данных.
Angular CDK предоставляет готовое решение для виртуального скроллинга.
npm install @angular/cdk
import { ScrollingModule } from '@angular/cdk/scrolling';
@NgModule({
imports: [
ScrollingModule
]
})
export class AppModule {}
<cdk-virtual-scroll-viewport itemSize="50" class="viewport">
<div *cdkVirtualFor="let item of items" class="item">
{{item}}
</div>
</cdk-virtual-scroll-viewport>
.viewport {
height: 500px;
width: 100%;
border: 1px solid #ccc;
}
.item {
height: 50px;
display: flex;
align-items: center;
padding: 0 16px;
box-sizing: border-box;
}
Основные параметры:
itemSize
: высота элемента в пикселях (обязательный параметр)minBufferPx
: минимальный буфер загрузки (сколько элементов загружать заранее)maxBufferPx
: максимальный буфер загрузки (сколько элементов держать в DOM после скролла)<cdk-virtual-scroll-viewport
itemSize="50"
minBufferPx="200"
maxBufferPx="400"
class="viewport">
...
</cdk-virtual-scroll-viewport>
Для элементов переменной высоты используйте autosize
:
import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling';
@Component({...})
export class MyComponent {
@ViewChild(CdkVirtualScrollViewport) viewport: CdkVirtualScrollViewport;
ngAfterViewInit() {
this.viewport.setRenderedRange({start: 0, end: 10});
}
}
И в шаблоне:
<cdk-virtual-scroll-viewport [itemSize]="undefined" class="viewport">
<div *cdkVirtualFor="let item of items; let i = index"
[style.height.px]="getItemHeight(i)">
{{item}}
</div>
</cdk-virtual-scroll-viewport>
Для оптимизации используйте trackBy как в обычном ngFor:
<div *cdkVirtualFor="let item of items; trackBy: trackByFn">
{{item.name}}
</div>
trackByFn(index: number, item: any): number {
return item.id;
}
Комбинация виртуального скроллинга с пагинацией:
@Component({...})
export class MyComponent {
items = [];
loading = false;
totalItems = 1000;
@ViewChild(CdkVirtualScrollViewport) viewport: CdkVirtualScrollViewport;
constructor() {
this.loadItems(0, 20);
}
ngAfterViewInit() {
this.viewport.scrolledIndexChange.subscribe(() => {
if (this.loading) return;
const end = this.viewport.getRenderedRange().end;
const total = this.viewport.getDataLength();
if (end === total && total < this.totalItems) {
this.loadMore();
}
});
}
loadMore() {
this.loading = true;
this.loadItems(this.items.length, 20).then(() => {
this.loading = false;
});
}
}
npm install ngx-virtual-scroller
Пример использования:
<virtual-scroller [items]="items" [parentScroll]="scroll.window">
<div *ngFor="let item of scroll.viewportItems">
{{item}}
</div>
</virtual-scroller>
Пример простой ручной реализации:
@Component({
selector: 'app-custom-virtual-scroll',
template: `
<div class="viewport" (scroll)="onScroll($event)">
<div class="phantom" [style.height.px]="totalHeight">
<div class="content" [style.transform]="transform">
<div *ngFor="let item of visibleItems" class="item">
{{item}}
</div>
</div>
</div>
</div>
`
})
export class CustomVirtualScrollComponent {
items = [...Array(1000).keys()];
itemHeight = 50;
visibleItemCount = 10;
totalHeight = this.items.length * this.itemHeight;
scrollTop = 0;
get startIndex(): number {
return Math.floor(this.scrollTop / this.itemHeight);
}
get visibleItems(): any[] {
return this.items.slice(
this.startIndex,
this.startIndex + this.visibleItemCount
);
}
get transform(): string {
return `translateY(${this.startIndex * this.itemHeight}px)`;
}
onScroll(event: Event): void {
this.scrollTop = (event.target as HTMLElement).scrollTop;
}
}
Используйте OnPush change detection
@Component({
changeDetection: ChangeDetectionStrategy.OnPush
})
Избегайте сложных вычислений в шаблонах
Оптимизируйте стили
Используйте will-change
и contain: strict
для элементов списка
Используйте trackBy
Для минимизации перерисовки элементов
Оптимизируйте обработку событий
Используйте debounceTime
для scroll-событий
fromEvent(this.viewport.elementRef.nativeElement, 'scroll')
.pipe(debounceTime(50))
.subscribe(() => {...});
Пример с Observable:
items$: Observable<any[]>;
constructor(private dataService: DataService) {
this.items$ = this.dataService.getLargeDataSet();
}
<cdk-virtual-scroll-viewport itemSize="50">
<div *cdkVirtualFor="let item of items$ | async">
{{item.name}}
</div>
</cdk-virtual-scroll-viewport>
виртуальный скроллинг в Angular эффективно реализуется через CDK Virtual Scroll, который предоставляет готовые оптимизированные компоненты. Для сложных сценариев можно использовать сторонние библиотеки или кастомные решения. Ключевые аспекты успешной реализации — правильная настройка размеров элементов, буферизации и оптимизация рендеринга.