import { inject, Injectable } from '@angular/core'; import { BehaviorSubject, Observable, switchMap } from 'rxjs'; import { HttpClient } from '@angular/common/http'; import { tap } from 'rxjs/operators'; import { HeadersConfiguration, mapEach, RequiredParam, Validate } from '../utils'; @Injectable() export abstract class StatsService { protected abstract readonly _primaryKey: string; protected abstract readonly _entityClass: new (entityInterface: I, ...args: unknown[]) => E; protected abstract readonly _defaultModelPath: string; readonly #http = inject(HttpClient); readonly #map = new Map>(); @Validate() getFor(@RequiredParam() ids: string[]): Observable { const request = this.#http.post(`/${encodeURI(this._defaultModelPath)}`, ids, { headers: HeadersConfiguration.getHeaders(), observe: 'body', }); return request.pipe( mapEach(entity => new this._entityClass(entity)), tap(entities => entities.forEach(entity => this.set(entity))), ); } get(key: string): E { return this._getBehaviourSubject(key).value; } set(stats: E): void { if (!this.#map.has(this._pluckPrimaryKey(stats))) { this.#map.set(this._pluckPrimaryKey(stats), new BehaviorSubject(stats)); return; } const old = this.get(this._pluckPrimaryKey(stats)); if (JSON.stringify(old) !== JSON.stringify(stats)) { this._getBehaviourSubject(this._pluckPrimaryKey(stats)).next(stats); } } watch$(key: string): Observable { const subject = this.#map.get(key); if (!subject) { return this.getFor([key]).pipe(switchMap(() => this._getBehaviourSubject(key).asObservable())); } return subject.asObservable(); } private _pluckPrimaryKey(stats: E): string { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore return stats[this._primaryKey] as string; } private _getBehaviourSubject(key: string): BehaviorSubject { const subject = this.#map.get(key); if (!subject) { throw new Error(`No stats for key ${key}`); } return subject; } }