diff --git a/src/app/components/add-task/add-task.component.ts b/src/app/components/add-task/add-task.component.ts index 80f8009..303714e 100644 --- a/src/app/components/add-task/add-task.component.ts +++ b/src/app/components/add-task/add-task.component.ts @@ -1,5 +1,5 @@ import { CommonModule } from '@angular/common'; -import { Component, Input, OnInit } from '@angular/core'; +import { Component, Input, OnDestroy, OnInit } from '@angular/core'; import { FormsModule, NgForm } from '@angular/forms'; import { AssignedComponent } from './assigned/assigned.component'; import { Subtask, Task } from '../../interfaces/task.interface'; @@ -8,7 +8,7 @@ import { FormBtnComponent } from '../../shared/components/buttons/form-btn/form- import { ActivatedRoute, Router } from '@angular/router'; import { TranslateModule } from '@ngx-translate/core'; import { AuthService } from '../../services/auth.service'; -import { firstValueFrom, map } from 'rxjs'; +import { firstValueFrom, map, Subject, takeUntil } from 'rxjs'; import { TaskService } from '../../services/task.service'; import { ApiService } from '../../services/api.service'; import { UpdateNotifierService } from '../../services/update-notifier.service'; @@ -27,7 +27,7 @@ import { ToastNotificationService } from '../../services/toast-notification.serv templateUrl: './add-task.component.html', styleUrl: './add-task.component.scss', }) -export class AddTaskComponent implements OnInit { +export class AddTaskComponent implements OnInit, OnDestroy { @Input() overlayData: string = ''; @Input() overlayType: string = ''; @Input() overlayMobile: boolean = false; @@ -36,6 +36,8 @@ export class AddTaskComponent implements OnInit { dateInPast: boolean = false; subtaskValue: string = ''; + private destroy$ = new Subject(); + constructor( private overlayService: OverlayService, private taskService: TaskService, @@ -67,16 +69,24 @@ export class AddTaskComponent implements OnInit { * - Sets up route parameters to determine task status. * - Loads any existing task data from local storage. */ - ngOnInit() { + ngOnInit(): void { this.setCurrentUserId(); this.loadExistingTaskData(); this.routeParams(); } + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } + setCurrentUserId() { this.authService .getCurrentUserId() - .pipe(map((userId) => userId ?? '')) + .pipe( + takeUntil(this.destroy$), + map((userId) => userId ?? '') + ) .subscribe((userId) => { this.taskData.creator = userId; }); @@ -103,13 +113,11 @@ export class AddTaskComponent implements OnInit { * @returns {void} */ routeParams() { - if (this.route.params.subscribe()) { - this.route.params.subscribe((params) => { - if (params['id']) { - this.taskData.status = params['id']; - } - }); - } + this.route.params.pipe(takeUntil(this.destroy$)).subscribe((params) => { + if (params['id']) { + this.taskData.status = params['id']; + } + }); } /** @@ -270,34 +278,40 @@ export class AddTaskComponent implements OnInit { } createTask(ngForm: NgForm) { - this.apiService.saveNewTask(this.taskData).subscribe({ - next: (response) => { - this.toastNotificationService.createTaskSuccessToast(); - this.updateNotifierService.notifyUpdate('task'); - this.clearTaskData(ngForm); - this.closeOverlay(); - this.router.navigate(['/board']); - }, - error: (error) => { - console.error('Fehler beim Speichern:', error); - }, - }); + this.apiService + .saveNewTask(this.taskData) + .pipe(takeUntil(this.destroy$)) + .subscribe({ + next: (response) => { + this.toastNotificationService.createTaskSuccessToast(); + this.updateNotifierService.notifyUpdate('task'); + this.clearTaskData(ngForm); + this.closeOverlay(); + this.router.navigate(['/board']); + }, + error: (error) => { + console.error('Fehler beim Speichern:', error); + }, + }); } updateTask(ngForm: NgForm) { // OverlayData = Current TaskId - this.apiService.updateTask(this.taskData, this.overlayData).subscribe({ - next: (response) => { - this.toastNotificationService.updateTaskSuccessToast(); - this.updateNotifierService.notifyUpdate('task'); - this.clearTaskData(ngForm); - this.closeOverlay(); - this.router.navigate(['/board']); - }, - error: (error) => { - console.error('Fehler beim Speichern:', error); - }, - }); + this.apiService + .updateTask(this.taskData, this.overlayData) + .pipe(takeUntil(this.destroy$)) + .subscribe({ + next: (response) => { + this.toastNotificationService.updateTaskSuccessToast(); + this.updateNotifierService.notifyUpdate('task'); + this.clearTaskData(ngForm); + this.closeOverlay(); + this.router.navigate(['/board']); + }, + error: (error) => { + console.error('Fehler beim Speichern:', error); + }, + }); } /** diff --git a/src/app/components/add-task/assigned/assigned.component.ts b/src/app/components/add-task/assigned/assigned.component.ts index 568df68..41393d9 100644 --- a/src/app/components/add-task/assigned/assigned.component.ts +++ b/src/app/components/add-task/assigned/assigned.component.ts @@ -3,6 +3,8 @@ import { EventEmitter, HostListener, Input, + OnDestroy, + OnInit, Output, } from '@angular/core'; import { CommonModule } from '@angular/common'; @@ -11,7 +13,7 @@ import { TranslateModule } from '@ngx-translate/core'; import { FormsModule } from '@angular/forms'; import { User } from '../../../interfaces/user.interface'; import { ApiService } from '../../../services/api.service'; -import { map, catchError, of } from 'rxjs'; +import { map, catchError, of, Subject, takeUntil } from 'rxjs'; import { Assignee } from '../../../interfaces/task.interface'; @Component({ @@ -21,7 +23,7 @@ import { Assignee } from '../../../interfaces/task.interface'; templateUrl: './assigned.component.html', styleUrl: './assigned.component.scss', }) -export class AssignedComponent { +export class AssignedComponent implements OnInit, OnDestroy { @Input() taskCreator: string = ''; @Input() currentAssignees: Assignee[] = []; @Output() assignedChange = new EventEmitter(); @@ -35,16 +37,24 @@ export class AssignedComponent { dialogX: number = 0; dialogY: number = 0; + private destroy$ = new Subject(); + constructor(private apiService: ApiService) {} - ngOnInit() { + ngOnInit(): void { this.loadUsers(); } + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } + loadUsers(): void { this.apiService .getUsers() .pipe( + takeUntil(this.destroy$), map((users) => users.filter((user) => user.id !== this.taskCreator)), catchError(() => of([])) ) diff --git a/src/app/components/board/board.component.ts b/src/app/components/board/board.component.ts index 03ed68b..44f89bf 100644 --- a/src/app/components/board/board.component.ts +++ b/src/app/components/board/board.component.ts @@ -1,4 +1,4 @@ -import { Component } from '@angular/core'; +import { Component, OnDestroy, OnInit } from '@angular/core'; import { DragDropService } from '../../services/drag-drop.service'; import { CommonModule } from '@angular/common'; import { TaskComponent } from './task/task.component'; @@ -32,14 +32,23 @@ import { ResizeService } from '../../services/resize.service'; templateUrl: './board.component.html', styleUrl: './board.component.scss', }) -export class BoardComponent { - private destroy$ = new Subject(); - +export class BoardComponent implements OnInit, OnDestroy { readonly TODO = 'todo'; readonly IN_PROGRESS = 'inprogress'; readonly AWAIT_FEEDBACK = 'awaitfeedback'; readonly DONE = 'done'; + allTasks: Task[] = []; + filteredTasks: { [key: string]: Task[] } = {}; + + searchValue: string = ''; + searchInput: boolean = false; + taskMovedTo: string = ''; + taskMovedFrom: string = ''; + isLoading = false; + + private destroy$ = new Subject(); + constructor( public dragDropService: DragDropService, public overlayService: OverlayService, @@ -51,27 +60,26 @@ export class BoardComponent { private router: Router ) {} - allTasks: Task[] = []; - filteredTasks: { [key: string]: Task[] } = {}; - - searchValue: string = ''; - searchInput: boolean = false; - taskMovedTo: string = ''; - taskMovedFrom: string = ''; - isLoading = false; - - ngOnInit() { + ngOnInit(): void { this.loadAllTasks(); this.subscribeToTaskUpdates(); this.subscribeToDragDropEvents(); } + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } + loadAllTasks(): void { this.isLoading = true; this.taskService .getTasksWithUsers() - .pipe(finalize(() => (this.isLoading = false))) + .pipe( + takeUntil(this.destroy$), + finalize(() => (this.isLoading = false)) + ) .subscribe({ next: (response) => { this.allTasks = response.allTasks; @@ -111,17 +119,23 @@ export class BoardComponent { * - `itemMovedFrom`: Sets `taskMovedFrom` to the status. */ private subscribeToDragDropEvents(): void { - this.dragDropService.itemDropped.subscribe(({ task, status }) => { - this.handleItemDropped(task, status); - }); + this.dragDropService.itemDropped + .pipe(takeUntil(this.destroy$)) + .subscribe(({ task, status }) => { + this.handleItemDropped(task, status); + }); - this.dragDropService.itemMovedTo.subscribe(({ status }) => { - this.taskMovedTo = status; - }); + this.dragDropService.itemMovedTo + .pipe(takeUntil(this.destroy$)) + .subscribe(({ status }) => { + this.taskMovedTo = status; + }); - this.dragDropService.itemMovedFrom.subscribe(({ status }) => { - this.taskMovedFrom = status; - }); + this.dragDropService.itemMovedFrom + .pipe(takeUntil(this.destroy$)) + .subscribe(({ status }) => { + this.taskMovedFrom = status; + }); } /** @@ -142,13 +156,16 @@ export class BoardComponent { handleItemDropped(task: Task, status: string): void { if (!task || task.status === status) return; - this.apiService.updateTaskStatus(task.id!, status).subscribe({ - next: () => { - this.toastNotificationService.taskStatusUpdatedToast(); - this.updateTaskStatus(task.id!, status); - }, - error: (error) => console.error('Error updating task:', error), - }); + this.apiService + .updateTaskStatus(task.id!, status) + .pipe(takeUntil(this.destroy$)) + .subscribe({ + next: () => { + this.toastNotificationService.taskStatusUpdatedToast(); + this.updateTaskStatus(task.id!, status); + }, + error: (error) => console.error('Error updating task:', error), + }); } /** @@ -201,9 +218,4 @@ export class BoardComponent { ]) ); } - - ngOnDestroy() { - this.destroy$.next(); - this.destroy$.complete(); - } } diff --git a/src/app/components/contacts/contact-detail/contact-detail.component.ts b/src/app/components/contacts/contact-detail/contact-detail.component.ts index 674c167..7e6525a 100644 --- a/src/app/components/contacts/contact-detail/contact-detail.component.ts +++ b/src/app/components/contacts/contact-detail/contact-detail.component.ts @@ -2,13 +2,15 @@ import { Component, EventEmitter, Input, + OnChanges, + OnDestroy, Output, SimpleChanges, } from '@angular/core'; import { CommonModule } from '@angular/common'; import { TranslateModule } from '@ngx-translate/core'; import { UserService } from '../../../services/user.service'; -import { finalize, lastValueFrom } from 'rxjs'; +import { finalize, lastValueFrom, Subject, takeUntil } from 'rxjs'; import { User } from '../../../interfaces/user.interface'; import { OverlayService } from '../../../services/overlay.service'; import { ApiService } from '../../../services/api.service'; @@ -23,7 +25,7 @@ import { ConfirmDialogComponent } from '../../../shared/components/confirm-dialo templateUrl: './contact-detail.component.html', styleUrl: './contact-detail.component.scss', }) -export class ContactDetailComponent { +export class ContactDetailComponent implements OnChanges, OnDestroy { @Input() selectedUserId: string | null = null; @Input() currentUser: User | null = null; @Output() contactClosed = new EventEmitter(); @@ -33,6 +35,8 @@ export class ContactDetailComponent { showMobileNavbar = false; showConfirmDialog = false; + private destroy$ = new Subject(); + constructor( private overlayService: OverlayService, private userService: UserService, @@ -56,6 +60,38 @@ export class ContactDetailComponent { } } + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } + + /** + * Loads the user data with the given id and sets it to the `selectedUser` property. + */ + loadUser(): void { + this.isLoading = true; + + if (!this.selectedUserId) { + this.isLoading = false; + return; + } + + this.userService + .getUserById(this.selectedUserId) + .pipe( + takeUntil(this.destroy$), + finalize(() => (this.isLoading = false)) + ) + .subscribe({ + next: (response) => { + this.selectedUser = response; + }, + error: (err) => { + console.error('Error loading the tasks:', err); + }, + }); + } + /** * Emits an event with the value false to trigger the contact list to close. */ @@ -104,28 +140,4 @@ export class ContactDetailComponent { console.error(error); } } - - /** - * Loads the user data with the given id and sets it to the `selectedUser` property. - */ - loadUser(): void { - this.isLoading = true; - - if (!this.selectedUserId) { - this.isLoading = false; - return; - } - - this.userService - .getUserById(this.selectedUserId) - .pipe(finalize(() => (this.isLoading = false))) - .subscribe({ - next: (response) => { - this.selectedUser = response; - }, - error: (err) => { - console.error('Error loading the tasks:', err); - }, - }); - } } diff --git a/src/app/components/contacts/contacts.component.ts b/src/app/components/contacts/contacts.component.ts index 25528a3..dafcf49 100644 --- a/src/app/components/contacts/contacts.component.ts +++ b/src/app/components/contacts/contacts.component.ts @@ -1,4 +1,4 @@ -import { Component, HostListener } from '@angular/core'; +import { Component, HostListener, OnDestroy, OnInit } from '@angular/core'; import { User } from '../../interfaces/user.interface'; import { CommonModule } from '@angular/common'; import { ContactDetailComponent } from './contact-detail/contact-detail.component'; @@ -15,15 +15,15 @@ import { UpdateNotifierService } from '../../services/update-notifier.service'; templateUrl: './contacts.component.html', styleUrl: './contacts.component.scss', }) -export class ContactsComponent { - private destroy$ = new Subject(); - +export class ContactsComponent implements OnInit, OnDestroy { allUsers: User[] = []; currentUser: User | null = null; selectedUserId: string | null = null; showAllUsers: boolean = false; isLoading: boolean = false; + private destroy$ = new Subject(); + constructor( private userService: UserService, private overlayService: OverlayService, @@ -35,6 +35,11 @@ export class ContactsComponent { this.initializeData(); } + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } + private async initializeData(): Promise { try { await this.loadAllUsers(); @@ -74,14 +79,17 @@ export class ContactsComponent { } private loadCurrentUser(): void { - this.userService.getCurrentUser().subscribe({ - next: (userData) => { - this.currentUser = userData; - }, - error: (err) => { - console.error('Error loading current user:', err); - }, - }); + this.userService + .getCurrentUser() + .pipe(takeUntil(this.destroy$)) + .subscribe({ + next: (userData) => { + this.currentUser = userData; + }, + error: (err) => { + console.error('Error loading current user:', err); + }, + }); } /** @@ -130,9 +138,4 @@ export class ContactsComponent { this.showAllUsers = true; } } - - ngOnDestroy(): void { - this.destroy$.next(); - this.destroy$.complete(); - } } diff --git a/src/app/components/login/forgot-pw/pw-reset/pw-reset.component.ts b/src/app/components/login/forgot-pw/pw-reset/pw-reset.component.ts index 91812e1..cfa9186 100644 --- a/src/app/components/login/forgot-pw/pw-reset/pw-reset.component.ts +++ b/src/app/components/login/forgot-pw/pw-reset/pw-reset.component.ts @@ -1,12 +1,12 @@ import { CommonModule } from '@angular/common'; -import { Component } from '@angular/core'; +import { Component, OnDestroy, OnInit } from '@angular/core'; import { FormsModule, NgForm } from '@angular/forms'; import { FormBtnComponent } from '../../../../shared/components/buttons/form-btn/form-btn.component'; import { FooterComponent } from '../../footer/footer.component'; import { HeaderComponent } from '../../header/header.component'; import { TranslateModule } from '@ngx-translate/core'; import { ActivatedRoute } from '@angular/router'; -import { Subscription } from 'rxjs'; +import { Subject, Subscription, takeUntil } from 'rxjs'; import { ButtonStateService } from '../../../../services/button-state.service'; import { AuthService } from '../../../../services/auth.service'; import { PasswordVisibilityService } from '../../../../services/password-visibility.service'; @@ -25,7 +25,7 @@ import { PasswordVisibilityService } from '../../../../services/password-visibil templateUrl: './pw-reset.component.html', styleUrl: './pw-reset.component.scss', }) -export class PwResetComponent { +export class PwResetComponent implements OnInit, OnDestroy { private routeSubscription: Subscription = new Subscription(); uid: string = ''; @@ -37,6 +37,8 @@ export class PwResetComponent { passwordConfirm: '', }; + private destroy$ = new Subject(); + constructor( public pwVisibility: PasswordVisibilityService, private authService: AuthService, @@ -45,16 +47,19 @@ export class PwResetComponent { ) {} ngOnInit(): void { - this.routeSubscription = this.route.params.subscribe((params) => { - this.uid = params['uid']; - this.token = params['token']; - }); + this.routeParams(); } ngOnDestroy(): void { - if (this.routeSubscription) { - this.routeSubscription.unsubscribe(); - } + this.destroy$.next(); + this.destroy$.complete(); + } + + routeParams() { + this.route.params.pipe(takeUntil(this.destroy$)).subscribe((params) => { + this.uid = params['uid']; + this.token = params['token']; + }); } isButtonDisabled() { diff --git a/src/app/components/login/login.component.ts b/src/app/components/login/login.component.ts index 6691318..15e148a 100644 --- a/src/app/components/login/login.component.ts +++ b/src/app/components/login/login.component.ts @@ -1,5 +1,5 @@ import { CommonModule } from '@angular/common'; -import { Component } from '@angular/core'; +import { Component, OnDestroy, OnInit } from '@angular/core'; import { FormsModule, NgForm } from '@angular/forms'; import { FormBtnComponent } from '../../shared/components/buttons/form-btn/form-btn.component'; import { ActivatedRoute, Router, RouterLink } from '@angular/router'; @@ -12,6 +12,7 @@ import { AuthService } from '../../services/auth.service'; import { TokenService } from '../../services/token.service'; import { ButtonStateService } from '../../services/button-state.service'; import { PasswordVisibilityService } from '../../services/password-visibility.service'; +import { Subject, takeUntil } from 'rxjs'; @Component({ selector: 'app-login', @@ -29,7 +30,7 @@ import { PasswordVisibilityService } from '../../services/password-visibility.se templateUrl: './login.component.html', styleUrl: './login.component.scss', }) -export class LoginComponent { +export class LoginComponent implements OnInit, OnDestroy { isPasswordIconVisible: boolean = true; checkboxRememberMe: boolean = false; @@ -38,6 +39,8 @@ export class LoginComponent { password: '', }; + private destroy$ = new Subject(); + constructor( public pwVisibility: PasswordVisibilityService, private authService: AuthService, @@ -47,13 +50,18 @@ export class LoginComponent { private route: ActivatedRoute ) {} - ngOnInit() { + ngOnInit(): void { this.routeParams(); this.deleteTokens(); } + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } + routeParams() { - this.route.params.subscribe((params) => { + this.route.params.pipe(takeUntil(this.destroy$)).subscribe((params) => { if (params['id'] && params['id'] === 'pw-send') { this.overlayService.setOverlayData('dialog', 'pw-send'); } diff --git a/src/app/components/main-layout/main-layout.component.ts b/src/app/components/main-layout/main-layout.component.ts index 8ff8ce5..80ce59a 100644 --- a/src/app/components/main-layout/main-layout.component.ts +++ b/src/app/components/main-layout/main-layout.component.ts @@ -1,4 +1,4 @@ -import { Component } from '@angular/core'; +import { Component, OnDestroy, OnInit } from '@angular/core'; import { RouterOutlet } from '@angular/router'; import { CommonModule } from '@angular/common'; import { LanguageService } from '../../services/language.service'; @@ -8,6 +8,7 @@ import { HeaderComponent } from '../../shared/components/header/header.component import { UserService } from '../../services/user.service'; import { User } from '../../interfaces/user.interface'; import { OverlayComponent } from '../../shared/components/overlay/overlay.component'; +import { Subject, takeUntil } from 'rxjs'; @Component({ selector: 'app-main-layout', @@ -23,9 +24,11 @@ import { OverlayComponent } from '../../shared/components/overlay/overlay.compon templateUrl: './main-layout.component.html', styleUrl: './main-layout.component.scss', }) -export class MainLayoutComponent { +export class MainLayoutComponent implements OnInit, OnDestroy { currentUser: User | null = null; + private destroy$ = new Subject(); + constructor( public langService: LanguageService, private userService: UserService @@ -35,9 +38,17 @@ export class MainLayoutComponent { this.loadCurrentUser(); } + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } + loadCurrentUser(): void { - this.userService.getCurrentUser().subscribe((userData) => { - this.currentUser = userData; - }); + this.userService + .getCurrentUser() + .pipe(takeUntil(this.destroy$)) + .subscribe((userData) => { + this.currentUser = userData; + }); } } diff --git a/src/app/components/summary/summary.component.ts b/src/app/components/summary/summary.component.ts index 4fdc861..f2690cd 100644 --- a/src/app/components/summary/summary.component.ts +++ b/src/app/components/summary/summary.component.ts @@ -1,10 +1,10 @@ -import { Component } from '@angular/core'; +import { Component, OnDestroy, OnInit } from '@angular/core'; import { RouterModule } from '@angular/router'; import { TranslateModule, TranslateService } from '@ngx-translate/core'; import { Task } from '../../interfaces/task.interface'; import { TaskService } from '../../services/task.service'; import { LoadingSpinnerComponent } from '../../shared/components/loading-spinner/loading-spinner.component'; -import { finalize } from 'rxjs'; +import { finalize, Subject, takeUntil } from 'rxjs'; import { UserService } from '../../services/user.service'; import { User } from '../../interfaces/user.interface'; @@ -16,13 +16,14 @@ import { User } from '../../interfaces/user.interface'; templateUrl: './summary.component.html', styleUrl: './summary.component.scss', }) -export class SummaryComponent { - nextUrgendTask: number[] = []; - +export class SummaryComponent implements OnInit, OnDestroy { allTasks: Task[] = []; + nextUrgendTask: number[] = []; currentUser: User | null = null; isLoading = false; + private destroy$ = new Subject(); + constructor( private translateService: TranslateService, private userService: UserService, @@ -32,11 +33,16 @@ export class SummaryComponent { /** * This method loads all tasks from the TaskService. */ - ngOnInit() { + ngOnInit(): void { this.loadAllTasks(); this.loadCurrentUser(); } + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } + /** * Loads all tasks from the TaskService. */ @@ -45,7 +51,10 @@ export class SummaryComponent { this.taskService .getTasks() - .pipe(finalize(() => (this.isLoading = false))) + .pipe( + takeUntil(this.destroy$), + finalize(() => (this.isLoading = false)) + ) .subscribe({ next: (response) => { this.allTasks = response; @@ -57,9 +66,12 @@ export class SummaryComponent { } loadCurrentUser(): void { - this.userService.getCurrentUser().subscribe((userData) => { - this.currentUser = userData; - }); + this.userService + .getCurrentUser() + .pipe(takeUntil(this.destroy$)) + .subscribe((userData) => { + this.currentUser = userData; + }); } /** diff --git a/src/app/shared/components/header/header.component.ts b/src/app/shared/components/header/header.component.ts index 57970a2..3abd3c5 100644 --- a/src/app/shared/components/header/header.component.ts +++ b/src/app/shared/components/header/header.component.ts @@ -1,10 +1,11 @@ -import { Component, HostListener } from '@angular/core'; +import { Component, HostListener, OnDestroy, OnInit } from '@angular/core'; import { RouterModule } from '@angular/router'; import { NavbarComponent } from './navbar/navbar.component'; import { CommonModule } from '@angular/common'; import { TranslateModule } from '@ngx-translate/core'; import { UserService } from '../../../services/user.service'; import { User } from '../../../interfaces/user.interface'; +import { Subject, takeUntil } from 'rxjs'; @Component({ selector: 'app-header', @@ -13,27 +14,37 @@ import { User } from '../../../interfaces/user.interface'; templateUrl: './header.component.html', styleUrl: './header.component.scss', }) -export class HeaderComponent { +export class HeaderComponent implements OnInit, OnDestroy { showNavbar: boolean = false; showLanguageNavbar: boolean = false; currentUser: User | null = null; + private destroy$ = new Subject(); + constructor(private userService: UserService) {} /** * Loads the current user data by calling the loadCurrentUser method. */ - ngOnInit() { + ngOnInit(): void { this.loadCurrentUser(); } + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } + /** * Loads the current user data and sets it to the `currentUser` property. */ loadCurrentUser(): void { - this.userService.getCurrentUser().subscribe((userData) => { - this.currentUser = userData; - }); + this.userService + .getCurrentUser() + .pipe(takeUntil(this.destroy$)) + .subscribe((userData) => { + this.currentUser = userData; + }); } /** diff --git a/src/app/shared/components/overlay/contact-overlay/contact-overlay.component.ts b/src/app/shared/components/overlay/contact-overlay/contact-overlay.component.ts index 1cd9ad7..90b5896 100644 --- a/src/app/shared/components/overlay/contact-overlay/contact-overlay.component.ts +++ b/src/app/shared/components/overlay/contact-overlay/contact-overlay.component.ts @@ -1,4 +1,4 @@ -import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; +import { Component, EventEmitter, Input, Output } from '@angular/core'; import { CommonModule } from '@angular/common'; import { ContactFormComponent } from './contact-form/contact-form.component'; import { BtnCloseComponent } from '../../buttons/btn-close/btn-close.component'; @@ -18,7 +18,7 @@ import { ColorPickerModule } from 'ngx-color-picker'; templateUrl: './contact-overlay.component.html', styleUrl: './contact-overlay.component.scss', }) -export class ContactOverlayComponent implements OnInit { +export class ContactOverlayComponent { @Input() overlayData: any = []; @Input() overlayType: string = ''; @Output() closeDialogEmitter = new EventEmitter(); @@ -27,10 +27,6 @@ export class ContactOverlayComponent implements OnInit { userInitials: string = ''; newColor: string = ''; - constructor() {} - - ngOnInit() {} - getRandomColor(): string { const letters = '0123456789ABCDEF'; let color = '#'; diff --git a/src/app/shared/components/overlay/overlay.component.ts b/src/app/shared/components/overlay/overlay.component.ts index b6e8ec1..645cedb 100644 --- a/src/app/shared/components/overlay/overlay.component.ts +++ b/src/app/shared/components/overlay/overlay.component.ts @@ -1,4 +1,4 @@ -import { Component, HostListener, OnInit } from '@angular/core'; +import { Component, HostListener, OnDestroy, OnInit } from '@angular/core'; import { OverlayService } from '../../../services/overlay.service'; import { CommonModule } from '@angular/common'; import { TaskOverlayComponent } from './task-overlay/task-overlay.component'; @@ -6,6 +6,7 @@ import { TaskEditOverlayComponent } from './task-edit-overlay/task-edit-overlay. import { Router } from '@angular/router'; import { DialogOverlayComponent } from './dialog-overlay/dialog-overlay.component'; import { ContactOverlayComponent } from './contact-overlay/contact-overlay.component'; +import { Subject, takeUntil } from 'rxjs'; @Component({ selector: 'app-overlay', @@ -20,10 +21,12 @@ import { ContactOverlayComponent } from './contact-overlay/contact-overlay.compo templateUrl: './overlay.component.html', styleUrl: './overlay.component.scss', }) -export class OverlayComponent implements OnInit { +export class OverlayComponent implements OnInit, OnDestroy { overlayType: any; overlayData: any; + private destroy$ = new Subject(); + constructor(private overlayService: OverlayService, private router: Router) {} /** @@ -34,18 +37,25 @@ export class OverlayComponent implements OnInit { this.checkOverlayData(); } + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } + /** * Subscribes to the overlay data observable from the OverlayService. * Updates the component's `overlayType` and `overlayData` properties * whenever new overlay data is emitted. */ checkOverlayData() { - this.overlayService.overlayData$.subscribe((data) => { - if (data) { - this.overlayType = data.overlay; - this.overlayData = data.data; - } - }); + this.overlayService.overlayData$ + .pipe(takeUntil(this.destroy$)) + .subscribe((data) => { + if (data) { + this.overlayType = data.overlay; + this.overlayData = data.data; + } + }); } /** diff --git a/src/app/shared/components/overlay/task-edit-overlay/task-edit-overlay.component.ts b/src/app/shared/components/overlay/task-edit-overlay/task-edit-overlay.component.ts index 3269d36..ebac987 100644 --- a/src/app/shared/components/overlay/task-edit-overlay/task-edit-overlay.component.ts +++ b/src/app/shared/components/overlay/task-edit-overlay/task-edit-overlay.component.ts @@ -1,10 +1,18 @@ -import { Component, EventEmitter, Input, Output } from '@angular/core'; +import { + Component, + EventEmitter, + Input, + OnDestroy, + OnInit, + Output, +} from '@angular/core'; import { BtnCloseComponent } from '../../buttons/btn-close/btn-close.component'; import { FormsModule } from '@angular/forms'; import { CommonModule } from '@angular/common'; import { AddTaskComponent } from '../../../../components/add-task/add-task.component'; import { ActivatedRoute, Router } from '@angular/router'; import { ApiService } from '../../../../services/api.service'; +import { Subject, takeUntil } from 'rxjs'; @Component({ selector: 'app-task-edit-overlay', @@ -13,13 +21,15 @@ import { ApiService } from '../../../../services/api.service'; templateUrl: './task-edit-overlay.component.html', styleUrl: './task-edit-overlay.component.scss', }) -export class TaskEditOverlayComponent { +export class TaskEditOverlayComponent implements OnInit, OnDestroy { @Input() overlayData: string = ''; @Input() overlayType: string = ''; @Output() closeDialogEmitter = new EventEmitter(); overlayMobile: boolean = false; + private destroy$ = new Subject(); + constructor( private apiService: ApiService, private route: ActivatedRoute, @@ -33,15 +43,22 @@ export class TaskEditOverlayComponent { * and sets the overlay data and type from the route parameters. It also sets * the overlay mobile flag to true. */ - ngOnInit() { - if (this.overlayData == '') { - if (this.route.params.subscribe()) { - this.route.params.subscribe((params) => { - this.overlayData = params['id']; - this.overlayType = this.overlayType; - this.overlayMobile = true; - }); - } + ngOnInit(): void { + this.loadOverlayData(); + } + + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } + + private loadOverlayData(): void { + if (this.overlayData === '') { + this.route.params.pipe(takeUntil(this.destroy$)).subscribe((params) => { + this.overlayData = params['id']; + this.overlayType = this.overlayType; + this.overlayMobile = true; + }); } } diff --git a/src/app/shared/components/overlay/task-overlay/task-overlay.component.ts b/src/app/shared/components/overlay/task-overlay/task-overlay.component.ts index df3c8fb..46fa398 100644 --- a/src/app/shared/components/overlay/task-overlay/task-overlay.component.ts +++ b/src/app/shared/components/overlay/task-overlay/task-overlay.component.ts @@ -1,4 +1,11 @@ -import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; +import { + Component, + EventEmitter, + Input, + OnDestroy, + OnInit, + Output, +} from '@angular/core'; import { BtnCloseComponent } from '../../buttons/btn-close/btn-close.component'; import { CommonModule } from '@angular/common'; import { OverlayService } from '../../../../services/overlay.service'; @@ -8,7 +15,7 @@ import { TranslateModule } from '@ngx-translate/core'; import { Task } from '../../../../interfaces/task.interface'; import { TaskService } from '../../../../services/task.service'; import { AuthService } from '../../../../services/auth.service'; -import { map } from 'rxjs'; +import { map, Subject, takeUntil } from 'rxjs'; import { ApiService } from '../../../../services/api.service'; import { UpdateNotifierService } from '../../../../services/update-notifier.service'; import { ToastNotificationService } from '../../../../services/toast-notification.service'; @@ -27,7 +34,7 @@ import { ConfirmDialogComponent } from '../../confirm-dialog/confirm-dialog.comp templateUrl: './task-overlay.component.html', styleUrl: './task-overlay.component.scss', }) -export class TaskOverlayComponent implements OnInit { +export class TaskOverlayComponent implements OnInit, OnDestroy { @Input() overlayData: string = ''; @Output() closeDialogEmitter = new EventEmitter(); @@ -36,6 +43,8 @@ export class TaskOverlayComponent implements OnInit { currentUserId: string = ''; showConfirmDialog = false; + private destroy$ = new Subject(); + constructor( private overlayService: OverlayService, private taskService: TaskService, @@ -52,16 +61,24 @@ export class TaskOverlayComponent implements OnInit { ['Technical Task', '#20d7c2'], ]); - ngOnInit() { + ngOnInit(): void { this.setCurrentUserId(); this.setOverlayDataFromRoute(); this.loadTask(this.overlayData); } + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } + setCurrentUserId() { this.authService .getCurrentUserId() - .pipe(map((userId) => userId ?? '')) + .pipe( + takeUntil(this.destroy$), + map((userId) => userId ?? '') + ) .subscribe((userId) => { this.currentUserId = userId; }); @@ -69,7 +86,7 @@ export class TaskOverlayComponent implements OnInit { setOverlayDataFromRoute() { if (this.overlayData === '') { - this.route.params.subscribe((params) => { + this.route.params.pipe(takeUntil(this.destroy$)).subscribe((params) => { this.overlayData = params['id']; this.overlayMobile = true; }); @@ -77,14 +94,17 @@ export class TaskOverlayComponent implements OnInit { } loadTask(taskId: string) { - this.taskService.getTaskById(taskId).subscribe({ - next: (task) => { - this.task = task; - }, - error: (err) => { - console.error('Error loading the task:', err); - }, - }); + this.taskService + .getTaskById(taskId) + .pipe(takeUntil(this.destroy$)) + .subscribe({ + next: (task) => { + this.task = task; + }, + error: (err) => { + console.error('Error loading the task:', err); + }, + }); } /** @@ -117,15 +137,18 @@ export class TaskOverlayComponent implements OnInit { } deleteTask(taskId: string) { - this.apiService.deleteTaskById(taskId).subscribe({ - next: (task) => { - this.toastNotificationService.deleteTaskSuccessToast(); - this.updateNotifierService.notifyUpdate('task'); - }, - error: (err) => { - console.error('Error deleting task', err); - }, - }); + this.apiService + .deleteTaskById(taskId) + .pipe(takeUntil(this.destroy$)) + .subscribe({ + next: (task) => { + this.toastNotificationService.deleteTaskSuccessToast(); + this.updateNotifierService.notifyUpdate('task'); + }, + error: (err) => { + console.error('Error deleting task', err); + }, + }); this.closeDialog(); } @@ -140,20 +163,23 @@ export class TaskOverlayComponent implements OnInit { subtask_title: subtaskTitle, subtask_status: !currentStatus, }; - this.apiService.updateSubtaskStatus(taskId, body).subscribe( - (response) => { - this.task?.subtasks.forEach((subtask) => { - if (subtask.id === subtaskId) { - subtask.status = !currentStatus; - } - }); - this.toastNotificationService.updateSubtaskSuccessToast(); - this.updateNotifierService.notifyUpdate('task'); - }, - (error) => { - console.error('Error updating subtask:', error); - } - ); + this.apiService + .updateSubtaskStatus(taskId, body) + .pipe(takeUntil(this.destroy$)) + .subscribe( + (response) => { + this.task?.subtasks.forEach((subtask) => { + if (subtask.id === subtaskId) { + subtask.status = !currentStatus; + } + }); + this.toastNotificationService.updateSubtaskSuccessToast(); + this.updateNotifierService.notifyUpdate('task'); + }, + (error) => { + console.error('Error updating subtask:', error); + } + ); } /** diff --git a/src/app/shared/components/sidebar/sidebar.component.ts b/src/app/shared/components/sidebar/sidebar.component.ts index be8a301..e403bf2 100644 --- a/src/app/shared/components/sidebar/sidebar.component.ts +++ b/src/app/shared/components/sidebar/sidebar.component.ts @@ -1,9 +1,10 @@ import { CommonModule } from '@angular/common'; -import { Component, OnInit } from '@angular/core'; +import { Component, OnDestroy, OnInit } from '@angular/core'; import { NavigationEnd, Router, RouterModule } from '@angular/router'; import { TranslateModule } from '@ngx-translate/core'; import { LanguageService } from '../../../services/language.service'; import { AuthService } from '../../../services/auth.service'; +import { Subject, takeUntil } from 'rxjs'; @Component({ selector: 'app-sidebar', @@ -12,9 +13,11 @@ import { AuthService } from '../../../services/auth.service'; templateUrl: './sidebar.component.html', styleUrl: './sidebar.component.scss', }) -export class SidebarComponent implements OnInit { +export class SidebarComponent implements OnInit, OnDestroy { currentPath: string = ''; + private destroy$ = new Subject(); + constructor( private router: Router, public languageService: LanguageService, @@ -28,13 +31,18 @@ export class SidebarComponent implements OnInit { this.getCurrentPath(); } + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } + /** * Subscribes to router events and updates the currentPath property * with the current route's URL, excluding the leading slash, whenever * a navigation ends. */ getCurrentPath() { - this.router.events.subscribe((event) => { + this.router.events.pipe(takeUntil(this.destroy$)).subscribe((event) => { if (event instanceof NavigationEnd) { this.currentPath = this.router.url.substring(1); }