diff --git a/npm/ng-packs/packages/core/src/lib/services/index.ts b/npm/ng-packs/packages/core/src/lib/services/index.ts index f8b016bbd1..f01dc876de 100644 --- a/npm/ng-packs/packages/core/src/lib/services/index.ts +++ b/npm/ng-packs/packages/core/src/lib/services/index.ts @@ -4,6 +4,7 @@ export * from './config-state.service'; export * from './content-projection.service'; export * from './dom-insertion.service'; export * from './lazy-load.service'; +export * from './list.service'; export * from './localization.service'; export * from './profile-state.service'; export * from './profile.service'; diff --git a/npm/ng-packs/packages/core/src/lib/services/list.service.ts b/npm/ng-packs/packages/core/src/lib/services/list.service.ts new file mode 100644 index 0000000000..1e0b8e22b9 --- /dev/null +++ b/npm/ng-packs/packages/core/src/lib/services/list.service.ts @@ -0,0 +1,96 @@ +import { Inject, Injectable, Optional } from '@angular/core'; +import { BehaviorSubject, Observable, ReplaySubject } from 'rxjs'; +import { debounceTime, shareReplay, switchMap, tap } from 'rxjs/operators'; +import { ABP } from '../models/common'; +import { LIST_QUERY_DEBOUNCE_TIME } from '../tokens/list.token'; +import { takeUntilDestroy } from '../utils/rxjs-utils'; + +@Injectable() +export class ListService { + private _filter = ''; + set filter(value: string) { + this._filter = value; + this.get(); + } + get filter(): string { + return this._filter; + } + + private _maxResultCount = 10; + set maxResultCount(value: number) { + this._maxResultCount = value; + this.get(); + } + get maxResultCount(): number { + return this._maxResultCount; + } + + private _page = 1; + set page(value: number) { + this._page = value; + this.get(); + } + get page(): number { + return this._page; + } + + private _sortKey = ''; + set sortKey(value: string) { + console.log(value); + this._sortKey = value; + this.get(); + } + get sortKey(): string { + return this._sortKey; + } + + private _sortOrder = ''; + set sortOrder(value: string) { + this._sortOrder = value; + this.get(); + } + get sortOrder(): string { + return this._sortOrder; + } + + private _query$ = new ReplaySubject(1); + + get query$(): Observable { + return this._query$ + .asObservable() + .pipe(debounceTime(this.delay || 300), shareReplay({ bufferSize: 1, refCount: true })); + } + + private _isLoading$ = new BehaviorSubject(false); + + get isLoading$(): Observable { + return this._isLoading$.asObservable(); + } + + constructor(@Optional() @Inject(LIST_QUERY_DEBOUNCE_TIME) private delay: number) { + this.get(); + } + + get() { + this._query$.next({ + filter: this._filter || undefined, + maxResultCount: this._maxResultCount, + skipCount: (this._page - 1) * this._maxResultCount, + sorting: this._sortOrder ? `${this._sortKey} ${this._sortOrder}` : undefined, + }); + } + + hookToQuery(streamCreatorCallback: QueryStreamCreatorCallback): Observable { + this._isLoading$.next(true); + + return this.query$.pipe( + switchMap(streamCreatorCallback), + tap(() => this._isLoading$.next(false)), + takeUntilDestroy(this), + ); + } + + ngOnDestroy() {} +} + +export type QueryStreamCreatorCallback = (query: ABP.PageQueryParams) => Observable; diff --git a/npm/ng-packs/packages/core/src/lib/tests/list.service.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/list.service.spec.ts new file mode 100644 index 0000000000..b7e2cc8450 --- /dev/null +++ b/npm/ng-packs/packages/core/src/lib/tests/list.service.spec.ts @@ -0,0 +1,150 @@ +import { createServiceFactory, SpectatorService } from '@ngneat/spectator/jest'; +import { of } from 'rxjs'; +import { bufferCount, take } from 'rxjs/operators'; +import { ListService } from '../services/list.service'; +import { LIST_QUERY_DEBOUNCE_TIME } from '../tokens'; + +describe('ListService', () => { + let spectator: SpectatorService; + let service: ListService; + + const createService = createServiceFactory({ + service: ListService, + providers: [ + { + provide: LIST_QUERY_DEBOUNCE_TIME, + useValue: 0, + }, + ], + }); + + beforeEach(() => { + spectator = createService(); + service = spectator.service; + }); + + describe('#filter', () => { + it('should initially be empty string', () => { + expect(service.filter).toBe(''); + }); + + it('should be changed', () => { + service.filter = 'foo'; + + expect(service.filter).toBe('foo'); + }); + }); + + describe('#maxResultCount', () => { + it('should initially be 10', () => { + expect(service.maxResultCount).toBe(10); + }); + + it('should be changed', () => { + service.maxResultCount = 20; + + expect(service.maxResultCount).toBe(20); + }); + }); + + describe('#page', () => { + it('should initially be 1', () => { + expect(service.page).toBe(1); + }); + + it('should be changed', () => { + service.page = 9; + + expect(service.page).toBe(9); + }); + }); + + describe('#sortKey', () => { + it('should initially be empty string', () => { + expect(service.sortKey).toBe(''); + }); + + it('should be changed', () => { + service.sortKey = 'foo'; + + expect(service.sortKey).toBe('foo'); + }); + }); + + describe('#sortOrder', () => { + it('should initially be empty string', () => { + expect(service.sortOrder).toBe(''); + }); + + it('should be changed', () => { + service.sortOrder = 'foo'; + + expect(service.sortOrder).toBe('foo'); + }); + }); + + describe('#query$', () => { + it('should initially emit default query', done => { + service.query$.pipe(take(1)).subscribe(query => { + expect(query).toEqual({ + filter: undefined, + maxResultCount: 10, + skipCount: 0, + sorting: undefined, + }); + + done(); + }); + }); + + it('should emit a query based on params set', done => { + service.filter = 'foo'; + service.sortKey = 'bar'; + service.sortOrder = 'baz'; + service.maxResultCount = 20; + service.page = 9; + + service.query$.pipe(take(1)).subscribe(query => { + expect(query).toEqual({ + filter: 'foo', + sorting: 'bar baz', + maxResultCount: 20, + skipCount: 160, + }); + + done(); + }); + }); + }); + + describe('#hookToQuery', () => { + it('should call given callback with the query', done => { + const callback = query => of(query); + + service.hookToQuery(callback).subscribe(query => { + expect(query).toEqual({ + filter: undefined, + maxResultCount: 10, + skipCount: 0, + sorting: undefined, + }); + + done(); + }); + }); + + it('should emit isLoading as side effect', done => { + const callback = query => of(query); + + service.isLoading$.pipe(bufferCount(3)).subscribe(([idle, init, end]) => { + expect(idle).toBe(false); + expect(init).toBe(true); + expect(end).toBe(false); + + done(); + }); + + service.hookToQuery(callback).subscribe(); + }); + }); +});