From a740e96c8a00a30691d0acb25d00614f1d65dfb8 Mon Sep 17 00:00:00 2001 From: Enno Gotthold Date: Mon, 2 Sep 2024 09:20:58 +0200 Subject: [PATCH] Unsubscribe during ngOnDestroy lifecycle This is needed to prevent memory leaks in the application. --- .../app/app-events/app-events.component.ts | 47 ++++++++++------ .../src/app/navbar/navbar.component.ts | 53 ++++++++++++------- .../settings/view/settings-view.component.ts | 33 ++++++++---- .../app/signatures/signatures.component.ts | 20 +++++-- 4 files changed, 105 insertions(+), 48 deletions(-) diff --git a/projects/cobbler-frontend/src/app/app-events/app-events.component.ts b/projects/cobbler-frontend/src/app/app-events/app-events.component.ts index 5558654..a4f8b04 100644 --- a/projects/cobbler-frontend/src/app/app-events/app-events.component.ts +++ b/projects/cobbler-frontend/src/app/app-events/app-events.component.ts @@ -1,5 +1,5 @@ import { CommonModule, DatePipe } from '@angular/common'; -import { Component, Inject, OnInit } from '@angular/core'; +import { Component, Inject, OnDestroy, OnInit } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; import { MatIconModule } from '@angular/material/icon'; import { MatListModule } from '@angular/material/list'; @@ -9,6 +9,8 @@ import { MatTableDataSource, MatTableModule } from '@angular/material/table'; import { MatDialog } from '@angular/material/dialog'; import { CobblerApiService, Event } from 'cobbler-api'; import { DialogBoxTextConfirmComponent } from '../common/dialog-box-text-confirm/dialog-box-text-confirm'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; @Component({ selector: 'cobbler-app-events', @@ -26,7 +28,11 @@ import { DialogBoxTextConfirmComponent } from '../common/dialog-box-text-confirm CommonModule, ], }) -export class AppEventsComponent implements OnInit { +export class AppEventsComponent implements OnInit, OnDestroy { + // Unsubscribe + private ngUnsubscribe = new Subject(); + + // Table displayedColumns: string[] = [ 'name', 'state', @@ -42,22 +48,33 @@ export class AppEventsComponent implements OnInit { ) {} ngOnInit(): void { - this.cobblerApiService.get_events('').subscribe((value: Array) => { - this.cobblerEvents.data = value; - }); + this.cobblerApiService + .get_events('') + .pipe(takeUntil(this.ngUnsubscribe)) + .subscribe((value: Array) => { + this.cobblerEvents.data = value; + }); + } + + ngOnDestroy() { + this.ngUnsubscribe.next(); + this.ngUnsubscribe.complete(); } showLogs(eventId: string, name: string) { - this.cobblerApiService.get_event_log(eventId).subscribe((value: string) => { - const dialogRef = this.dialog.open(DialogBoxTextConfirmComponent, { - data: { - eventId: eventId, - name: name, - eventLog: value, - }, - }); + this.cobblerApiService + .get_event_log(eventId) + .pipe(takeUntil(this.ngUnsubscribe)) + .subscribe((value: string) => { + const dialogRef = this.dialog.open(DialogBoxTextConfirmComponent, { + data: { + eventId: eventId, + name: name, + eventLog: value, + }, + }); - dialogRef.afterClosed().subscribe(); - }); + dialogRef.afterClosed().subscribe(); + }); } } diff --git a/projects/cobbler-frontend/src/app/navbar/navbar.component.ts b/projects/cobbler-frontend/src/app/navbar/navbar.component.ts index d97db4b..439facf 100644 --- a/projects/cobbler-frontend/src/app/navbar/navbar.component.ts +++ b/projects/cobbler-frontend/src/app/navbar/navbar.component.ts @@ -1,15 +1,16 @@ -import { Component, EventEmitter, Output } from '@angular/core'; +import { Component, EventEmitter, OnDestroy, Output } from '@angular/core'; import { MatIconModule, MatIconRegistry } from '@angular/material/icon'; import { MatSnackBar } from '@angular/material/snack-bar'; import { DomSanitizer } from '@angular/platform-browser'; import { Router, RouterLink } from '@angular/router'; import { CobblerApiService } from 'cobbler-api'; -import { Subscription } from 'rxjs'; +import { Subject, Subscription } from 'rxjs'; import { AuthGuardService } from '../services/auth-guard.service'; import { UserService } from '../services/user.service'; import { MatToolbarModule } from '@angular/material/toolbar'; import { CommonModule } from '@angular/common'; import { MatButtonModule } from '@angular/material/button'; +import { takeUntil } from 'rxjs/operators'; @Component({ selector: 'cobbler-navbar', @@ -24,7 +25,11 @@ import { MatButtonModule } from '@angular/material/button'; MatButtonModule, ], }) -export class NavbarComponent { +export class NavbarComponent implements OnDestroy { + // Unsubscribe + private ngUnsubscribe = new Subject(); + + // Navbar @Output() toggleSidenav = new EventEmitter(); cobbler_version: String = 'Unknown'; islogged: boolean = false; @@ -46,22 +51,32 @@ export class NavbarComponent { ), ); - this.subscription = this.authO.authorized.subscribe((value) => { - if (value) { - this.islogged = value; - } else { - this.islogged = false; - } - }); - cobblerApiService.extended_version().subscribe( - (value) => { - this.cobbler_version = value.version; - }, - (error) => { - this.cobbler_version = 'Error'; - this._snackBar.open(error.message, 'Close'); - }, - ); + this.subscription = this.authO.authorized + .pipe(takeUntil(this.ngUnsubscribe)) + .subscribe((value) => { + if (value) { + this.islogged = value; + } else { + this.islogged = false; + } + }); + cobblerApiService + .extended_version() + .pipe(takeUntil(this.ngUnsubscribe)) + .subscribe( + (value) => { + this.cobbler_version = value.version; + }, + (error) => { + this.cobbler_version = 'Error'; + this._snackBar.open(error.message, 'Close'); + }, + ); + } + + ngOnDestroy(): void { + this.ngUnsubscribe.next(); + this.ngUnsubscribe.complete(); } logout(): void { diff --git a/projects/cobbler-frontend/src/app/settings/view/settings-view.component.ts b/projects/cobbler-frontend/src/app/settings/view/settings-view.component.ts index c719edf..3d336d3 100644 --- a/projects/cobbler-frontend/src/app/settings/view/settings-view.component.ts +++ b/projects/cobbler-frontend/src/app/settings/view/settings-view.component.ts @@ -1,4 +1,4 @@ -import { AfterViewInit, Component, ViewChild } from '@angular/core'; +import {AfterViewInit, Component, OnDestroy, ViewChild} from '@angular/core'; import { MatPaginator, MatPaginatorModule } from '@angular/material/paginator'; import { MatTableDataSource, MatTableModule } from '@angular/material/table'; import { MatSort, MatSortModule } from '@angular/material/sort'; @@ -13,6 +13,8 @@ import { ViewableTreeComponent } from '../../common/viewable-tree/viewable-tree. import { MatInputModule } from '@angular/material/input'; import { MatButtonModule } from '@angular/material/button'; import { MatTooltipModule } from '@angular/material/tooltip'; +import {Subject} from "rxjs"; +import {takeUntil} from "rxjs/operators"; interface SettingsTableRowData { name: string; @@ -41,7 +43,11 @@ interface SettingsTableRowData { MatSortModule, ], }) -export class SettingsViewComponent implements AfterViewInit { +export class SettingsViewComponent implements AfterViewInit, OnDestroy { + // Unsubscribe + private ngUnsubscribe = new Subject(); + + // Table data = new MatTableDataSource([]); displayedColumns: string[] = ['name', 'value', 'actions']; @@ -49,14 +55,16 @@ export class SettingsViewComponent implements AfterViewInit { @ViewChild(MatSort) sort: MatSort; constructor(service: ItemSettingsService) { - service.getAll().subscribe((data: Settings) => { - const settings_data: SettingsTableRowData[] = []; - for (const key in data) { - settings_data.push({ - name: key, - value: data[key], - type: typeof data[key], - }); + service.getAll() + .pipe(takeUntil(this.ngUnsubscribe)) + .subscribe((data: Settings) => { + const settings_data: SettingsTableRowData[] = []; + for (const key in data) { + settings_data.push({ + name: key, + value: data[key], + type: typeof data[key], + }); } this.data.data = settings_data; }); @@ -67,6 +75,11 @@ export class SettingsViewComponent implements AfterViewInit { this.data.sort = this.sort; } + ngOnDestroy() { + this.ngUnsubscribe.next() + this.ngUnsubscribe.complete() + } + applyFilter(event: Event) { const filterValue = (event.target as HTMLInputElement).value; this.data.filter = filterValue.trim().toLowerCase(); diff --git a/projects/cobbler-frontend/src/app/signatures/signatures.component.ts b/projects/cobbler-frontend/src/app/signatures/signatures.component.ts index 2dc0fd5..80a6bb2 100644 --- a/projects/cobbler-frontend/src/app/signatures/signatures.component.ts +++ b/projects/cobbler-frontend/src/app/signatures/signatures.component.ts @@ -1,5 +1,5 @@ import { AsyncPipe, NgForOf, NgIf } from '@angular/common'; -import { Component, OnInit } from '@angular/core'; +import {Component, OnDestroy, OnInit} from '@angular/core'; import { MatDivider } from '@angular/material/divider'; import { MatList, MatListItem } from '@angular/material/list'; import { MatProgressSpinner } from '@angular/material/progress-spinner'; @@ -16,7 +16,7 @@ import { MatRowDef, MatTable, } from '@angular/material/table'; -import { filter, repeat, take } from 'rxjs/operators'; +import {filter, repeat, take, takeUntil} from 'rxjs/operators'; import { UserService } from '../services/user.service'; import { CobblerApiService } from 'cobbler-api'; import { @@ -31,6 +31,7 @@ import { import { FlatTreeControl } from '@angular/cdk/tree'; import { MatIcon } from '@angular/material/icon'; import { MatIconButton } from '@angular/material/button'; +import {Subject} from "rxjs"; interface TableRow { key: string; @@ -85,7 +86,10 @@ interface OsBreedFlatNode { templateUrl: './signatures.component.html', styleUrl: './signatures.component.scss', }) -export class SignaturesComponent implements OnInit { +export class SignaturesComponent implements OnInit, OnDestroy { + // Unsubscribe + private ngUnsubscribe = new Subject(); + // Table columns = [ { @@ -137,13 +141,20 @@ export class SignaturesComponent implements OnInit { this.generateSignatureUiData(); } + ngOnDestroy(): void { + this.ngUnsubscribe.next() + this.ngUnsubscribe.complete() + } + hasChild = (_: number, node: OsBreedFlatNode) => node.expandable; hasOsVersion = (_: number, node: OsBreedFlatNode) => typeof node.data !== 'string'; generateSignatureUiData(): void { - this.cobblerApiService.get_signatures(this.userService.token).subscribe( + this.cobblerApiService.get_signatures(this.userService.token) + .pipe(takeUntil(this.ngUnsubscribe)) + .subscribe( (value) => { const newData: Array = []; for (const k in value.breeds) { @@ -174,6 +185,7 @@ export class SignaturesComponent implements OnInit { this.isLoading = true; this.cobblerApiService .background_signature_update(this.userService.token) + .pipe(takeUntil(this.ngUnsubscribe)) .subscribe( (value) => { this.cobblerApiService