We need to implement the kendo grid with virtual scroll, but we need to get millions of data from the server, so we can't get all of them into the same request. I follow those articles (http://www.telerik.com/kendo-angular-ui/components/grid/configuration/#toc-scroll-modes, http://www.telerik.com/kendo-angular-ui/components/grid/data-binding/) but virtual scroll with pagination doesn't work properly.
My code:
msa-grid.component.ts
import { Component, OnInit, ViewChild, ElementRef, Input } from '@angular/core';
import { products } from './products';
import { MsaGridCol } from './MsaGridCol';
import { MsaGridService } from './services/msa-grid.service';
import { Observable, BehaviorSubject } from 'rxjs/Rx';
import * as $ from 'jquery';
import {
GridDataResult,
PageChangeEvent
} from '@progress/kendo-angular-grid';
import {
} from '@progress/kendo-data-query';
@Component({
selector: 'msa-grid',
templateUrl: './msa-grid.component.html',
styleUrls: ['./msa-grid.component.scss']
})
export class MsaGridComponent implements OnInit {
//The data to view
@Input() private gridData: Array<any> | Observable<GridDataResult>;
//The selection option
@Input() private selectionType: string = null;
//The table columns to show
@Input() private gridCols: Array<MsaGridCol> = new Array<MsaGridCol>();
//If the data has to be get from remote or not
@Input() private remote: boolean = true;
//If true divide data in pages with paginator
@Input() private pageable: boolean = false;
//Scrolling: if scrollable = 'virtual' pageable go to false
@Input() private scrollable: string = null;
//Set the grid height
@Input() private height: number = null;
//Page Size: THe page size if pageable or virtual-scrolling are enable
@Input() private pageSize: number = 12;
//Service Url: Your service data url
@Input() private serviceUrl: string = "";
//The records to skip
private skip: number = 0;
//The selected items
private selectedItems: Array<any> = new Array<any>();
//The table template
@ViewChild("msaGridTemplate") msaGridTemplate: ElementRef;
constructor(private msaGridService: MsaGridService) {
}
ngOnInit() {
if (this.scrollable == 'virtual') {
this.pageable = false;
}
if (this.remote) {
this.gridData = this.msaGridService;
this.pageChange({ skip: this.skip, take: this.pageSize });
}
}
//Function that select (or deselect) the clicked record into the table
selectItem(event, product) {
if (event.currentTarget.checked) {
if (this.selectionType == 'single') {
$(this.msaGridTemplate.nativeElement).find(".k-state-selected").find(".tableSelectionCheckbox").prop("checked", false);
$(this.msaGridTemplate.nativeElement).find(".k-state-selected").removeClass("k-state-selected");
this.selectedItems = new Array<any>();
}
this.selectedItems.push(product);
$(event.currentTarget).closest("tr").addClass("k-state-selected");
} else {
this.selectedItems = this.selectedItems.filter(el => !(JSON.stringify(el) == JSON.stringify(product)));
$(event.currentTarget).closest("tr").removeClass("k-state-selected");
}
console.log("SELECTED ITEMS", this.selectedItems.map((el) => el.ProductID));
}
//Function that select (or deselect) all the records into the table
selectAllItem(event) {
if (event.currentTarget.checked) {
$(this.msaGridTemplate.nativeElement).find(".k-grid-content tr").each((index, el) => {
if (!$(el).hasClass("k-state-selected")) {
$(el).addClass("k-state-selected")
$(el).find(".tableSelectionCheckbox").prop('checked', true);
}
this.selectedItems = JSON.parse(JSON.stringify(this.gridData));
});
} else {
$(this.msaGridTemplate.nativeElement).find(".k-grid-content tr").each((index, el) => {
if ($(el).hasClass("k-state-selected")) {
$(el).removeClass("k-state-selected");
$(el).find(".tableSelectionCheckbox").prop('checked', false);
}
this.selectedItems = new Array<any>();
});
}
}
//When a change page is detected
protected pageChange(event: PageChangeEvent): void {
if (this.remote) {
this.skip = event.skip;
this.msaGridService.query({ skip: this.skip, take: this.pageSize });
}
}
}
msa-grid.component.html
<div #msaGridTemplate>
<kendo-grid [data]="gridData | async"
[height]="370"
[selectable]="false"
[pageable]="false"
[scrollable]="'virtual'"
[skip]="skip"
[pageSize]="pageSize"
(pageChange)="pageChange($event)">
<kendo-grid-column field="SelectRows" title="ID" width="40" *ngIf="selectionType">
<template kendoGridHeaderTemplate >
<span><input type="checkbox" *ngIf="selectionType == 'multiple'" (change)="selectAllItem($event)" /></span>
</template>
<template kendoGridCellTemplate let-dataItem *ngIf="selectionType">
<input class="tableSelectionCheckbox" type="checkbox" (change)="selectItem($event, dataItem)" />
</template>
</kendo-grid-column>
<kendo-grid-column *ngFor="let col of gridCols" field="{{col.field}}" title="{{col.title}}" width="{{col.width}}">
<template kendoGridHeaderTemplate *ngIf="col.headerTemplate">
<msa-template-wrapper-component [componentType]="col.headerTemplate" [dataItem]="dataItem"></msa-template-wrapper-component>
</template>
<template kendoGridCellTemplate let-dataItem *ngIf="col.cellTemplate">
<msa-template-wrapper-component [componentType]="col.cellTemplate" [dataItem]="dataItem[col.field]"></msa-template-wrapper-component>
</template>
</kendo-grid-column>
</kendo-grid>
</div>
msa-gris.service.ts
import { Component, ViewChild, Input, OnInit, Injectable } from '@angular/core';
import { Http } from '@angular/http';
import { Observable, BehaviorSubject } from 'rxjs/Rx';
import {
GridComponent,
GridDataResult,
DataStateChangeEvent
} from '@progress/kendo-angular-grid';
import {
toODataString
} from '@progress/kendo-data-query';
@Injectable()
export class MsaGridService extends BehaviorSubject<GridDataResult>{
private BASE_URL: string = 'http://services.odata.org/V4/Northwind/Northwind.svc/';
private tableName: string = "Customers";
constructor(private http: Http) {
super(null);
}
query(state): void {
this.fetch(this.tableName, state)
.subscribe(x => super.next(x));
}
fetch(tableName: string, state: any): Observable<GridDataResult> {
const queryStr = `${toODataString(state)}&$count=true`;
return this.http
.get(`${this.BASE_URL}${tableName}?${queryStr}`)
.map(response => response.json())
.map(response => (<GridDataResult>{
data: response.value,
total: parseInt(response["@odata.count"], 10)
}));
}
}