common-ui/src/lib/tables/entities.service.ts
2021-08-24 13:45:07 +03:00

117 lines
4.2 KiB
TypeScript

import { Injectable } from '@angular/core';
import { BehaviorSubject, combineLatest, Observable, pipe } from 'rxjs';
import { distinctUntilChanged, map, tap } from 'rxjs/operators';
import { FilterService } from '../filtering/filter.service';
import { SearchService } from '../search/search.service';
import { getFilteredEntities } from '../filtering/filter-utils';
import { Listable } from './models/listable';
const toLengthValue = (entities: unknown[]) => entities?.length ?? 0;
const getLength = pipe(map(toLengthValue), distinctUntilChanged());
@Injectable()
export class EntitiesService<T extends Listable> {
private readonly _all$ = new BehaviorSubject<T[]>([]);
readonly all$ = this._all$.asObservable();
readonly allLength$ = this._all$.pipe(getLength);
private _displayed: T[] = [];
readonly displayed$ = this._getDisplayed$;
readonly displayedLength$ = this.displayed$.pipe(getLength);
private readonly _selected$ = new BehaviorSubject<(string | number)[]>([]);
readonly selected$ = this._selected$.asObservable();
readonly selectedEntities$ = this._selected$.asObservable().pipe(map(() => this.selected));
readonly selectedLength$ = this._selected$.pipe(getLength);
readonly noData$ = this._noData$;
readonly areAllSelected$ = this._areAllSelected$;
readonly areSomeSelected$ = this._areSomeSelected$;
readonly notAllSelected$ = this._notAllSelected$;
constructor(private readonly _filterService: FilterService, private readonly _searchService: SearchService<T>) {}
get all(): T[] {
return Object.values(this._all$.getValue());
}
get selected(): T[] {
const selectedIds = Object.values(this._selected$.getValue());
return this.all.filter(a => selectedIds.indexOf(a.id) !== -1);
}
private get _getDisplayed$(): Observable<T[]> {
const { filterGroups$ } = this._filterService;
const { valueChanges$ } = this._searchService;
return combineLatest([this.all$, filterGroups$, valueChanges$]).pipe(
map(([entities, filterGroups]) => getFilteredEntities(entities, filterGroups)),
map(entities => this._searchService.searchIn(entities)),
tap(displayed => {
this._displayed = displayed;
})
);
}
private get _areAllSelected$(): Observable<boolean> {
return combineLatest([this.displayedLength$, this.selectedLength$]).pipe(
map(([displayedLength, selectedLength]) => !!displayedLength && displayedLength === selectedLength),
distinctUntilChanged()
);
}
private get _areSomeSelected$(): Observable<boolean> {
return this.selectedLength$.pipe(
map(length => !!length),
distinctUntilChanged()
);
}
private get _notAllSelected$(): Observable<boolean> {
return combineLatest([this.areAllSelected$, this.areSomeSelected$]).pipe(
map(([allAreSelected, someAreSelected]) => !allAreSelected && someAreSelected),
distinctUntilChanged()
);
}
private get _noData$(): Observable<boolean> {
return this.allLength$.pipe(
map(length => length === 0),
distinctUntilChanged()
);
}
private get _allSelected() {
return this._displayed.length !== 0 && this._displayed.length === this.selected.length;
}
setEntities(newEntities: T[]): void {
this._all$.next(newEntities);
}
setSelected(newEntities: T[]): void {
const selectedIds = newEntities.map(e => e.id);
this._selected$.next(selectedIds);
}
isSelected(entity: T): boolean {
return this.selected.indexOf(entity) !== -1;
}
selectAll(): void {
if (this._allSelected) return this.setSelected([]);
this.setSelected(this._displayed);
}
select(entity: T): void {
const currentEntityIdx = this.selected.indexOf(entity);
if (currentEntityIdx === -1) return this.setSelected([...this.selected, entity]);
this.setSelected(this.selected.filter((el, idx) => idx !== currentEntityIdx));
}
updateSelection(): void {
const items = this._displayed.filter(item => this.selected.includes(item));
this.setSelected(items);
}
}