-
Notifications
You must be signed in to change notification settings - Fork 27
Feature/data provider strategies #585
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
base: master
Are you sure you want to change the base?
Changes from 10 commits
b44d38c
abdd742
5896dad
1366258
2d6513e
7e88f63
862926b
8e435ac
5c848a8
5410eca
4b77842
99e1bb8
ac56e85
ab5c721
ca9c107
5e06bc4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,9 @@ | ||
| import { ChangeDetectionStrategy, Component } from '@angular/core'; | ||
| import { GridModel } from 'ng2-qgrid'; | ||
| import { Observable } from 'rxjs'; | ||
| import { map, switchMap, tap } from 'rxjs/operators'; | ||
| import { | ||
| CacheAlreadyRequestedPageStrategy, 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 = [ | ||
|
|
@@ -12,6 +14,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 | ||
| }) | ||
|
|
@@ -21,34 +24,43 @@ 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 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( | ||
| private dataService: DataService, | ||
| ) { } | ||
|
|
||
| getPage(pageNumber: number, pageSize: number): Observable<Atom[]> { | ||
| getPage(number: number, pageSize: number): Observable<Atom[]> { | ||
| return this.dataService.getAtoms() | ||
| .pipe(map(atoms => atoms.splice(pageNumber * pageSize, pageSize))); | ||
| .pipe(map(atoms => atoms.splice(number * pageSize, pageSize))); | ||
| } | ||
|
|
||
| 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()); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why we need to do memo slice?
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Rename it please to ExampleReverseDataStrategy
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
.reverse() mutates initial array |
||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| 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[]>; | ||
| private paginationSize: number; | ||
|
|
||
| constructor( | ||
| private server: Pick<DataProviderPageServer<T>, 'getPage'>, | ||
| private options: { pagesToLoad: number } = { pagesToLoad: 0 }, | ||
|
||
| ) { } | ||
|
|
||
| process(data: T[], { model }: DataProviderContext): Observable<T[]> { | ||
| const { current, size } = model.pagination(); | ||
|
|
||
| if (this.paginationSize !== size) { | ||
|
||
| this.pageCache = new Map(); | ||
| this.paginationSize = size; | ||
| } | ||
|
|
||
| if (this.options.pagesToLoad) { | ||
| this.loadInBackground(this.options.pagesToLoad, model); | ||
| } | ||
|
|
||
| if (this.pageCache.has(current)) { | ||
| return of(this.pageCache.get(current)); | ||
| } | ||
|
|
||
| const shouldRequestData = !data.length; | ||
| return (shouldRequestData ? this.server.getPage(current, size) : of(data)) | ||
|
||
| .pipe(tap(rows => this.pageCache.set(current, rows))); | ||
| } | ||
|
|
||
| private loadInBackground(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.getPage(page, 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,6 @@ | ||
| import { Observable } from 'rxjs'; | ||
|
|
||
| export interface DataProviderPageServer<T> { | ||
| getPage(page: number, size: number): Observable<T[]>; | ||
| getTotal(): Observable<number>; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| import { Observable } from 'rxjs'; | ||
| import { DataProviderContext } from './data-provider'; | ||
|
|
||
| export interface DataProviderStrategy<T> { | ||
| process(data: T[], context: DataProviderContext): Observable<T[]>; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| 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); | ||
| } | ||
|
|
||
| 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) { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what will happen if count will be 0 from the server?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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)) | ||
| ) | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.