import { Injectable } from '@angular/core'; import { BehaviorSubject, combineLatest, Observable } from 'rxjs'; import { map, tap } from 'rxjs/operators'; import { FilterService, getFilteredEntities } from '../../filtering'; import { SearchService } from '../../search'; import { IListable } from '../models'; import { EntitiesService } from './entities.service'; import { any, getLength, shareDistinctLast, shareLast } from '../../utils'; @Injectable() export class ListingService { readonly displayed$: Observable; readonly displayedLength$: Observable; readonly areAllSelected$: Observable; readonly areSomeSelected$: Observable; readonly notAllSelected$: Observable; readonly selected$: Observable<(string | number)[]>; readonly selectedEntities$: Observable; readonly selectedLength$: Observable; private _displayed: E[] = []; private readonly _selected$ = new BehaviorSubject<(string | number)[]>([]); constructor( private readonly _filterService: FilterService, private readonly _searchService: SearchService, private readonly _entitiesService: EntitiesService, ) { this.displayed$ = this._getDisplayed$; this.displayedLength$ = this.displayed$.pipe(getLength, shareDistinctLast()); this.selected$ = this._selected$.asObservable().pipe(shareDistinctLast()); this.selectedEntities$ = this._selected$.asObservable().pipe( map(() => this.selected), shareLast(), ); this.selectedLength$ = this._selected$.pipe(getLength, shareDistinctLast()); this.areAllSelected$ = this._areAllSelected$; this.areSomeSelected$ = this._areSomeSelected$; this.notAllSelected$ = this._notAllSelected$; } get selected(): E[] { const selectedIds = this.selectedIds; return this._entitiesService.all.filter(a => selectedIds.includes(a.id)); } get selectedIds(): (string | number)[] { return this._selected$.getValue(); } private get _getDisplayed$(): Observable { const { filterGroups$ } = this._filterService; const { valueChanges$ } = this._searchService; return combineLatest([this._entitiesService.all$, filterGroups$, valueChanges$]).pipe( map(([entities, filterGroups]) => getFilteredEntities(entities, filterGroups)), map(entities => this._searchService.searchIn(entities)), tap(displayed => { this._displayed = displayed; this._updateSelection(); }), shareLast(), ); } private get _areAllSelected$(): Observable { return combineLatest([this.displayedLength$, this.selectedLength$]).pipe( map(([displayedLength, selectedLength]) => !!displayedLength && displayedLength === selectedLength), shareDistinctLast(), ); } private get _areSomeSelected$(): Observable { return this.selectedLength$.pipe( map(length => !!length), shareDistinctLast(), ); } private get _notAllSelected$(): Observable { return combineLatest([this.areAllSelected$, this.areSomeSelected$]).pipe( map(([allAreSelected, someAreSelected]) => !allAreSelected && someAreSelected), shareDistinctLast(), ); } private get _allSelected() { return this._displayed.length !== 0 && this._displayed.length === this.selected.length; } setSelected(newEntities: E[]): void { const selectedIds = newEntities.map(e => e.id); this._selected$.next(selectedIds); } isSelected(entity: E): boolean { return this.selectedIds.indexOf(entity.id) !== -1; } isSelected$(entity: E): Observable { return this._selected$.pipe( any(selectedId => selectedId === entity.id), shareLast(), ); } selectAll(): void { if (this._allSelected) { return this.setSelected([]); } this.setSelected(this._displayed); } select(entity: E): 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)); } private _updateSelection(): void { const items = this._displayed.filter(item => this.selected.includes(item)); this.setSelected(items); } }