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, PrimaryKey extends Id = Class['id']> extends AutoUnsubscribe implements OnDestroy { readonly filterService = inject(FilterService); readonly searchService = inject>(SearchService); readonly sortingService = inject>(SortingService); readonly entitiesService = inject>(EntitiesService); readonly listingService = inject>(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[]; abstract readonly tableHeaderLabel: string; @ViewChild('tableItemTemplate') readonly tableItemTemplate?: TemplateRef; get allEntities(): Class[] { return this.entitiesService.all; } get #sortedDisplayedEntities$(): Observable { 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 { return combineLatest([this.entitiesService.allLength$, this.listingService.displayedLength$]).pipe( map(([hasEntities, hasDisplayedEntities]) => !!hasEntities && !hasDisplayedEntities), shareDistinctLast(), ); } get #noContent$(): Observable { return combineLatest([this.noMatch$, this.entitiesService.noData$]).pipe( map(([noMatch, noData]) => noMatch || noData), shareDistinctLast(), ); } cast(entity: unknown): Class { return entity as Class; } }