Skip to content

Commit 58bc3fc

Browse files
authored
Merge pull request #7484 from IgniteUI/mkirova/hgrid-remote-POC
HierarchicalGrid remote virtualization POC
2 parents 283d4fa + 6e55d6a commit 58bc3fc

12 files changed

+261
-9
lines changed

projects/igniteui-angular/src/lib/directives/for-of/for_of.directive.ts

+28-5
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,26 @@ export class IgxForOfDirective<T> implements OnInit, OnChanges, DoCheck, OnDestr
179179
* this.parentVirtDir.totalItemCount = data.Count;
180180
* ```
181181
*/
182-
public totalItemCount: number = null;
182+
public get totalItemCount() {
183+
return this._totalItemCount;
184+
}
185+
186+
public set totalItemCount(val) {
187+
if (this._totalItemCount !== val) {
188+
this._totalItemCount = val;
189+
// update sizes in case total count changes.
190+
const newSize = this.initSizesCache(this.igxForOf);
191+
const sizeDiff = this.scrollComponent.size - newSize;
192+
this.scrollComponent.size = newSize;
193+
const lastChunkExceeded = this.state.startIndex + this.state.chunkSize > val;
194+
if (lastChunkExceeded) {
195+
this.state.startIndex = val - this.state.chunkSize;
196+
}
197+
this._adjustScrollPositionAfterSizeChange(sizeDiff);
198+
}
199+
}
200+
201+
private _totalItemCount: number = null;
183202

184203
/**
185204
* An event that is emitted after a new chunk has been loaded.
@@ -320,7 +339,7 @@ export class IgxForOfDirective<T> implements OnInit, OnChanges, DoCheck, OnDestr
320339
/**
321340
* @hidden
322341
*/
323-
protected get isRemote(): boolean {
342+
public get isRemote(): boolean {
324343
return this.totalItemCount !== null;
325344
}
326345

@@ -1117,13 +1136,17 @@ export class IgxForOfDirective<T> implements OnInit, OnChanges, DoCheck, OnDestr
11171136
const newHeight = this.initSizesCache(this.igxForOf);
11181137

11191138
const diff = oldHeight - newHeight;
1139+
this._adjustScrollPositionAfterSizeChange(diff);
1140+
}
11201141

1142+
private _adjustScrollPositionAfterSizeChange(sizeDiff) {
11211143
// if data has been changed while container is scrolled
11221144
// should update scroll top/left according to change so that same startIndex is in view
1123-
if (Math.abs(diff) > 0 && this.scrollPosition > 0) {
1145+
if (Math.abs(sizeDiff) > 0 && this.scrollPosition > 0) {
11241146
this.recalcUpdateSizes();
11251147
const offset = parseInt(this.dc.instance._viewContainer.element.nativeElement.style.top, 10);
1126-
this.scrollPosition = this.sizesCache[this.state.startIndex] - offset;
1148+
const newSize = this.sizesCache[this.state.startIndex] - offset;
1149+
this.scrollPosition = newSize === this.scrollPosition ? newSize + 1 : newSize;
11271150
}
11281151
}
11291152

@@ -1525,7 +1548,7 @@ export class IgxGridForOfDirective<T> extends IgxForOfDirective<T> implements On
15251548
if (changes && !this.isRemote) {
15261549
newHeight = this.handleCacheChanges(changes);
15271550
} else {
1528-
newHeight = this.initSizesCache(this.igxForOf);
1551+
return;
15291552
}
15301553

15311554
const diff = oldHeight - newHeight;

projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid-api.service.ts

+7-2
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,13 @@ export class IgxHierarchicalGridAPIService extends GridBaseAPIService<IgxGridBas
102102
let inState;
103103
if (record.childGridsData !== undefined) {
104104
const ri = record.rowID;
105-
const rec = this.grid.primaryKey ? this.get_rec_by_id(ri) : ri;
106-
inState = !!super.get_row_expansion_state(rec);
105+
const states = this.grid.expansionStates;
106+
const expanded = states.get(ri);
107+
if (expanded !== undefined) {
108+
return expanded;
109+
} else {
110+
return this.grid.getDefaultExpandState(record);
111+
}
107112
} else {
108113
inState = !!super.get_row_expansion_state(record);
109114
}

projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid.pipes.ts

+3
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ export class IgxGridHierarchicalPipe implements PipeTransform {
2828
return collection;
2929
}
3030
const grid: IgxHierarchicalGridComponent = this.gridAPI.grid;
31+
if (grid.verticalScrollContainer.isRemote) {
32+
return collection;
33+
}
3134
const result = this.addHierarchy(grid, cloneArray(collection), state, primaryKey, childKeys);
3235

3336
return result;

projects/igniteui-angular/src/public_api.ts

+1
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ export * from './lib/grids/columns/templates.directive';
7676
export * from './lib/grids/columns/column.component';
7777
export * from './lib/grids/columns/column-group.component';
7878
export * from './lib/grids/columns/column-layout.component';
79+
export * from './lib/grids/hierarchical-grid/hierarchical-grid.pipes';
7980
export * from './lib/icon/public_api';
8081
export * from './lib/input-group/public_api';
8182
export * from './lib/list/public_api';

src/app/app.component.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -408,7 +408,11 @@ export class AppComponent implements OnInit {
408408
}, {
409409
link: '/hierarchicalGridRemote',
410410
icon: 'swap_vert',
411-
name: 'Hierarchical Grid Remote'
411+
name: 'Hierarchical Grid Remote Load on Demand'
412+
}, {
413+
link: '/hierarchicalGridRemoteVirtualization',
414+
icon: 'swap_vert',
415+
name: 'Hierarchical Grid Remote Virtualization'
412416
}, {
413417
link: '/hierarchicalGridUpdating',
414418
icon: 'edit',

src/app/app.module.ts

+10-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
IgxIconModule, IgxGridModule, IgxExcelExporterService, IgxCsvExporterService, IgxOverlayService,
88
IgxDragDropModule, IgxDividerModule, IgxTreeGridModule, IgxHierarchicalGridModule, IgxInputGroupModule,
99
IgxIconService, DisplayDensityToken, DisplayDensity,
10-
IgxDateTimeEditorModule, IgxDateRangePickerModule, IgxButtonModule, IgxActionStripModule
10+
IgxDateTimeEditorModule, IgxDateRangePickerModule, IgxButtonModule, IgxActionStripModule, GridBaseAPIService
1111
} from 'igniteui-angular';
1212
import { IgxColumnHidingModule } from 'igniteui-angular';
1313
import { SharedModule } from './shared/shared.module';
@@ -126,6 +126,11 @@ import { GridColumnSelectionSampleComponent, GridColumnSelectionFilterPipe } fro
126126
import { ReactiveFormSampleComponent } from './reactive-from/reactive-form-sample.component';
127127
import { GridRowPinningSampleComponent } from './grid-row-pinning/grid-row-pinning.sample';
128128
import { DateRangeSampleComponent } from './date-range/date-range.sample';
129+
import {
130+
HierarchicalGridRemoteVirtualizationComponent
131+
} from './hierarchical-grid-remote-virtualization/hierarchical-grid-remote-virtualization';
132+
import { HierarchicalRemoteService } from './hierarchical-grid-remote-virtualization/hierarchical-remote.service';
133+
import { IgxGridHierarchicalPipe } from 'igniteui-angular';
129134
import { GridVirtualizationScrollSampleComponent } from './grid-remote-virtualization-with-scroll/grid-remote-virtualization-scroll.sample';
130135

131136
const components = [
@@ -223,6 +228,7 @@ const components = [
223228
TooltipSampleComponent,
224229
HierarchicalGridSampleComponent,
225230
HierarchicalGridRemoteSampleComponent,
231+
HierarchicalGridRemoteVirtualizationComponent,
226232
HierarchicalGridUpdatingSampleComponent,
227233
DisplayFormatPipe,
228234
InputFormatPipe,
@@ -273,6 +279,9 @@ const components = [
273279
providers: [
274280
LocalService,
275281
RemoteService,
282+
HierarchicalRemoteService,
283+
GridBaseAPIService,
284+
IgxGridHierarchicalPipe,
276285
IgxExcelExporterService,
277286
IgxIconService,
278287
IgxCsvExporterService,

src/app/app.routing.ts

+4
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ import { GridMasterDetailSampleComponent } from './grid-master-detail/grid-maste
7272
import { DateTimeEditorSampleComponent } from './date-time-editor/date-time-editor.sample';
7373
import { GridRowPinningSampleComponent } from './grid-row-pinning/grid-row-pinning.sample';
7474
import { ActionStripSampleComponent } from './action-strip/action-strip.sample';
75+
import { HierarchicalGridRemoteVirtualizationComponent } from './hierarchical-grid-remote-virtualization/hierarchical-grid-remote-virtualization';
7576
import { GridVirtualizationScrollSampleComponent } from './grid-remote-virtualization-with-scroll/grid-remote-virtualization-scroll.sample';
7677

7778
const appRoutes = [
@@ -331,6 +332,9 @@ const appRoutes = [
331332
}, {
332333
path: 'hierarchicalGridRemote',
333334
component: HierarchicalGridRemoteSampleComponent
335+
}, {
336+
path: 'hierarchicalGridRemoteVirtualization',
337+
component: HierarchicalGridRemoteVirtualizationComponent
334338
}, {
335339
path: 'hierarchicalGridUpdating',
336340
component: HierarchicalGridUpdatingSampleComponent
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<ng-template #remoteDataLoading>
2+
<div class="remote-data-loading-template-medium"></div>
3+
</ng-template>
4+
5+
<ng-template #cellTemplate igxCell let-val let-rowData="cell.row.rowData">
6+
<div *ngIf="!rowData.emptyRec; else remoteDataLoading">
7+
{{ val }}
8+
</div>
9+
</ng-template>
10+
11+
<igx-hierarchical-grid #hGrid [data]="gridData" [primaryKey]="'CustomerID'" [autoGenerate]="false" [height]="'500px'" [width]="'100%'" #hGrid [emptyGridMessage]="''">
12+
<igx-column field="CustomerID" [hidden]='true'></igx-column>
13+
<igx-column field="CompanyName" [cellTemplate]="cellTemplate"></igx-column>
14+
<igx-column field="ContactName" [cellTemplate]="cellTemplate"></igx-column>
15+
<igx-column field="ContactTitle" [cellTemplate]="cellTemplate"></igx-column>
16+
<igx-column field="Country" [cellTemplate]="cellTemplate"></igx-column>
17+
<igx-column field="Phone" [cellTemplate]="cellTemplate"></igx-column>
18+
<igx-row-island #rowIsland1 [key]="'Orders'" [primaryKey]="'OrderID'" [autoGenerate]="false" [rowSelection]='selectionMode' [emptyGridMessage]="''">
19+
<igx-column field="OrderID"></igx-column>
20+
<igx-column field="OrderDate"></igx-column>
21+
<igx-column field="ShipCountry"></igx-column>
22+
<igx-column field="ShipCity"></igx-column>
23+
<igx-column field="ShipAddress"></igx-column>
24+
</igx-row-island>
25+
</igx-hierarchical-grid>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
.remote-data-loading-template {
2+
animation: content-placeholder-animation .5s infinite;
3+
background-color: lightgray;
4+
height: 15px;
5+
}
6+
7+
.remote-data-loading-template-medium {
8+
@extend .remote-data-loading-template;
9+
width: 30px;
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { Component, ViewChild, AfterViewInit, PipeTransform, Pipe, ChangeDetectorRef } from '@angular/core';
2+
import {
3+
IgxRowIslandComponent,
4+
IgxHierarchicalGridComponent,
5+
IGridCreatedEventArgs,
6+
GridSelectionMode
7+
} from 'igniteui-angular';
8+
import { RemoteService } from '../shared/remote.service';
9+
import { HierarchicalRemoteService } from './hierarchical-remote.service';
10+
import { debounceTime } from 'rxjs/operators';
11+
12+
@Component({
13+
selector: 'app-hierarchical-grid-remote-virtualization-sample',
14+
templateUrl: 'hierarchical-grid-remote-virtualization.html',
15+
styleUrls: ['hierarchical-grid-remote-virtualization.scss'],
16+
providers: [RemoteService]
17+
})
18+
export class HierarchicalGridRemoteVirtualizationComponent implements AfterViewInit {
19+
20+
public selectionMode;
21+
remoteData = [];
22+
gridData = [];
23+
totalCount: number;
24+
25+
@ViewChild('rowIsland1', { static: true })
26+
rowIsland1: IgxRowIslandComponent;
27+
28+
@ViewChild('hGrid', { static: true })
29+
hGrid: IgxHierarchicalGridComponent;
30+
31+
public isExpanding = false;
32+
33+
constructor(private remoteService: HierarchicalRemoteService, private cdr: ChangeDetectorRef) {
34+
remoteService.url = 'https://services.odata.org/V4/Northwind/Northwind.svc/Customers?$expand=Orders';
35+
}
36+
37+
public handleLoad(args) {
38+
this.remoteService.getData(args, this.hGrid, (data) => {
39+
this.gridData = data;
40+
this.cdr.detectChanges();
41+
});
42+
}
43+
44+
public ngAfterViewInit() {
45+
// load initial data
46+
this.handleLoad(this.hGrid.virtualizationState);
47+
48+
// update when row is expanded/collapsed
49+
this.hGrid.expansionStatesChange.subscribe(x => {
50+
this.handleLoad(this.hGrid.virtualizationState);
51+
});
52+
53+
// update on scroll
54+
this.hGrid.onDataPreLoad.pipe().subscribe(() => {
55+
const data = this.remoteService.getDataFromCache(this.hGrid.virtualizationState);
56+
if (data) {
57+
this.gridData = data;
58+
}
59+
});
60+
61+
// handle remote request after user stops scrolling for 500ms
62+
this.hGrid.onDataPreLoad.pipe(debounceTime(500)).subscribe((event) => {
63+
this.handleLoad(event);
64+
});
65+
}
66+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import { Injectable } from '@angular/core';
2+
import { Observable, BehaviorSubject } from 'rxjs';
3+
import { map } from 'rxjs/operators';
4+
import { HttpClient } from '@angular/common/http';
5+
import { IgxGridHierarchicalPipe } from 'projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid.pipes';
6+
import { IgxHierarchicalGridAPIService, IgxHierarchicalGridComponent } from 'igniteui-angular';
7+
8+
@Injectable()
9+
export class HierarchicalRemoteService {
10+
11+
public remotePagingData: BehaviorSubject<any[]>;
12+
public cachedData = [];
13+
requestStartIndex = 0;
14+
totalCount: number;
15+
remoteData: Observable<any[]>;
16+
_remoteData: BehaviorSubject<any[]>;
17+
url = `https://services.odata.org/V4/Northwind/Northwind.svc/Products`;
18+
urlBuilder;
19+
20+
constructor(private http: HttpClient, private hierarchyPipe: IgxGridHierarchicalPipe) {
21+
this._remoteData = new BehaviorSubject([]);
22+
this.remoteData = this._remoteData.asObservable();
23+
}
24+
25+
26+
nullData() {
27+
this._remoteData.next(null);
28+
}
29+
30+
undefinedData() {
31+
this._remoteData.next(undefined);
32+
}
33+
34+
public getDataFromCache(data: any) {
35+
const startIndex = data.startIndex;
36+
const endIndex = (data.chunkSize || 11) + startIndex;
37+
const dataResult = this.cachedData.slice(startIndex, endIndex);
38+
while (dataResult.length < data.chunkSize) {
39+
dataResult.unshift({emptyRec: true});
40+
}
41+
this._remoteData.next(dataResult);
42+
return dataResult;
43+
}
44+
45+
private _updateCachedData (data: any, startIndex: number, lastChunk: boolean) {
46+
for (let i = 0; i < data.length; i++) {
47+
this.cachedData[i + startIndex] = data[i];
48+
}
49+
if (lastChunk) {
50+
this.cachedData.splice(startIndex + data.length);
51+
}
52+
}
53+
54+
getData(virtualizationState: any, grid: IgxHierarchicalGridComponent, cb?: (any) => void) {
55+
return this.http.get(this.buildUrl(virtualizationState, grid)).pipe(
56+
map(response => response),
57+
)
58+
.subscribe(d => {
59+
const result = d['value'];
60+
this.totalCount = d['@odata.count'];
61+
if (this.cachedData.length === 0) {
62+
this.cachedData = new Array<any>(this.totalCount * 2).fill({emptyRec: true});
63+
}
64+
const processedData = this.hierarchyPipe
65+
.addHierarchy(grid, result, grid.expansionStates, grid.primaryKey, grid.childLayoutKeys);
66+
67+
const childRecsBeforeIndex = this.cachedData
68+
.filter((x, index) => grid.isChildGridRecord(x) && index < virtualizationState.startIndex);
69+
const size = virtualizationState.chunkSize || 11;
70+
const lastChunk = virtualizationState.startIndex + size === grid.verticalScrollContainer.totalItemCount;
71+
this._updateCachedData(processedData, this.requestStartIndex + childRecsBeforeIndex.length, lastChunk);
72+
const allChildRecords = this.cachedData.filter(x => grid.isChildGridRecord(x));
73+
grid.verticalScrollContainer.totalItemCount = this.totalCount + allChildRecords.length;
74+
const dataResult = this.getDataFromCache(virtualizationState);
75+
this._remoteData.next(dataResult);
76+
if (cb) {
77+
cb(dataResult);
78+
}
79+
});
80+
}
81+
82+
public buildUrl(virtualizationArgs, grid) {
83+
let qS = '';
84+
if (virtualizationArgs) {
85+
let requiredChunkSize: number;
86+
const childRecsBeforeIndex = this.cachedData
87+
.filter((x, index) => grid.isChildGridRecord(x) && index <= virtualizationArgs.startIndex);
88+
const skip = virtualizationArgs.startIndex - childRecsBeforeIndex.length;
89+
this.requestStartIndex = skip;
90+
requiredChunkSize = virtualizationArgs.chunkSize === 0 ? 11 : virtualizationArgs.chunkSize + childRecsBeforeIndex.length;
91+
const top = requiredChunkSize;
92+
qS = `&$skip=${skip}&$top=${top}&$count=true`;
93+
}
94+
return `${this.url}${qS}`;
95+
}
96+
}

src/app/routing.ts

+6
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,9 @@ import { GridRowPinningSampleComponent } from './grid-row-pinning/grid-row-pinni
102102
import { ReactiveFormSampleComponent } from './reactive-from/reactive-form-sample.component';
103103
import { DateRangeSampleComponent } from './date-range/date-range.sample';
104104
import { ActionStripSampleComponent } from './action-strip/action-strip.sample';
105+
import {
106+
HierarchicalGridRemoteVirtualizationComponent
107+
} from './hierarchical-grid-remote-virtualization/hierarchical-grid-remote-virtualization';
105108
import { GridVirtualizationScrollSampleComponent } from './grid-remote-virtualization-with-scroll/grid-remote-virtualization-scroll.sample';
106109

107110
const appRoutes = [
@@ -460,6 +463,9 @@ const appRoutes = [
460463
}, {
461464
path: 'hierarchicalGridRemote',
462465
component: HierarchicalGridRemoteSampleComponent
466+
}, {
467+
path: 'hierarchicalGridRemoteVirtualization',
468+
component: HierarchicalGridRemoteVirtualizationComponent
463469
}, {
464470
path: 'hierarchicalGridUpdating',
465471
component: HierarchicalGridUpdatingSampleComponent

0 commit comments

Comments
 (0)