Abstract entities map service

This commit is contained in:
Adina Țeudan 2022-02-10 18:58:04 +02:00
parent 0bab458476
commit 492ac8d7c0
2 changed files with 109 additions and 0 deletions

View File

@ -0,0 +1,108 @@
import { Inject, Injectable } from '@angular/core';
import { BehaviorSubject, Observable, Subject } from 'rxjs';
import { filter, startWith } from 'rxjs/operators';
import { Entity } from '../listing';
import { RequiredParam, shareLast, Validate } from '../utils';
@Injectable({ providedIn: 'root' })
export abstract class EntitiesMapService<E extends Entity<I>, I> {
protected readonly _map = new Map<string, BehaviorSubject<E[]>>();
private readonly _entityChanged$ = new Subject<E>();
private readonly _entityDeleted$ = new Subject<E>();
protected constructor(@Inject('ENTITY_PRIMARY_KEY') protected readonly _primaryKey: string) {}
get$(key: string) {
if (!this._map.has(key)) {
this._map.set(key, new BehaviorSubject<E[]>([]));
}
return this._getBehaviourSubject(key).asObservable();
}
has(dossierId: string) {
return this._map.has(dossierId);
}
get(key: string): E[];
get(key: string, id: string): E | undefined;
get(key: string, id?: string): E | E[] | undefined {
const value = this._getBehaviourSubject(key)?.value;
if (!id) {
return value ?? [];
}
return value?.find(item => item.id === id);
}
set(key: string, entities: E[]): void {
if (!this._map.has(key)) {
this._map.set(key, new BehaviorSubject<E[]>(entities));
return entities.forEach(entity => this._entityChanged$.next(entity));
}
const changedEntities: E[] = [];
const deletedEntities = this.get(key).filter(oldEntity => !entities.find(newEntity => newEntity.id === oldEntity.id));
// Keep old object references for unchanged entities
const newEntities: E[] = entities.map(newEntity => {
const oldEntity: E | undefined = this.get(key, newEntity.id);
if (oldEntity && newEntity.isEqual(oldEntity)) {
return oldEntity;
}
changedEntities.push(newEntity);
return newEntity;
});
this._getBehaviourSubject(key).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);
}
}
replace(entities: E[]) {
const key = this._pluckPrimaryKey(entities[0]);
const entityIds = entities.map(entity => entity.id);
const existingEntities = this.get(key).filter(entity => entityIds.includes(entity.id));
const newEntities = entities.filter(entity => {
const old = existingEntities.find(e => e.id === entity.id);
return !old || !entity.isEqual(old);
});
if (newEntities.length) {
const all = this.get(key).filter(e => !newEntities.map(entity => entity.id).includes(e.id));
this.set(key, [...all, ...newEntities]);
}
}
@Validate()
watch$(@RequiredParam() key: string, @RequiredParam() entityId: string): Observable<E> {
return this._entityChanged$.pipe(
filter(entity => entity.id === entityId),
startWith(this.get(key, entityId) as E),
shareLast(),
);
}
watchDeleted$(entityId: string): Observable<E> {
return this._entityDeleted$.pipe(filter(entity => entity.id === entityId));
}
private _pluckPrimaryKey(entity: E): string {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
return entity[this._primaryKey] as string;
}
private _getBehaviourSubject(key: string): BehaviorSubject<E[]> {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return this._map.get(key)!;
}
}

View File

@ -4,3 +4,4 @@ export * from './error-message.service';
export * from './generic.service';
export * from './composite-route.guard';
export * from './stats.service';
export * from './entities-map.service';