common-ui/src/lib/listing/listing-component.directive.ts
2023-01-05 16:03:46 +02:00

65 lines
2.6 KiB
TypeScript

import { Directive, inject, OnDestroy, TemplateRef, ViewChild } from '@angular/core';
import { combineLatest, Observable } from 'rxjs';
import { map, switchMap } from 'rxjs/operators';
import { FilterService } from '../filtering';
import { SortingService } from '../sorting';
import { AutoUnsubscribe, shareDistinctLast } from '../utils';
import { SearchService } from '../search';
import { EntitiesService, ListingService } from './services';
import { IListable, TableColumnConfig } from './models';
import { Id } from './models/trackable';
@Directive()
export abstract class ListingComponent<Class extends IListable<PrimaryKey>, PrimaryKey extends Id = Class['id']>
extends AutoUnsubscribe
implements OnDestroy
{
readonly filterService = inject(FilterService);
readonly searchService = inject<SearchService<Class>>(SearchService);
readonly sortingService = inject<SortingService<Class>>(SortingService);
readonly entitiesService = inject<EntitiesService<Class, Class>>(EntitiesService);
readonly listingService = inject<ListingService<Class>>(ListingService);
// TODO: Move to table content component
readonly noMatch$ = this.#noMatch$;
// TODO: Move to table content component
readonly noContent$ = this.#noContent$;
readonly sortedDisplayedEntities$ = this.#sortedDisplayedEntities$;
abstract readonly tableColumnConfigs: readonly TableColumnConfig<Class>[];
abstract readonly tableHeaderLabel: string;
@ViewChild('tableItemTemplate') readonly tableItemTemplate?: TemplateRef<unknown>;
get allEntities(): Class[] {
return this.entitiesService.all;
}
get #sortedDisplayedEntities$(): Observable<Class[]> {
const sort = (entities: Class[]) => this.sortingService.defaultSort(entities);
const sortedEntities$ = this.listingService.displayed$.pipe(map(sort));
return this.sortingService.sortingOption$.pipe(
switchMap(() => sortedEntities$),
shareDistinctLast(),
);
}
get #noMatch$(): Observable<boolean> {
return combineLatest([this.entitiesService.allLength$, this.listingService.displayedLength$]).pipe(
map(([hasEntities, hasDisplayedEntities]) => !!hasEntities && !hasDisplayedEntities),
shareDistinctLast(),
);
}
get #noContent$(): Observable<boolean> {
return combineLatest([this.noMatch$, this.entitiesService.noData$]).pipe(
map(([noMatch, noData]) => noMatch || noData),
shareDistinctLast(),
);
}
cast(entity: unknown): Class {
return entity as Class;
}
}