Indexeddb Implementaion

This commit is contained in:
Lorenz Hilpert
2024-10-01 13:14:25 +02:00
parent a09eef038e
commit 5775e444b8
21 changed files with 275 additions and 182 deletions

View File

@@ -1,6 +1,4 @@
// start:ng42.barrel
export * from './application.module';
export * from './application.service';
export * from './defs';
export * from './store';
// end:ng42.barrel

View File

@@ -1,4 +1,3 @@
export interface CacheOptions {
ttl?: number;
persist?: boolean;
}

View File

@@ -1,80 +1,45 @@
import { Injectable } from '@angular/core';
import { CacheOptions } from './cache-options';
import { Cached } from './cached';
import { effect, inject, Injectable } from '@angular/core';
import { toSignal } from '@angular/core/rxjs-interop';
import { AuthService } from '@core/auth';
import { memorize } from '@utils/common';
import { interval } from 'rxjs';
@Injectable({
providedIn: 'root',
})
interface DbEntry<T> {
key: string;
token: object | string;
data: T;
ttl: number;
}
const H12INMS = 1000 * 60 * 60 * 12;
@Injectable({ providedIn: 'root' })
export class CacheService {
constructor() {
this._registerCleanupTask();
private auth = inject(AuthService);
private db!: IDBDatabase;
get sub() {
return this.auth.getClaimByKey('sub');
}
_registerCleanupTask() {
_cleanupInterval = toSignal(interval(1000 * 60));
_cleanupIntervalEffect = effect(() => {
this._cleanupInterval();
this.cleanup();
interval(1000 * 60).subscribe(() => {
this.cleanup();
});
});
get storeName() {
return 'cache';
}
set<T>(token: Object, data: T, options?: CacheOptions) {
const persist = options?.persist;
const ttl = options?.ttl;
const cached: Cached = {
data,
};
if (ttl) {
cached.until = Date.now() + ttl;
} else {
cached.until = Date.now() + 1000 * 60 * 60 * 12;
private getKey(token: Object | string): string {
if (typeof token === 'string') {
return this.hash(token);
}
if (persist) {
localStorage.setItem(this.getKey(token), this.serialize(cached));
} else {
sessionStorage.setItem(this.getKey(token), this.serialize(cached));
}
Object.freeze(cached);
return cached;
}
get<T = any>(token: Object, from?: 'session' | 'persist'): T {
let cached: Cached;
if (from === 'session') {
cached = this.deserialize(sessionStorage.getItem(this.getKey(token)));
} else if (from === 'persist') {
cached = this.deserialize(localStorage.getItem(this.getKey(token)));
} else {
cached = this.deserialize(sessionStorage.getItem(this.getKey(token))) || this.deserialize(localStorage.getItem(this.getKey(token)));
}
if (!cached) {
return undefined;
}
if (cached.until < Date.now()) {
this.delete(token, from);
return undefined;
}
return cached.data;
}
delete(token: Object, from: 'session' | 'persist' = 'session') {
if (from === 'session') {
sessionStorage.removeItem(this.getKey(token));
} else if (from === 'persist') {
localStorage.removeItem(this.getKey(token));
}
}
private getKey(token: Object) {
const key = `CacheService_` + this.hash(JSON.stringify(token));
return key;
return this.hash(JSON.stringify(token));
}
private hash(data: string): string {
@@ -82,35 +47,139 @@ export class CacheService {
for (let i = 0; i < data.length; i++) {
hash = data.charCodeAt(i) + ((hash << 5) - hash);
}
return hash.toString(16);
return (this.sub + hash).toString(16);
}
private serialize(data: Cached): string {
return JSON.stringify(data);
}
@memorize()
private async openDB(): Promise<IDBDatabase> {
if (this.db) {
return this.db; // Datenbank bereits geöffnet, bestehende Verbindung zurückgeben
}
private deserialize(data: string): Cached {
return JSON.parse(data);
}
return new Promise<IDBDatabase>((resolve, reject) => {
const request = indexedDB.open('isa-cache', 1);
cleanup() {
// get all keys created by this service by looking for the service name and remove the entries
// that ttl is expired
let localStorageKeys = Object.keys(localStorage).filter((key) => key.startsWith('CacheService_'));
let seesionStorageKeys = Object.keys(sessionStorage).filter((key) => key.startsWith('CacheService_'));
request.onerror = (event) => {
console.error('IndexedDB error:', event);
reject(event);
};
localStorageKeys.forEach((key) => {
const cached = this.deserialize(localStorage.getItem(key));
if (cached.until < Date.now()) {
localStorage.removeItem(key);
}
request.onupgradeneeded = (event) => {
this.db = (event.target as IDBOpenDBRequest).result;
if (!this.db.objectStoreNames.contains(this.storeName)) {
this.db.createObjectStore(this.storeName, { keyPath: 'key' });
}
};
request.onsuccess = (event) => {
this.db = (event.target as IDBOpenDBRequest).result;
resolve(this.db);
};
});
}
seesionStorageKeys.forEach((key) => {
const cached = this.deserialize(sessionStorage.getItem(key));
if (cached.until < Date.now()) {
sessionStorage.removeItem(key);
private async getObjectStore(mode: IDBTransactionMode = 'readonly'): Promise<IDBObjectStore> {
const db = await this.openDB(); // Datenbankverbindung öffnen oder wiederverwenden
const transaction = db.transaction(this.storeName, mode);
return transaction.objectStore(this.storeName);
}
async set<T>(token: object | string, data: T, options: { ttl?: number } = {}): Promise<string> {
const store = await this.getObjectStore('readwrite');
return new Promise<string>((resolve, reject) => {
const key = this.getKey(token);
const entry: DbEntry<T> = {
key,
data,
token,
ttl: Date.now() + (options.ttl || H12INMS),
};
const request = store.add(entry);
request.onsuccess = (event) => {
resolve(key);
};
request.onerror = (event) => {
reject(event);
};
});
}
private async cached(token: Object | string): Promise<DbEntry<any> | undefined> {
const store = await this.getObjectStore();
return new Promise<DbEntry<any> | undefined>((resolve, reject) => {
const request = store.get(this.getKey(token));
request.onsuccess = (event) => {
resolve((event.target as IDBRequest).result);
};
request.onerror = (event) => {
reject(event);
};
});
}
async get<T = any>(token: Object | string): Promise<T | undefined> {
const cached = await this.cached(token);
if (!cached) {
return undefined;
}
if (cached.ttl < Date.now()) {
this.delete(token);
return undefined;
}
return cached.data;
}
async delete(token: Object | string): Promise<void> {
const store = await this.getObjectStore('readwrite');
return new Promise<void>((resolve, reject) => {
const request = store.delete(this.getKey(token));
request.onsuccess = () => {
resolve();
};
request.onerror = (event) => {
reject(event);
};
});
}
async cleanup() {
const store = await this.getObjectStore('readwrite');
store.openCursor().onsuccess = (event) => {
const cursor = (event.target as IDBRequest).result;
if (cursor) {
if (cursor.value.ttl < Date.now()) {
store.delete(cursor.key);
}
cursor.continue();
}
};
return new Promise<void>((resolve, reject) => {
store.transaction.oncomplete = () => {
resolve();
};
store.transaction.onerror = (event) => {
reject(event);
};
});
}
async clear() {
const store = await this.getObjectStore('readwrite');
return new Promise<void>((resolve, reject) => {
const request = store.clear();
request.onsuccess = () => {
resolve();
};
request.onerror = (event) => {
reject(event);
};
});
}
}

View File

@@ -1,4 +0,0 @@
export interface Cached {
until?: number;
data?: any;
}

View File

@@ -160,12 +160,12 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
const cleanQueryParams = this.cleanupQueryParams(queryParams);
if (processChanged) {
this.scrollToItem(this._getScrollIndexFromCache());
this.scrollToItem(await this._getScrollIndexFromCache());
}
if (!isEqual(cleanQueryParams, this.cleanupQueryParams(this.searchService.filter.getQueryParams()))) {
await this.searchService.setDefaultFilter(queryParams);
const data = this.getCachedData(processId, queryParams, selectedBranch?.id);
const data = await this.getCachedData(processId, queryParams, selectedBranch?.id);
if (data.items?.length > 0) {
this.searchService.setItems(data.items);
this.searchService.setHits(data.hits);
@@ -267,22 +267,22 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
}
ngAfterViewInit(): void {
this.scrollToItem(this._getScrollIndexFromCache());
this._getScrollIndexFromCache().then((index) => this.scrollToItem(index));
}
private _addScrollIndexToCache(index: number): void {
this.cache.set<number>({ processId: this.getProcessId(), token: this.SCROLL_INDEX_TOKEN }, index);
}
private _getScrollIndexFromCache(): number {
return this.cache.get<number>({ processId: this.getProcessId(), token: this.SCROLL_INDEX_TOKEN }) ?? 0;
private async _getScrollIndexFromCache(): Promise<number> {
return (await this.cache.get<number>({ processId: this.getProcessId(), token: this.SCROLL_INDEX_TOKEN })) ?? 0;
}
scrollToItem(i?: number) {
async scrollToItem(i?: number) {
let index = i;
if (!index) {
index = this._getScrollIndexFromCache();
index = await this._getScrollIndexFromCache();
} else {
this._addScrollIndexToCache(index);
}

View File

@@ -168,8 +168,8 @@ export class CustomerOrderSearchStore extends ComponentStore<CustomerOrderSearch
this.patchState({ queryParams });
}
loadSearchHistoryFromSessionStorage() {
const searchHistory = this._cache.get<string[]>({ token: 'search_history_customer_orders' });
async loadSearchHistoryFromSessionStorage() {
const searchHistory = await this._cache.get<string[]>({ token: 'search_history_customer_orders' });
this.setSearchHistory(searchHistory?.slice(0, 7) ?? []);
}
@@ -220,12 +220,13 @@ export class CustomerOrderSearchStore extends ComponentStore<CustomerOrderSearch
this.searchResultClearedSubject.next();
}
}),
switchMap(([options, results, filter, branch]) => {
switchMap(async ([options, results, filter, branch]) => {
const queryToken = filter?.getQueryToken() ?? {};
const cached =
options?.siletReload && this._cache.get({ ...filter?.getQueryToken(), processId: this.processId, branchId: String(branch?.id) });
options?.siletReload &&
(await this._cache.get({ ...filter?.getQueryToken(), processId: this.processId, branchId: String(branch?.id) }));
if (cached) {
const cachedResults = this._cache.get({ ...queryToken, processId: this.processId, branchId: String(branch?.id) });
const cachedResults = await this._cache.get({ ...queryToken, processId: this.processId, branchId: String(branch?.id) });
if (cachedResults?.results?.length > 0) {
this.patchState(cachedResults);
this.searchResultFromCacheSubject.next({ hits: cachedResults.hits, results: cachedResults.results });

View File

@@ -209,7 +209,7 @@ export class CustomerOrderSearchResultsComponent
processId,
branchId: String(selectedBranch?.id),
};
const data = this._customerOrderSearchStore.getCachedData({ queryToken });
const data = await this._customerOrderSearchStore.getCachedData({ queryToken });
if (data?.results?.length > 0) {
this._customerOrderSearchStore.patchState({
@@ -229,7 +229,7 @@ export class CustomerOrderSearchResultsComponent
this._customerOrderSearchStore.search({ siletReload: true });
}
const scrollPos = this._getScrollPositionFromCache();
const scrollPos = await this._getScrollPositionFromCache();
if (!!scrollPos && this._activatedRoute.outlet === 'primary') {
setTimeout(() => {
this.scrollContainer?.scrollTo(scrollPos);
@@ -309,8 +309,8 @@ export class CustomerOrderSearchResultsComponent
}
}
private _getScrollPositionFromCache(): number {
return this._cache.get<number>({ processId: this._customerOrderSearchStore.processId, token: this.SCROLL_POSITION_TOKEN });
private async _getScrollPositionFromCache(): Promise<number> {
return await this._cache.get<number>({ processId: this._customerOrderSearchStore.processId, token: this.SCROLL_POSITION_TOKEN });
}
// After Navigating to Result Side Outlet

View File

@@ -155,8 +155,16 @@ export class CustomerTypeSelectorComponent
processId: undefined,
customerType: undefined,
p4mUser: false,
options: _cache.get('customerTypeOptions') ?? [],
options: [],
});
this.initOptions();
}
async initOptions() {
const options = await this._cache.get('customerTypeOptions');
if (options) {
this.patchState({ options });
}
}
ngOnInit(): void {}

View File

@@ -83,8 +83,8 @@ export class GoodsInListComponent implements OnInit, AfterViewInit, OnDestroy {
) {
this.store.search({
cb: () => {
setTimeout(() => {
this.scrollContainer?.scrollTo(this._getScrollPositionFromCache());
setTimeout(async () => {
this.scrollContainer?.scrollTo(await this._getScrollPositionFromCache());
this._removeScrollPositionFromCache();
}, 0);
},
@@ -103,8 +103,8 @@ export class GoodsInListComponent implements OnInit, AfterViewInit, OnDestroy {
this.store?.filter?.fromQueryParams(params);
this.store.search({
cb: () => {
setTimeout(() => {
this.scrollContainer?.scrollTo(this._getScrollPositionFromCache());
setTimeout(async () => {
this.scrollContainer?.scrollTo(await this._getScrollPositionFromCache());
this._removeScrollPositionFromCache();
}, 0);
},
@@ -159,8 +159,8 @@ export class GoodsInListComponent implements OnInit, AfterViewInit, OnDestroy {
);
}
private _getScrollPositionFromCache(): number {
return this._cache.get<number>({ processId: this._config.get('process.ids.goodsIn'), token: this.SCROLL_POSITION_TOKEN });
private async _getScrollPositionFromCache(): Promise<number> {
return await this._cache.get<number>({ processId: this._config.get('process.ids.goodsIn'), token: this.SCROLL_POSITION_TOKEN });
}
navigateToDetails(orderItem: OrderItemListItemDTO) {

View File

@@ -35,7 +35,7 @@ export class GoodsInRemissionPreviewComponent implements OnInit, OnDestroy {
listEmpty$ = combineLatest([this.loading$, this.hits$]).pipe(
map(([loading, hits]) => !loading && hits === 0),
shareReplay()
shareReplay(),
);
actions$ = this.items$.pipe(map((items) => items[0]?.actions));
@@ -60,7 +60,7 @@ export class GoodsInRemissionPreviewComponent implements OnInit, OnDestroy {
private _modal: UiModalService,
private _config: Config,
private _toast: ToasterService,
private _cache: CacheService
private _cache: CacheService,
) {}
ngOnInit(): void {
@@ -83,12 +83,12 @@ export class GoodsInRemissionPreviewComponent implements OnInit, OnDestroy {
private _addScrollPositionToCache(): void {
this._cache.set<number>(
{ processId: this._config.get('process.ids.goodsIn'), token: this.SCROLL_POSITION_TOKEN },
this.scrollContainer?.scrollPos
this.scrollContainer?.scrollPos,
);
}
private _getScrollPositionFromCache(): number {
return this._cache.get<number>({ processId: this._config.get('process.ids.goodsIn'), token: this.SCROLL_POSITION_TOKEN });
private async _getScrollPositionFromCache(): Promise<number> {
return await this._cache.get<number>({ processId: this._config.get('process.ids.goodsIn'), token: this.SCROLL_POSITION_TOKEN });
}
async createBreadcrumb() {
@@ -149,7 +149,7 @@ export class GoodsInRemissionPreviewComponent implements OnInit, OnDestroy {
this._store.searchResult$.pipe(takeUntil(this._onDestroy$)).subscribe(async (result) => {
await this.createBreadcrumb();
this.scrollContainer?.scrollTo(this._getScrollPositionFromCache() ?? 0);
this.scrollContainer?.scrollTo((await this._getScrollPositionFromCache()) ?? 0);
this._removeScrollPositionFromCache();
});
}

View File

@@ -39,7 +39,7 @@ export class PackageResultComponent implements OnInit, AfterViewInit, OnDestroy
* und mindestens ein package vorhanden ist.
*/
showList$ = combineLatest([this.store.fetching$, this.store.packages$]).pipe(
map(([fetching, packages]) => fetching || packages.length > 0)
map(([fetching, packages]) => fetching || packages.length > 0),
);
@ViewChild(PackageListComponent, { static: true, read: PackageListComponent })
@@ -54,7 +54,7 @@ export class PackageResultComponent implements OnInit, AfterViewInit, OnDestroy
private _breadcrumb: BreadcrumbService,
private _router: Router,
private _cache: CacheService,
private _navigation: PackageInspectionNavigationService
private _navigation: PackageInspectionNavigationService,
) {}
ngOnInit(): void {
@@ -78,13 +78,13 @@ export class PackageResultComponent implements OnInit, AfterViewInit, OnDestroy
initFilterSubscription() {
const initialFilter$ = this.store.filter$.pipe(
filter((f) => f instanceof UiFilter),
first()
first(),
);
const queryParams$ = this._activatedRoute.queryParams;
const filterSub = combineLatest([initialFilter$, queryParams$]).subscribe(([filter, queryParams]) => {
const filterSub = combineLatest([initialFilter$, queryParams$]).subscribe(async ([filter, queryParams]) => {
const restoredFilter = this.restoreFilterFromQueryParams(filter, queryParams);
const restoredData = this.restoreResultsFromCache(restoredFilter);
const restoredData = await this.restoreResultsFromCache(restoredFilter);
this.createBreadcrumbIfNotExists(restoredFilter || this.store.filter);
this.fetchPackages(restoredFilter, { keep: true, take: restoredData?.packages?.length });
});
@@ -110,8 +110,8 @@ export class PackageResultComponent implements OnInit, AfterViewInit, OnDestroy
return nextFilter;
}
restoreResultsFromCache(filter: UiFilter): PackageResultCacheData | undefined {
const data = this._cache.get<PackageResultCacheData>(filter.getQueryParams());
async restoreResultsFromCache(filter: UiFilter): Promise<PackageResultCacheData | undefined> {
const data = await this._cache.get<PackageResultCacheData>(filter.getQueryParams());
if (data) {
const update = this.hasUpdate();

View File

@@ -76,7 +76,7 @@ export class PickUpShelfInListComponent implements OnInit, AfterViewInit {
map(([filter, defaultFilter]) => {
const filterQueryParams = filter?.getQueryParams();
return !isEqual(filterQueryParams, Filter.create(defaultFilter).getQueryParams());
})
}),
);
fetching$: Observable<boolean> = this.store.fetchingList$;
@@ -117,19 +117,19 @@ export class PickUpShelfInListComponent implements OnInit, AfterViewInit {
private _activatedRoute: ActivatedRoute,
private _pickUpShelfInNavigation: PickupShelfInNavigationService,
private _cache: CacheService,
private _router: Router
private _router: Router,
) {}
ngOnInit() {
combineLatest([this.store.processId$, this._activatedRoute.queryParams])
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe(([_, queryParams]) => {
.subscribe(async ([_, queryParams]) => {
if (!this.store.list.length || !isEqual(queryParams, this.cleanupQueryParams(this.store.filter.getQueryParams()))) {
this.store.setQueryParams(queryParams);
this.store.fetchList({ emitFetchListResponse: false });
}
const scrollPos = this._getScrollPositionFromCache();
const scrollPos = await this._getScrollPositionFromCache();
if (!!scrollPos && this._activatedRoute.outlet === 'primary') {
setTimeout(() => {
this.scrollContainer?.scrollTo(scrollPos);
@@ -173,8 +173,8 @@ export class PickUpShelfInListComponent implements OnInit, AfterViewInit {
}
}
private _getScrollPositionFromCache(): number {
return this._cache.get<number>({ processId: this.store.processId, token: this.SCROLL_POSITION_TOKEN });
private async _getScrollPositionFromCache(): Promise<number> {
return await this._cache.get<number>({ processId: this.store.processId, token: this.SCROLL_POSITION_TOKEN });
}
// After Navigating to Result Side Outlet

View File

@@ -80,7 +80,7 @@ export class PickupShelfOutListComponent implements OnInit, AfterViewInit {
map(([filter, defaultFilter]) => {
const filterQueryParams = filter?.getQueryParams();
return !isEqual(filterQueryParams, Filter.create(defaultFilter).getQueryParams());
})
}),
);
fetching$: Observable<boolean> = this.store.fetchingList$;
@@ -120,9 +120,9 @@ export class PickupShelfOutListComponent implements OnInit, AfterViewInit {
actions$ = combineLatest([this.list$, this.selectedIds$]).pipe(
map(([items, selectedIds]) =>
items?.find((item) => selectedIds.find((orderItemSubsetId) => item.orderItemSubsetId === orderItemSubsetId))
items?.find((item) => selectedIds.find((orderItemSubsetId) => item.orderItemSubsetId === orderItemSubsetId)),
),
map((item) => item?.actions?.filter((action) => this.selectionRules(action)))
map((item) => item?.actions?.filter((action) => this.selectionRules(action))),
);
loadingFetchedActionButton$ = new BehaviorSubject<boolean>(false);
@@ -135,16 +135,16 @@ export class PickupShelfOutListComponent implements OnInit, AfterViewInit {
private _uiModal: UiModalService,
private _pickUpShelfOutNavigation: PickUpShelfOutNavigationService,
private _cache: CacheService,
private _router: Router
private _router: Router,
) {}
ngOnInit() {
this.processId$.pipe(takeUntilDestroyed(this.destroyRef), debounceTime(150)).subscribe((_) => {
this.processId$.pipe(takeUntilDestroyed(this.destroyRef), debounceTime(150)).subscribe(async (_) => {
if (!this.store.list.length) {
this.store.fetchList();
}
const scrollPos = this._getScrollPositionFromCache();
const scrollPos = await this._getScrollPositionFromCache();
if (!!scrollPos && this._activatedRoute.outlet === 'primary') {
setTimeout(() => {
this.scrollContainer?.scrollTo(scrollPos);
@@ -175,8 +175,8 @@ export class PickupShelfOutListComponent implements OnInit, AfterViewInit {
}
}
private _getScrollPositionFromCache(): number {
return this._cache.get<number>({ processId: this.store.processId, token: this.SCROLL_POSITION_TOKEN });
private async _getScrollPositionFromCache(): Promise<number> {
return await this._cache.get<number>({ processId: this.store.processId, token: this.SCROLL_POSITION_TOKEN });
}
// After Navigating to Result Side Outlet
@@ -190,7 +190,7 @@ export class PickupShelfOutListComponent implements OnInit, AfterViewInit {
getSelectedItem$(item: DBHOrderItemListItemDTO) {
return this.store.selectedListItems$.pipe(
map((selectedListItems) => selectedListItems?.find((i) => i?.orderItemSubsetId === item?.orderItemSubsetId))
map((selectedListItems) => selectedListItems?.find((i) => i?.orderItemSubsetId === item?.orderItemSubsetId)),
);
}
@@ -204,7 +204,7 @@ export class PickupShelfOutListComponent implements OnInit, AfterViewInit {
} else {
return item?.actions?.some((action) => this.selectionRules(action));
}
})
}),
);
}

View File

@@ -89,26 +89,26 @@ export class PickUpShelfListItemComponent {
private _processingStatusPipe: PickupShelfProcessingStatusPipe,
) {}
onDetailsClick() {
async onDetailsClick() {
if (this.isDesktopLarge) {
this.scrollIntoView();
}
if (!this.hasOrderItemInCache()) {
if (!(await this.hasOrderItemInCache())) {
this.addOrderItemIntoCache();
}
}
hasOrderItemInCache() {
async hasOrderItemInCache() {
const items =
this.cache.get<DBHOrderItemListItemDTO[]>({
(await this.cache.get<DBHOrderItemListItemDTO[]>({
name: 'orderItems',
orderId: this.item.orderId,
compartmentCode: this.item.compartmentCode,
compartmentInfo: this.item.compartmentInfo,
orderItemProcessingStatus: this.item.processingStatus,
store: 'PickupShelfDetailsStore',
}) ?? [];
})) ?? [];
return items.some((i) => i.orderItemSubsetId === this.item.orderItemSubsetId);
}
@@ -124,7 +124,7 @@ export class PickUpShelfListItemComponent {
store: 'PickupShelfDetailsStore',
},
[this.item],
{ persist: false, ttl: 1000 },
{ ttl: 1000 },
);
}

View File

@@ -362,9 +362,9 @@ export class PickupShelfDetailsStore extends ComponentStore<PickupShelfDetailsSt
),
);
private beforeFetchOrder = (orderId) => {
const order = this._cacheService.get<OrderDTO>({ name: 'order', orderId, store: 'PickupShelfDetailsStore' }) ?? { id: orderId };
const customer = this._cacheService.get<CustomerInfoDTO>({
private beforeFetchOrder = async (orderId) => {
const order = (await this._cacheService.get<OrderDTO>({ name: 'order', orderId, store: 'PickupShelfDetailsStore' })) ?? { id: orderId };
const customer = await this._cacheService.get<CustomerInfoDTO>({
name: 'customer',
orderId,
store: 'PickupShelfDetailsStore',
@@ -377,7 +377,6 @@ export class PickupShelfDetailsStore extends ComponentStore<PickupShelfDetailsSt
this.patchState({ fetchingOrder: false, order: res.result });
this._cacheService.set<OrderDTO>({ name: 'order', orderId: res.result.id, store: 'PickupShelfDetailsStore' }, res.result, {
persist: true,
ttl: 3600000,
});
@@ -416,7 +415,7 @@ export class PickupShelfDetailsStore extends ComponentStore<PickupShelfDetailsSt
),
);
private beforeFetchOrderItems = ({
private beforeFetchOrderItems = async ({
compartmentCode,
compartmentInfo,
orderItemProcessingStatus,
@@ -429,14 +428,14 @@ export class PickupShelfDetailsStore extends ComponentStore<PickupShelfDetailsSt
orderItemSubsetId?: number;
}) => {
const orderItems =
this._cacheService.get<DBHOrderItemListItemDTO[]>({
(await this._cacheService.get<DBHOrderItemListItemDTO[]>({
name: 'orderItems',
orderId: this.order?.id,
compartmentCode,
compartmentInfo,
orderItemProcessingStatus,
store: 'PickupShelfDetailsStore',
}) ?? [];
})) ?? [];
this.patchState({
fetchingOrderItems: true,
@@ -505,8 +504,8 @@ export class PickupShelfDetailsStore extends ComponentStore<PickupShelfDetailsSt
),
);
beforeFetchCustomer = () => {
const customer = this._cacheService.get<CustomerInfoDTO>({
beforeFetchCustomer = async () => {
const customer = await this._cacheService.get<CustomerInfoDTO>({
name: 'customer',
orderId: this.order.id,
store: 'PickupShelfDetailsStore',
@@ -548,7 +547,6 @@ export class PickupShelfDetailsStore extends ComponentStore<PickupShelfDetailsSt
store: 'PickupShelfDetailsStore',
},
customer,
{ persist: true, ttl: 3600000 },
);
this.patchState({ fetchingCustomer: false, customer });

View File

@@ -171,7 +171,7 @@ export class PickupShelfStore extends ComponentStore<PickupShelfState> implement
fetchQuerySettings = this.effect((trigger$) =>
trigger$.pipe(
map(() => this.beforeFetchQuerySettings()),
switchMap(() => this.beforeFetchQuerySettings()),
filter((shouldFetch) => shouldFetch),
switchMap(() =>
this._pickupShelfIOService.getQuerySettings().pipe(tapResponse(this.fetchQuerySettingsDone, this.fetchQuerySettingsError)),
@@ -183,8 +183,8 @@ export class PickupShelfStore extends ComponentStore<PickupShelfState> implement
* Before fetching query settings
* @returns true if query settings should be fetched
*/
private beforeFetchQuerySettings = () => {
const cachedQuerySettings = this._cacheService.get<QuerySettingsDTO>({
private beforeFetchQuerySettings = async () => {
const cachedQuerySettings = await this._cacheService.get<QuerySettingsDTO>({
name: 'pickup-shelf',
providerName: this._pickupShelfIOService.name(),
});
@@ -220,7 +220,7 @@ export class PickupShelfStore extends ComponentStore<PickupShelfState> implement
trigger$.pipe(
this.delayWhenFilterIsNotReady,
withLatestFrom(this.filter$, this.processId$),
map(([{ emitFetchListResponse } = { emitFetchListResponse: true }, filter, processId]) =>
switchMap(([{ emitFetchListResponse } = { emitFetchListResponse: true }, filter, processId]) =>
this.beforeFetchList(emitFetchListResponse, filter, processId),
),
switchMap(({ emitFetchListResponse, filter, processId, list }) =>
@@ -240,11 +240,11 @@ export class PickupShelfStore extends ComponentStore<PickupShelfState> implement
),
);
private beforeFetchList = (emitFetchListResponse: boolean, filter: Filter, processId: number) => {
private beforeFetchList = async (emitFetchListResponse: boolean, filter: Filter, processId: number) => {
this.cancelListRequests();
this.patchState({ fetchingList: true });
const queryToken = filter.getQueryParams();
const cachedListResponse = this._cacheService.get<ListResponseArgsOfDBHOrderItemListItemDTO>({ processId, queryToken });
const cachedListResponse = await this._cacheService.get<ListResponseArgsOfDBHOrderItemListItemDTO>({ processId, queryToken });
let list: DBHOrderItemListItemDTO[] = [];
@@ -274,9 +274,7 @@ export class PickupShelfStore extends ComponentStore<PickupShelfState> implement
this._fetchListResponse.next({ processId, response, queryParams });
}
this._cacheService.set<ListResponseArgsOfDBHOrderItemListItemDTO>({ processId, queryToken: queryParams }, response, {
persist: true,
});
this._cacheService.set<ListResponseArgsOfDBHOrderItemListItemDTO>({ processId, queryToken: queryParams }, response);
};
private fetchListError = (err: any) => {

View File

@@ -170,8 +170,8 @@ export class RemissionListComponentStore extends ComponentStore<RemissionState>
initSearch() {
combineLatest([this._triggerReload$, this._filterChange$])
.pipe(debounceTime(500), takeUntil(this._onDestroy$))
.subscribe(([reload, filter]) => {
const data = this.getCachedData();
.subscribe(async ([reload, filter]) => {
const data = await this.getCachedData();
if (reload || data.items?.length === 0 || filter) {
this.search({ newSearch: true });
}
@@ -254,10 +254,12 @@ export class RemissionListComponentStore extends ComponentStore<RemissionState>
// Only trigger search with _filterChange$ if no cached data available
// If cached data is available, there is no need to trigger the search
const data = this.getCachedData();
if (data.items?.length === 0) {
this._filterChange$.next(true);
}
this.getCachedData().then((data) => {
if (data.items?.length === 0) {
this._filterChange$.next(true);
}
});
},
(err) => {},
),
@@ -439,12 +441,12 @@ export class RemissionListComponentStore extends ComponentStore<RemissionState>
);
}
getCachedData() {
async getCachedData() {
return (
this._cache.get<{
(await this._cache.get<{
items: RemissionListItem[];
hits: number;
}>({ processId: String(this.processId) }) || { items: [], hits: 0 }
}>({ processId: String(this.processId) })) || { items: [], hits: 0 }
);
}

View File

@@ -162,7 +162,7 @@ export class RemissionListComponent implements OnInit, OnDestroy {
this._remissionListStore.setSelectedSource(source);
}
const data = this._remissionListStore.getCachedData();
const data = await this._remissionListStore.getCachedData();
if (data.items?.length !== 0) {
this._remissionListStore.setItems(data.items);
this._remissionListStore.setHits(data.hits);

BIN
bun.lockb Normal file
View File

Binary file not shown.

22
package-lock.json generated
View File

@@ -27,8 +27,10 @@
"@ngrx/entity": "^17.2.0",
"@ngrx/store": "^17.2.0",
"@ngrx/store-devtools": "^17.2.0",
"@tempfix/idb": "^8.0.3",
"angular-oauth2-oidc": "^17.0.2",
"angular-oauth2-oidc-jwks": "^17.0.2",
"idb": "^8.0.0",
"lodash": "^4.17.21",
"moment": "^2.30.1",
"ng2-pdf-viewer": "^10.2.2",
@@ -4267,6 +4269,11 @@
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz",
"integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg=="
},
"node_modules/@tempfix/idb": {
"version": "8.0.3",
"resolved": "https://registry.npmjs.org/@tempfix/idb/-/idb-8.0.3.tgz",
"integrity": "sha512-hPJQKO7+oAIY+pDNImrZ9QAINbz9KmwT+yO4iRVwdPanok2YKpaUxdJzIvCUwY0YgAawlvYdffbLvRLV5hbs2g=="
},
"node_modules/@testing-library/dom": {
"version": "8.20.1",
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.20.1.tgz",
@@ -8482,6 +8489,11 @@
"postcss": "^8.1.0"
}
},
"node_modules/idb": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/idb/-/idb-8.0.0.tgz",
"integrity": "sha512-l//qvlAKGmQO31Qn7xdzagVPPaHTxXx199MhrAFuVBTPqydcPYBWjkrbv4Y0ktB+GmWOiwHl237UUOrLmQxLvw=="
},
"node_modules/ieee754": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
@@ -18932,6 +18944,11 @@
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz",
"integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg=="
},
"@tempfix/idb": {
"version": "8.0.3",
"resolved": "https://registry.npmjs.org/@tempfix/idb/-/idb-8.0.3.tgz",
"integrity": "sha512-hPJQKO7+oAIY+pDNImrZ9QAINbz9KmwT+yO4iRVwdPanok2YKpaUxdJzIvCUwY0YgAawlvYdffbLvRLV5hbs2g=="
},
"@testing-library/dom": {
"version": "8.20.1",
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.20.1.tgz",
@@ -22198,6 +22215,11 @@
"dev": true,
"requires": {}
},
"idb": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/idb/-/idb-8.0.0.tgz",
"integrity": "sha512-l//qvlAKGmQO31Qn7xdzagVPPaHTxXx199MhrAFuVBTPqydcPYBWjkrbv4Y0ktB+GmWOiwHl237UUOrLmQxLvw=="
},
"ieee754": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",

View File

@@ -78,8 +78,10 @@
"@ngrx/entity": "^17.2.0",
"@ngrx/store": "^17.2.0",
"@ngrx/store-devtools": "^17.2.0",
"@tempfix/idb": "^8.0.3",
"angular-oauth2-oidc": "^17.0.2",
"angular-oauth2-oidc-jwks": "^17.0.2",
"idb": "^8.0.0",
"lodash": "^4.17.21",
"moment": "^2.30.1",
"ng2-pdf-viewer": "^10.2.2",