feat: add takeUntil to manage subscriptions lifecycle and prevent memory leaks

This commit is contained in:
Chneemann 2025-04-08 10:51:08 +02:00
parent 86692177a0
commit ea1e14b1a8
15 changed files with 373 additions and 218 deletions

View file

@ -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<void>();
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,14 +113,12 @@ export class AddTaskComponent implements OnInit {
* @returns {void}
*/
routeParams() {
if (this.route.params.subscribe()) {
this.route.params.subscribe((params) => {
this.route.params.pipe(takeUntil(this.destroy$)).subscribe((params) => {
if (params['id']) {
this.taskData.status = params['id'];
}
});
}
}
/**
* Loads any existing task data from local storage and assigns it to the taskData object.
@ -270,7 +278,10 @@ export class AddTaskComponent implements OnInit {
}
createTask(ngForm: NgForm) {
this.apiService.saveNewTask(this.taskData).subscribe({
this.apiService
.saveNewTask(this.taskData)
.pipe(takeUntil(this.destroy$))
.subscribe({
next: (response) => {
this.toastNotificationService.createTaskSuccessToast();
this.updateNotifierService.notifyUpdate('task');
@ -286,7 +297,10 @@ export class AddTaskComponent implements OnInit {
updateTask(ngForm: NgForm) {
// OverlayData = Current TaskId
this.apiService.updateTask(this.taskData, this.overlayData).subscribe({
this.apiService
.updateTask(this.taskData, this.overlayData)
.pipe(takeUntil(this.destroy$))
.subscribe({
next: (response) => {
this.toastNotificationService.updateTaskSuccessToast();
this.updateNotifierService.notifyUpdate('task');

View file

@ -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<string[]>();
@ -35,16 +37,24 @@ export class AssignedComponent {
dialogX: number = 0;
dialogY: number = 0;
private destroy$ = new Subject<void>();
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([]))
)

View file

@ -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<void>();
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<void>();
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,15 +119,21 @@ export class BoardComponent {
* - `itemMovedFrom`: Sets `taskMovedFrom` to the status.
*/
private subscribeToDragDropEvents(): void {
this.dragDropService.itemDropped.subscribe(({ task, status }) => {
this.dragDropService.itemDropped
.pipe(takeUntil(this.destroy$))
.subscribe(({ task, status }) => {
this.handleItemDropped(task, status);
});
this.dragDropService.itemMovedTo.subscribe(({ status }) => {
this.dragDropService.itemMovedTo
.pipe(takeUntil(this.destroy$))
.subscribe(({ status }) => {
this.taskMovedTo = status;
});
this.dragDropService.itemMovedFrom.subscribe(({ status }) => {
this.dragDropService.itemMovedFrom
.pipe(takeUntil(this.destroy$))
.subscribe(({ status }) => {
this.taskMovedFrom = status;
});
}
@ -142,7 +156,10 @@ export class BoardComponent {
handleItemDropped(task: Task, status: string): void {
if (!task || task.status === status) return;
this.apiService.updateTaskStatus(task.id!, status).subscribe({
this.apiService
.updateTaskStatus(task.id!, status)
.pipe(takeUntil(this.destroy$))
.subscribe({
next: () => {
this.toastNotificationService.taskStatusUpdatedToast();
this.updateTaskStatus(task.id!, status);
@ -201,9 +218,4 @@ export class BoardComponent {
])
);
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
}

View file

@ -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<boolean>();
@ -33,6 +35,8 @@ export class ContactDetailComponent {
showMobileNavbar = false;
showConfirmDialog = false;
private destroy$ = new Subject<void>();
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);
},
});
}
}

View file

@ -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<void>();
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<void>();
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<void> {
try {
await this.loadAllUsers();
@ -74,7 +79,10 @@ export class ContactsComponent {
}
private loadCurrentUser(): void {
this.userService.getCurrentUser().subscribe({
this.userService
.getCurrentUser()
.pipe(takeUntil(this.destroy$))
.subscribe({
next: (userData) => {
this.currentUser = userData;
},
@ -130,9 +138,4 @@ export class ContactsComponent {
this.showAllUsers = true;
}
}
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
}
}

View file

@ -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<void>();
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() {

View file

@ -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<void>();
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');
}

View file

@ -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<void>();
constructor(
public langService: LanguageService,
private userService: UserService
@ -35,8 +38,16 @@ export class MainLayoutComponent {
this.loadCurrentUser();
}
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
}
loadCurrentUser(): void {
this.userService.getCurrentUser().subscribe((userData) => {
this.userService
.getCurrentUser()
.pipe(takeUntil(this.destroy$))
.subscribe((userData) => {
this.currentUser = userData;
});
}

View file

@ -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<void>();
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,7 +66,10 @@ export class SummaryComponent {
}
loadCurrentUser(): void {
this.userService.getCurrentUser().subscribe((userData) => {
this.userService
.getCurrentUser()
.pipe(takeUntil(this.destroy$))
.subscribe((userData) => {
this.currentUser = userData;
});
}

View file

@ -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,25 +14,35 @@ 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<void>();
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.userService
.getCurrentUser()
.pipe(takeUntil(this.destroy$))
.subscribe((userData) => {
this.currentUser = userData;
});
}

View file

@ -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<boolean>();
@ -27,10 +27,6 @@ export class ContactOverlayComponent implements OnInit {
userInitials: string = '';
newColor: string = '';
constructor() {}
ngOnInit() {}
getRandomColor(): string {
const letters = '0123456789ABCDEF';
let color = '#';

View file

@ -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<void>();
constructor(private overlayService: OverlayService, private router: Router) {}
/**
@ -34,13 +37,20 @@ 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) => {
this.overlayService.overlayData$
.pipe(takeUntil(this.destroy$))
.subscribe((data) => {
if (data) {
this.overlayType = data.overlay;
this.overlayData = data.data;

View file

@ -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<boolean>();
overlayMobile: boolean = false;
private destroy$ = new Subject<void>();
constructor(
private apiService: ApiService,
private route: ActivatedRoute,
@ -33,17 +43,24 @@ 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) => {
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;
});
}
}
}
/**
* Gets the task data from the Firebase service for the given task id.

View file

@ -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<boolean>();
@ -36,6 +43,8 @@ export class TaskOverlayComponent implements OnInit {
currentUserId: string = '';
showConfirmDialog = false;
private destroy$ = new Subject<void>();
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,7 +94,10 @@ export class TaskOverlayComponent implements OnInit {
}
loadTask(taskId: string) {
this.taskService.getTaskById(taskId).subscribe({
this.taskService
.getTaskById(taskId)
.pipe(takeUntil(this.destroy$))
.subscribe({
next: (task) => {
this.task = task;
},
@ -117,7 +137,10 @@ export class TaskOverlayComponent implements OnInit {
}
deleteTask(taskId: string) {
this.apiService.deleteTaskById(taskId).subscribe({
this.apiService
.deleteTaskById(taskId)
.pipe(takeUntil(this.destroy$))
.subscribe({
next: (task) => {
this.toastNotificationService.deleteTaskSuccessToast();
this.updateNotifierService.notifyUpdate('task');
@ -140,7 +163,10 @@ export class TaskOverlayComponent implements OnInit {
subtask_title: subtaskTitle,
subtask_status: !currentStatus,
};
this.apiService.updateSubtaskStatus(taskId, body).subscribe(
this.apiService
.updateSubtaskStatus(taskId, body)
.pipe(takeUntil(this.destroy$))
.subscribe(
(response) => {
this.task?.subtasks.forEach((subtask) => {
if (subtask.id === subtaskId) {

View file

@ -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<void>();
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);
}