Observable-based virtual scroll implementation in Angular.
npm i -S od-virtualscroll
Let's you scroll efficiently through a humongous list/grid of items (with single predefined height) by recycling components and minimizing component updates.
- Handles resizing
- Efficient
- Displays necessary amount of rows
- Optimal updates on data change or resize
- Supports tiling
- Supports fixed number of columns
- Reactive component
- Observable interface for most parts
- Supports AoT
- API
- Subscribe to key component observables
- Plus
- Debounce scrolling / resizing
- Set scroll position, focus row or item via index
- Customizable equality checking
- A lot of code samples
- Module formats
- Ships FESM5 and FESM15
- Ships ES5/UMD, ES5/ES2015 and ES2015/ES2015 exports (
{{target}}/{{module}}
)
All examples are written in Angular 4 and provided in separate repositories to keep this repository simple.
Name | Description |
---|---|
od-vsstatic / Demo | Static example with 100k cells. Ideal for performance analysis and GC testing |
od-vsdynamic / Demo | Scroll through GIFs, without the risk of a CPU meltdown (GIPHY API) |
od-vslist / Demo | Render only 1 cell per row with dynamic width (randomuser API) |
od-vsadvanced / Demo | Shows more advanced API features and utilizes the auxiliary debug module |
od-vscolors / Demo | Just for fun |
However, this repository also holds a minimalistic demo, to allow local development and AoT compilation.
Import the module and specify the cell and container styling (traditional layout or flexbox/... your choice).
// app.module.ts
import {NgModule} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';
import {VirtualScrollModule} from 'od-virtualscroll';
import {AppComponent} from './app.component';
@NgModule({
bootstrap: [AppComponent],
declarations: [AppComponent],
imports: [
BrowserModule,
VirtualScrollModule
]
})
export class AppModule {}
// app.component.ts
import {Component} from '@angular/core';
import {Observable} from 'rxjs';
importΒ {IVirtualScrollOptions} from 'od-virtualscroll';
@Component({
selector: 'app-shell',
styles: [`...`], // <-- Style your cell and container
template: `
<od-virtualscroll [vsData]="data$" [vsOptions]="options$">
<ng-template let-item let-row="row" let-column="column">
<span>Row: {{row}}</span><br>
<span>Column: {{column}}</span>
{{item}}
</ng-template>
</od-virtualscroll>`
})
export class AppComponent {
data$: Observable<any[]> = ... ; // <-- Define data
options$: Observable<IVirtualScrollOptions> = ... ; // <-- Define options
}
If you want to apply a traditional layout and wonder about the space between inline block elements - read this.
Name | Type | Description |
---|---|---|
vsData | Observable<any[]> |
Stream of data |
vsOptions | Observable<IVirtualScrollOptions> |
Stream of options |
vsResize | Observable<any> |
Stream of resize commands (optional, default: -\-> ) |
vsUserCmd | Observable<IUserCmd> |
Stream of user specific commands (optional, default: -\-> ) |
vsDebounceTime | number |
Debounce scroll and resize events [ms] (optional, default: 0) |
vsEqualsFunc | (prevIndex: number, curIndex:number) => boolean |
Function to determine equality, given two indicies in the array (optional, default: (p,c) => p === c) ) |
export interface IVirtualScrollOptions {
itemWidth?: number;
itemHeight: number;
numAdditionalRows?: number;
numLimitColumns?: number;
}
The component requires either fixed-size cells (itemWidth, itemHeight) or a fixed number of cells per row (itemHeight, numLimitColumns).
Further, to improve scrolling, additional rows may be requested.
Currently, the supported user specific commands are:
SetScrollTopCmd
: Set scroll top to specific valueFocusRowCmd
: Focus specific row indexFocusItemCmd
: Focus specific item index
E.g. Focus row index 42.
data$ = // Data...;
userCmd$ = of(new FocusRowCmd(42)).pipe(delay(2000));
<od-virtualscroll [vsData]="data$" [vsUserCmd]="userCmd$">
<!-- Your template -->
</od-virtualscroll>
Inject the ScrollObservableService to subscribe to key component observables.
Name | Type | Description |
---|---|---|
scrollWin$ | [IVirtualScrollWindow] |
Stream of the most important inner data structure |
createRow$ | [CreateRowCmd, ComponentRef<VirtualRowComponent>] |
Create row command and ComponentRef |
removeRow$ | [RemoveRowCmd, ComponentRef<VirtualRowComponent>] |
Remove row command and ComponentRef |
shiftRow$ | [ShiftRowCmd, ComponentRef<VirtualRowComponent>] |
Shift row command and ComponentRef |
createItem$ | [CreateItemCmd, ScrollItem, EmbeddedViewRef<ScrollItem>] |
Create item command, scroll item and EmbeddedViewRef |
updateItem$ | [UpdateItemCmd, ScrollItem, EmbeddedViewRef<ScrollItem>] |
Update item command, scroll item and EmbeddedViewRef |
removeItem$ | [RemoveItemCmd] |
Remove item command |
The od-vsdynamic and od-vsadvanced examples show how the API may be used.
This interface provides pretty much all needed information.
export interface IVirtualScrollWindow {
dataTimestamp: number;
containerWidth: number;
containerHeight: number;
itemWidth?: number;
itemHeight: number;
numVirtualItems: number;
numVirtualRows: number;
virtualHeight: number;
numAdditionalRows: number;
scrollTop: number;
scrollPercentage: number;
numActualRows: number;
numActualColumns: number;
actualHeight: number;
numActualItems: number;
visibleStartRow: number;
visibleEndRow: number;
}
It is used internally and may also be useful in consuming application components.
E.g.: The od-vsdynamic example.
The ScrollObservableService
is registered on the VirtualScrollModule by default, so it is available on the root injector.
However, if you have multiple instances of the scroll component, a singleton instance of the ScrollObservableService
is not enough.
Register the service on the wrapping component, via the providers property in the @Component
decorator, so that the injector bubbling will stop on the Component level and will serve the right instance of the ScrollObservableService.
Check the feature/testMultiInstances branch for a simple example.
api.ts reveals the current API surface.
The lib is AoT compatible and ships with FESM5 and FESM15 exports.
See Angular Package Format v4.0 for more info.
ES5/UMD, ES5/ES2015 and ES2015/ES2015 exports are also provided.
1.1.x uses Angular6/RxJS6.
Rename component input vsScrollTop
to vsUserCmd
.
npm run {{scriptName}}
Name | Description |
---|---|
buildAll | Build lib and demo |
cleanAll | Remove generated directories |
buildDemo | Build demo bundle with AoT compilation |
tslint | Lint lib and demo |
serve | Starts browser-sync for local development |
explore | Source map explorer of AoT compiled demo |
Contribution and feedback is highly appreciated.
MIT