Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/data provider strategies #585

Open
wants to merge 16 commits into
base: master
Choose a base branch
from
11 changes: 9 additions & 2 deletions packages/ng2-qgrid/src/public-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,13 +164,15 @@ export {
BlurDirective,
BoolEditorComponent,
BoolEditorModule,
CacheAlreadyRequestedPageStrategy,
CaptionComponent,
CaptionModule,
CellEditorComponent,
CellEditorModule,
CellTooltipComponent,
CellTooltipDirective,
CellTooltipModule,
CheckNextPageCountStrategy,
ColumnChooserComponent,
ColumnChooserModule,
ColumnChooserTriggerComponent,
Expand All @@ -189,6 +191,12 @@ export {
CurrencyPipe,
DataManipulationComponent,
DataManipulationModule,
DataProvider,
DataProviderComponent,
DataProviderContext,
DataProviderModule,
DataProviderPageServer,
DataProviderStrategy,
DateDirective,
DateMaskDirective,
DateModule,
Expand Down Expand Up @@ -247,10 +255,9 @@ export {
ReferenceComponent,
ReferenceEditorComponent,
ReferenceEditorModule,
RequestTotalCountOnceStategy,
RestComponent,
RestModule,
DataProviderComponent,
DataProviderModule,
RuleComponent,
SerializationService,
StatusBarComponent,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<q-grid [caption]="title">
<q-grid [caption]="title" [model]="gridModel">
<q-grid-columns generation="deep">
</q-grid-columns>

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { GridModel } from 'ng2-qgrid';
import { Observable } from 'rxjs';
import { map, switchMap, tap } from 'rxjs/operators';
import { CacheAlreadyRequestedPageStrategy, CheckNextPageCountStrategy, DataProvider, DataProviderPageServer, DataProviderStrategy, Grid, GridModel, RequestTotalCountOnceStategy } from 'ng2-qgrid';
import { Observable, of } from 'rxjs';
import { map } from 'rxjs/operators';
import { Atom, DataService } from '../data.service';

const EXAMPLE_TAGS = [
Expand All @@ -12,6 +12,7 @@ const EXAMPLE_TAGS = [
@Component({
selector: 'example-data-provider',
templateUrl: 'example-data-provider.component.html',
styleUrls: ['example-data-provider.component.scss'],
providers: [DataService],
changeDetection: ChangeDetectionStrategy.OnPush
})
Expand All @@ -21,34 +22,44 @@ export class ExampleDataProviderComponent {

page$: Observable<Atom[]>;

gridModel = this.qgrid.model();

private server = new FakeServer(this.dataService);
private dataProvider: DataProvider<Atom> = new DataProvider<Atom>(this.gridModel, [
new CheckNextPageCountStrategy(this.server),
new RequestTotalCountOnceStategy(this.server),
new CacheAlreadyRequestedPageStrategy(this.server, { pagesToLoad: 1 }),
new ExampleReverseDataStrategy(),
]);

constructor(
private dataService: DataService,
private qgrid: Grid,
) { }

onRequestRows(gridModel: GridModel): void {
const server = new FakeServer(this.dataService);
const pager = gridModel.pagination();

this.page$ = server.getTotal()
.pipe(
tap(total => gridModel.pagination({ count: total })),
switchMap(() => server.getPage(pager.current, pager.size)),
);
this.page$ = this.dataProvider.getPage();
}
}

class FakeServer {
class FakeServer implements DataProviderPageServer<Atom> {
constructor(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@DataProviderCache()
@PataProviderBackground()
getPage() {}

@DataProviderCache()
@PataProviderBackground()
getPage() {}

private dataService: DataService,
) { }

getPage(pageNumber: number, pageSize: number): Observable<Atom[]> {
getRecords(from: number, to: number): Observable<Atom[]> {
return this.dataService.getAtoms()
.pipe(map(atoms => atoms.splice(pageNumber * pageSize, pageSize)));
.pipe(map(atoms => atoms.slice(from, to)));
}

getTotal(): Observable<number> {
return this.dataService.getAtoms()
.pipe(map(atoms => atoms.length));
}
}

class ExampleReverseDataStrategy<T> implements DataProviderStrategy<T> {
process(memo: T[]): Observable<T[]> {
return of(memo.slice().reverse());
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why we need to do memo slice?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rename it please to ExampleReverseDataStrategy

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why we need to do memo slice?

.reverse() mutates initial array

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { GridModel } from '@qgrid/ngx';
import { Observable, of } from 'rxjs';
import { filter, tap } from 'rxjs/operators';
import { DataProviderContext } from './data-provider';
import { DataProviderPageServer } from './data-provider-page-server';
import { DataProviderStrategy } from './data-provider-strategy';

export class CacheAlreadyRequestedPageStrategy<T> implements DataProviderStrategy<T> {
private pageCache: Map<number, T[]> = new Map();
private pagerSize: number;

constructor(
private server: Pick<DataProviderPageServer<T>, 'getRecords'>,
private options: { pagesToLoad: number } = { pagesToLoad: 1 },
) { }

process(data: T[], { model }: DataProviderContext): Observable<T[]> {
const { current, size } = model.pagination();

if (this.options.pagesToLoad) {
this.loadBackgroundPages(this.options.pagesToLoad, model);
}

if (this.pageCache.has(current)) {
return of(this.pageCache.get(current));
}

const shouldRequestData = !data.length;
return (shouldRequestData ? this.server.getRecords(current * size, (current + 1) * size) : of(data))
.pipe(tap(rows => this.pageCache.set(current, rows)));
}

invalidate(gridModel: GridModel): void {
const { size } = gridModel.pagination();
if (this.pagerSize !== size) {
this.pageCache.clear();
this.pagerSize = size;
}
}

private loadBackgroundPages(pagesToLoad: number, model: GridModel): void {
const { count, current, size } = model.pagination();
const fromPage = current + 1;
const toPage = current + pagesToLoad;
const maxPage = Math.floor(count / size);
for (let page = fromPage; page < toPage; page++) {
if (page <= maxPage && !this.pageCache.has(page)) {
this.server.getRecords(page * size, (page + 1) * size)
.pipe(filter(rows => !!rows?.length))
.subscribe(rows => this.pageCache.set(page, rows));
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { GridModel } from '@qgrid/ngx';
import { Observable, of } from 'rxjs';
import { map } from 'rxjs/operators';
import { DataProviderContext } from './data-provider';
import { DataProviderPageServer } from './data-provider-page-server';
import { DataProviderStrategy } from './data-provider-strategy';

export class CheckNextPageCountStrategy<T> implements DataProviderStrategy<T> {
private cache: Set<number> = new Set();
private pagerSize: number;

constructor(
private server: Pick<DataProviderPageServer<T>, 'getRecords'>,
) { }

process(data: T[], { model }: DataProviderContext): Observable<T[]> {
const { current } = model.pagination();

if (this.cache.has(current)) {
return of(data);
}

const { count, size } = model.pagination();
return this.server.getRecords(current * size, (current + 1) * size + 1)
.pipe(
map((next: T[]) => {
if (next?.length > size) {
next.pop();
}
model.pagination({ count: next.length + (count || 1) });
this.cache.add(current);
return next;
}),
);
}

invalidate(gridModel: GridModel): void {
const { size } = gridModel.pagination();
if (size !== this.pagerSize) {
this.cache.clear();
gridModel.pagination({ count: 0 });
this.pagerSize = size;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { Observable } from 'rxjs';

export interface DataProviderPageServer<T> {
getRecords(from: number, to: number): Observable<T[]>;
getTotal(): Observable<number>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { GridModel } from '@qgrid/ngx';
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can u put all strategies directly to the data-provider folder? also remove please index.ts file, we don't use them

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

import { Observable } from 'rxjs';
import { DataProviderContext } from './data-provider';

export interface DataProviderStrategy<T> {
process(data: T[], context: DataProviderContext): Observable<T[]>;
invalidate?(gridModel: GridModel): void;
}
35 changes: 35 additions & 0 deletions packages/qgrid-ngx-plugins/src/lib/data-provider/data-provider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { GridModel } from '@qgrid/ngx';
import { Observable, of } from 'rxjs';
import { switchMap } from 'rxjs/operators';
import { DataProviderStrategy } from './data-provider-strategy';

export interface DataProviderContext {
model: GridModel;
}

export class DataProvider<T> {

constructor(
private gridModel: GridModel,
private strategies: DataProviderStrategy<T>[],
) { }

getPage(): Observable<T[]> {
return this.applyStrategies();
}

private applyStrategies(data = [], index = 0): Observable<T[]> {
const strategy = this.strategies[index];
const hasNext = !!this.strategies[index + 1];
if (!strategy) {
return of(data);
}

if (typeof strategy.invalidate === 'function') {
strategy.invalidate(this.gridModel);
}

return strategy.process(data, { model: this.gridModel })
.pipe(switchMap(x => hasNext ? this.applyStrategies(x, index + 1) : of(x)));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { Observable, of } from 'rxjs';
import { switchMap, tap } from 'rxjs/operators';
import { DataProviderContext } from './data-provider';
import { DataProviderPageServer } from './data-provider-page-server';
import { DataProviderStrategy } from './data-provider-strategy';

export class RequestTotalCountOnceStategy<T> implements DataProviderStrategy<T> {
private totalCount: number = 0;

constructor(
private server: Pick<DataProviderPageServer<T>, 'getTotal'>,
) { }

process(data: T[], { model }: DataProviderContext): Observable<T[]> {
if (this.totalCount > 0) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what will happen if count will be 0 from the server?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if there was empty array from server wi will try to send that request everytime. Do you think we should remove it?

model.pagination({ count: this.totalCount });
return of(data);
}

return this.server.getTotal()
.pipe(
tap(count => {
this.totalCount = count;
model.pagination({ count });
}),
switchMap(() => of(data))
)
}
}
Loading