Fix subscription leak

This commit is contained in:
aditya.chandel
2025-06-27 20:45:30 -06:00
parent b76c0023ba
commit 3d71a21add

View File

@@ -1,24 +1,48 @@
import {Component, inject, OnDestroy, OnInit, Optional} from '@angular/core';
import {ActivatedRoute} from '@angular/router';
import {UserService} from '../../../settings/user-management/user.service';
import {Book, BookRecommendation} from '../../model/book.model';
import {BehaviorSubject, Observable, Subscription} from 'rxjs';
import {distinctUntilChanged, filter, map, shareReplay, switchMap, take, tap} from 'rxjs/operators';
import {BookService} from '../../service/book.service';
import {AppSettingsService} from '../../../core/service/app-settings.service';
import {Tab, TabList, TabPanel, TabPanels, Tabs} from 'primeng/tabs';
import {MetadataViewerComponent} from './metadata-viewer/metadata-viewer.component';
import {MetadataEditorComponent} from './metadata-editor/metadata-editor.component';
import {MetadataSearcherComponent} from './metadata-searcher/metadata-searcher.component';
import {DynamicDialogConfig} from 'primeng/dynamicdialog';
import {BookMetadataHostService} from '../../../book-metadata-host-service';
import { Component, inject, OnDestroy, OnInit, Optional } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { UserService } from '../../../settings/user-management/user.service';
import { Book, BookRecommendation } from '../../model/book.model';
import { BehaviorSubject, Observable, Subject } from 'rxjs';
import {
distinctUntilChanged,
filter,
map,
shareReplay,
switchMap,
takeUntil,
tap,
take,
} from 'rxjs/operators';
import { BookService } from '../../service/book.service';
import { AppSettingsService } from '../../../core/service/app-settings.service';
import {
Tab,
TabList,
TabPanel,
TabPanels,
Tabs,
} from 'primeng/tabs';
import { MetadataViewerComponent } from './metadata-viewer/metadata-viewer.component';
import { MetadataEditorComponent } from './metadata-editor/metadata-editor.component';
import { MetadataSearcherComponent } from './metadata-searcher/metadata-searcher.component';
import { DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog';
import { BookMetadataHostService } from '../../../book-metadata-host-service';
@Component({
selector: 'app-book-metadata-center',
standalone: true,
templateUrl: './book-metadata-center.component.html',
imports: [Tabs, TabList, Tab, TabPanels, TabPanel, MetadataViewerComponent, MetadataEditorComponent, MetadataSearcherComponent],
styleUrls: ['./book-metadata-center.component.scss']
imports: [
Tabs,
TabList,
Tab,
TabPanels,
TabPanel,
MetadataViewerComponent,
MetadataEditorComponent,
MetadataSearcherComponent,
],
styleUrls: ['./book-metadata-center.component.scss'],
})
export class BookMetadataCenterComponent implements OnInit, OnDestroy {
private route = inject(ActivatedRoute);
@@ -26,6 +50,7 @@ export class BookMetadataCenterComponent implements OnInit, OnDestroy {
private userService = inject(UserService);
private appSettingsService = inject(AppSettingsService);
private metadataHostService = inject(BookMetadataHostService);
private destroy$ = new Subject<void>();
book$!: Observable<Book>;
recommendedBooks: BookRecommendation[] = [];
@@ -33,14 +58,13 @@ export class BookMetadataCenterComponent implements OnInit, OnDestroy {
canEditMetadata: boolean = false;
admin: boolean = false;
private userSubscription: Subscription = Subscription.EMPTY;
private tabSubscription: Subscription = Subscription.EMPTY;
private recommendationSubscription: Subscription = Subscription.EMPTY;
private appSettings$ = this.appSettingsService.appSettings$;
private currentBookId$ = new BehaviorSubject<number | null>(null);
constructor(@Optional() private config?: DynamicDialogConfig) {
}
constructor(
@Optional() private config?: DynamicDialogConfig,
@Optional() private ref?: DynamicDialogRef
) {}
ngOnInit(): void {
this.bookService.loadBooks();
@@ -51,7 +75,8 @@ export class BookMetadataCenterComponent implements OnInit, OnDestroy {
this.route.paramMap
.pipe(
map(params => Number(params.get('bookId'))),
filter(bookId => !isNaN(bookId))
filter(bookId => !isNaN(bookId)),
takeUntil(this.destroy$)
)
.subscribe(bookId => this.currentBookId$.next(bookId));
}
@@ -59,7 +84,8 @@ export class BookMetadataCenterComponent implements OnInit, OnDestroy {
this.metadataHostService.bookSwitches$
.pipe(
filter((bookId): bookId is number => !!bookId),
distinctUntilChanged()
distinctUntilChanged(),
takeUntil(this.destroy$)
)
.subscribe(bookId => {
this.currentBookId$.next(bookId);
@@ -72,29 +98,39 @@ export class BookMetadataCenterComponent implements OnInit, OnDestroy {
this.bookService.bookState$.pipe(
map(state => state.books?.find(b => b.id === bookId)),
filter((book): book is Book => !!book && !!book.metadata),
switchMap(book => this.bookService.getBookByIdFromAPI(book.id, true))
switchMap(book =>
this.bookService.getBookByIdFromAPI(book.id, true)
)
)
),
takeUntil(this.destroy$),
shareReplay(1)
);
this.recommendationSubscription = this.currentBookId$.pipe(
filter((id): id is number => id != null),
tap(bookId => this.fetchBookRecommendationsIfNeeded(bookId))
).subscribe();
this.currentBookId$
.pipe(
filter((id): id is number => id != null),
takeUntil(this.destroy$)
)
.subscribe(bookId => this.fetchBookRecommendationsIfNeeded(bookId));
this.tabSubscription = this.route.queryParamMap.pipe(
map(params => params.get('tab') ?? 'view'),
distinctUntilChanged()
).subscribe(tab => {
const validTabs = ['view', 'edit', 'match'];
this.tab = validTabs.includes(tab) ? tab : 'view';
});
this.route.queryParamMap
.pipe(
map(params => params.get('tab') ?? 'view'),
distinctUntilChanged(),
takeUntil(this.destroy$)
)
.subscribe(tab => {
const validTabs = ['view', 'edit', 'match'];
this.tab = validTabs.includes(tab) ? tab : 'view';
});
this.userSubscription = this.userService.userState$.subscribe(userData => {
this.canEditMetadata = userData?.permissions?.canEditMetadata ?? false;
this.admin = userData?.permissions?.admin ?? false;
});
this.userService.userState$
.pipe(takeUntil(this.destroy$))
.subscribe(userData => {
this.canEditMetadata = userData?.permissions?.canEditMetadata ?? false;
this.admin = userData?.permissions?.admin ?? false;
});
}
private fetchBookRecommendationsIfNeeded(bookId: number): void {
@@ -105,18 +141,20 @@ export class BookMetadataCenterComponent implements OnInit, OnDestroy {
)
.subscribe(settings => {
if (settings!.similarBookRecommendation ?? false) {
this.bookService.getBookRecommendations(bookId).subscribe(recommendations => {
this.recommendedBooks = recommendations.sort(
(a, b) => (b.similarityScore ?? 0) - (a.similarityScore ?? 0)
);
});
this.bookService
.getBookRecommendations(bookId)
.pipe(takeUntil(this.destroy$))
.subscribe(recommendations => {
this.recommendedBooks = recommendations.sort(
(a, b) => (b.similarityScore ?? 0) - (a.similarityScore ?? 0)
);
});
}
});
}
ngOnDestroy(): void {
this.userSubscription.unsubscribe();
this.tabSubscription.unsubscribe();
this.recommendationSubscription.unsubscribe();
this.destroy$.next();
this.destroy$.complete();
}
}