119 lines
4.1 KiB
TypeScript
119 lines
4.1 KiB
TypeScript
import { Inject, Injectable, InjectionToken, Injector, Optional } from '@angular/core';
|
|
import { BehaviorSubject, Observable, Subject } from 'rxjs';
|
|
import { filter, map, startWith, tap } from 'rxjs/operators';
|
|
import { IListable } from '../models';
|
|
import { GenericService, QueryParam } from '../../services';
|
|
import { getLength, List, mapEach, shareDistinctLast, shareLast } from '../../utils';
|
|
|
|
/**
|
|
* This should be removed when refactoring is done
|
|
*/
|
|
const ENTITY_PATH = new InjectionToken<string>('This is here for compatibility while refactoring things.');
|
|
const ENTITY_CLASS = new InjectionToken<string>('This is here for compatibility while refactoring things.');
|
|
|
|
@Injectable()
|
|
/**
|
|
* E for Entity
|
|
* I for Interface
|
|
* By default, if no interface is provided, I = E
|
|
*/
|
|
export class EntitiesService<E extends IListable, I = E> extends GenericService<I> {
|
|
readonly noData$: Observable<boolean>;
|
|
readonly all$: Observable<E[]>;
|
|
readonly allLength$: Observable<number>;
|
|
protected readonly _entityChanged$ = new Subject<E>();
|
|
protected readonly _entityDeleted$ = new Subject<E>();
|
|
private readonly _all$ = new BehaviorSubject<E[]>([]);
|
|
|
|
constructor(
|
|
protected readonly _injector: Injector,
|
|
@Optional() @Inject(ENTITY_CLASS) private readonly _entityClass: new (entityInterface: I, ...args: unknown[]) => E,
|
|
@Optional() @Inject(ENTITY_PATH) protected readonly _defaultModelPath = '',
|
|
) {
|
|
super(_injector, _defaultModelPath);
|
|
this.all$ = this._all$.asObservable().pipe(shareDistinctLast());
|
|
this.allLength$ = this._all$.pipe(getLength, shareDistinctLast());
|
|
this.noData$ = this._noData$;
|
|
}
|
|
|
|
get all(): E[] {
|
|
return Object.values(this._all$.getValue());
|
|
}
|
|
|
|
private get _noData$(): Observable<boolean> {
|
|
return this.allLength$.pipe(
|
|
map(length => length === 0),
|
|
shareDistinctLast(),
|
|
);
|
|
}
|
|
|
|
loadAll(...args: unknown[]): Observable<E[]>;
|
|
loadAll(modelPath = this._defaultModelPath, queryParams?: List<QueryParam>): Observable<E[]> {
|
|
return this.getAll(modelPath, queryParams).pipe(
|
|
mapEach(entity => new this._entityClass(entity)),
|
|
tap((entities: E[]) => this.setEntities(entities)),
|
|
);
|
|
}
|
|
|
|
loadAllIfEmpty(modelPath = this._defaultModelPath, queryParams?: List<QueryParam>): Promise<unknown> | void {
|
|
if (!this.all.length) {
|
|
return this.loadAll(modelPath, queryParams).toPromise();
|
|
}
|
|
}
|
|
|
|
getEntityChanged$(entityId: string): Observable<E | undefined> {
|
|
return this._entityChanged$.pipe(
|
|
filter(entity => entity.id === entityId),
|
|
startWith(this.find(entityId)),
|
|
shareLast(),
|
|
);
|
|
}
|
|
|
|
getEntityDeleted$(entityId: string): Observable<E | undefined> {
|
|
return this._entityDeleted$.pipe(filter(entity => entity.id === entityId));
|
|
}
|
|
|
|
setEntities(entities: E[]): void {
|
|
const changedEntities: E[] = [];
|
|
const deletedEntities = this.all.filter(oldEntity => !entities.find(newEntity => newEntity.id === oldEntity.id));
|
|
|
|
// Keep old object references for unchanged entities
|
|
const newEntities = entities.map(entity => {
|
|
const oldEntity = this.find(entity.id);
|
|
|
|
if (oldEntity && JSON.stringify(oldEntity) === JSON.stringify(entity)) {
|
|
return oldEntity;
|
|
}
|
|
|
|
changedEntities.push(entity);
|
|
return entity;
|
|
});
|
|
|
|
this._all$.next(newEntities);
|
|
|
|
// Emit observables only after entities have been updated
|
|
|
|
for (const entity of changedEntities) {
|
|
this._entityChanged$.next(entity);
|
|
}
|
|
|
|
for (const entity of deletedEntities) {
|
|
this._entityDeleted$.next(entity);
|
|
}
|
|
}
|
|
|
|
find(id: string): E | undefined {
|
|
return this.all.find(entity => entity.id === id);
|
|
}
|
|
|
|
has(id: string): boolean {
|
|
return this.all.some(entity => entity.id === id);
|
|
}
|
|
|
|
replace(entity: E): void {
|
|
const all = this.all.filter(item => item.id !== entity.id);
|
|
this.setEntities([...all, entity]);
|
|
this._entityChanged$.next(entity);
|
|
}
|
|
}
|