Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add unsubscribe handling in existing components #14

Merged
merged 4 commits into from
Feb 17, 2025
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 33 additions & 16 deletions src/app/modules/organizations/cycles/cycles.component.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { CommonModule } from '@angular/common'
import type { OnInit } from '@angular/core'
import type { OnDestroy, OnInit } from '@angular/core'
import { Component, inject } from '@angular/core'
import { MatButtonModule } from '@angular/material/button'
import { MatDialog, MatDialogModule } from '@angular/material/dialog'
import { MatIconModule } from '@angular/material/icon'
import { MatTableDataSource, MatTableModule } from '@angular/material/table'
import { Subject, takeUntil, tap } from 'rxjs'
import type { Cycle } from '@seed/api/cycle'
import { CycleService } from '@seed/api/cycle/cycle.service'
import { PageComponent, TableContainerComponent } from '@seed/components'
Expand All @@ -26,11 +27,12 @@ import { FormModalComponent } from './modal/form-modal.component'
TableContainerComponent,
],
})
export class CyclesComponent implements OnInit {
export class CyclesComponent implements OnDestroy, OnInit {
private _cycleService = inject(CycleService)
private _dialog = inject(MatDialog)
private _orgId: number
private _existingNames: string[]
private readonly _unsubscribeAll$ = new Subject<void>()

cyclesDataSource = new MatTableDataSource<Cycle>([])
cyclesColumns = ['id', 'name', 'start', 'end', 'actions']
Expand All @@ -42,11 +44,15 @@ export class CyclesComponent implements OnInit {
refreshCycles(): void {
this._cycleService.get()

this._cycleService.cycles$.subscribe((cycles) => {
this.cyclesDataSource.data = cycles
this._orgId = cycles[0]?.organization
this._existingNames = cycles.map((cycle) => cycle.name)
})
this._cycleService.cycles$
.pipe(
takeUntil(this._unsubscribeAll$),
tap((cycles) => {
this.cyclesDataSource.data = cycles
this._orgId = cycles[0]?.organization
this._existingNames = cycles.map((cycle) => cycle.name)
}),
).subscribe()
}

createCycle = () => {
Expand All @@ -55,9 +61,11 @@ export class CyclesComponent implements OnInit {
data: { cycle: null, orgId: this._orgId, existingNames: this._existingNames },
})

dialogRef.afterClosed().subscribe(() => {
this.refreshCycles()
})
dialogRef.afterClosed()
.pipe(
takeUntil(this._unsubscribeAll$),
tap(() => { this.refreshCycles() }),
).subscribe()
}

editCycle(cycle: Cycle): void {
Expand All @@ -66,9 +74,11 @@ export class CyclesComponent implements OnInit {
data: { cycle, orgId: this._orgId, existingNames: this._existingNames },
})

dialogRef.afterClosed().subscribe(() => {
this.refreshCycles()
})
dialogRef.afterClosed()
.pipe(
takeUntil(this._unsubscribeAll$),
tap(() => { this.refreshCycles() }),
).subscribe()
}

deleteCycle(cycle: Cycle): void {
Expand All @@ -77,12 +87,19 @@ export class CyclesComponent implements OnInit {
data: { cycle, orgId: this._orgId },
})

dialogRef.afterClosed().subscribe(() => {
this.refreshCycles()
})
dialogRef.afterClosed()
.pipe(
takeUntil(this._unsubscribeAll$),
tap(() => { this.refreshCycles() }),
).subscribe()
}

trackByFn(_index: number, { id }: Cycle) {
return id
}

ngOnDestroy(): void {
this._unsubscribeAll$.next()
this._unsubscribeAll$.complete()
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { CommonModule } from '@angular/common'
import type { HttpErrorResponse } from '@angular/common/http'
import type { OnDestroy } from '@angular/core'
import { Component, inject } from '@angular/core'
import { MatButtonModule } from '@angular/material/button'
import { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from '@angular/material/dialog'
import { MatProgressBarModule } from '@angular/material/progress-bar'
import { catchError, throwError } from 'rxjs'
import { Subject, switchMap, takeUntil, tap } from 'rxjs'
import type { Cycle } from '@seed/api/cycle'
import { CycleService } from '@seed/api/cycle/cycle.service'
import { AlertComponent } from '@seed/components'
Expand All @@ -23,11 +23,12 @@ import { SnackbarService } from 'app/core/snackbar/snackbar.service'
MatProgressBarModule,
],
})
export class DeleteModalComponent {
export class DeleteModalComponent implements OnDestroy {
private _cycleService = inject(CycleService)
private _uploaderService = inject(UploaderService)
private _dialogRef = inject(MatDialogRef<DeleteModalComponent>)
private _snackBar = inject(SnackbarService)
private readonly _unsubscribeAll$ = new Subject<void>()
errorMessage: string
inProgress = false
progressBarObj: ProgressBarObj = {
Expand Down Expand Up @@ -55,31 +56,24 @@ export class DeleteModalComponent {
}

// initiate delete cycle task
this._cycleService.delete(this.data.cycle.id, this.data.orgId).subscribe({
next: (response: { progress_key: string; value: number }) => {
this.progressBarObj.progress = response.value
// monitor delete cycle task
this._uploaderService
.checkProgressLoop({
progressKey: response.progress_key,
this._cycleService.delete(this.data.cycle.id, this.data.orgId)
.pipe(
takeUntil(this._unsubscribeAll$),
tap((response: { progress_key: string; value: number }) => {
this.progressBarObj.progress = response.value
}),
switchMap(({ progress_key }) => {
return this._uploaderService.checkProgressLoop({
progressKey: progress_key,
offset: 0,
multiplier: 1,
successFn,
failureFn,
progressBarObj: this.progressBarObj,
})
.pipe(
catchError(({ error }: { error: HttpErrorResponse }) => {
return throwError(() => new Error(error?.message || 'Error checking progress'))
}),
)
.subscribe()
},
error: (error: string) => {
this.inProgress = false
this.errorMessage = error
},
})
}),
)
.subscribe()
}

close() {
Expand All @@ -89,4 +83,9 @@ export class DeleteModalComponent {
dismiss() {
this._dialogRef.close()
}

ngOnDestroy(): void {
this._unsubscribeAll$.next()
this._unsubscribeAll$.complete()
}
}
19 changes: 14 additions & 5 deletions src/app/modules/organizations/cycles/modal/form-modal.component.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { CommonModule, DatePipe } from '@angular/common'
import type { OnInit } from '@angular/core'
import type { OnDestroy, OnInit } from '@angular/core'
import { Component, inject } from '@angular/core'
import { FormControl, FormGroup, FormsModule, ReactiveFormsModule, Validators } from '@angular/forms'
import { MatButtonModule } from '@angular/material/button'
Expand All @@ -8,6 +8,7 @@ import { MatDatepickerModule } from '@angular/material/datepicker'
import { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from '@angular/material/dialog'
import { MatFormFieldModule } from '@angular/material/form-field'
import { MatInputModule } from '@angular/material/input'
import { Subject, takeUntil, tap } from 'rxjs'
import type { Cycle } from '@seed/api/cycle'
import { CycleService } from '@seed/api/cycle/cycle.service'
import { SEEDValidators } from '@seed/validators'
Expand Down Expand Up @@ -41,10 +42,11 @@ export const MY_DATE_FORMATS = {
MatNativeDateModule,
],
})
export class FormModalComponent implements OnInit {
export class FormModalComponent implements OnDestroy, OnInit {
private _cycleService = inject(CycleService)
private _datePipe = inject(DatePipe)
private _dialogRef = inject(MatDialogRef<FormModalComponent>)
private readonly _unsubscribeAll$ = new Subject<void>()

create = true
data = inject(MAT_DIALOG_DATA) as { cycle: Cycle | null; orgId: number; existingNames: string[] }
Expand All @@ -62,9 +64,11 @@ export class FormModalComponent implements OnInit {
this.create = false
this.form.patchValue(this.data.cycle)
}
this.form.get('start')?.valueChanges.subscribe(() => {
this.form.get('end')?.updateValueAndValidity()
})
this.form.get('start')?.valueChanges
.pipe(
takeUntil(this._unsubscribeAll$),
tap(() => { this.form.get('end')?.updateValueAndValidity() }),
).subscribe()
}

onSubmit() {
Expand All @@ -86,6 +90,11 @@ export class FormModalComponent implements OnInit {
this._dialogRef.close()
}

ngOnDestroy(): void {
this._unsubscribeAll$.next()
this._unsubscribeAll$.complete()
}

private _formatDates() {
this.form.value.start = this._datePipe.transform(this.form.value.start, 'yyyy-MM-dd')
this.form.value.end = this._datePipe.transform(this.form.value.end, 'yyyy-MM-dd')
Expand Down
47 changes: 32 additions & 15 deletions src/app/modules/organizations/members/members.component.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import type { OnInit } from '@angular/core'
import type { OnDestroy, OnInit } from '@angular/core'
import { Component, inject } from '@angular/core'
import { MatButtonModule } from '@angular/material/button'
import { MatDialog, MatDialogModule } from '@angular/material/dialog'
import { MatIconModule } from '@angular/material/icon'
import { MatTableDataSource, MatTableModule } from '@angular/material/table'
import { Subject, takeUntil, tap } from 'rxjs'
import { OrganizationService, type OrganizationUser } from '@seed/api/organization'
import { PageComponent, TableContainerComponent } from '@seed/components'
import { SharedImports } from '@seed/directives'
Expand All @@ -23,29 +24,36 @@ import { FormModalComponent } from './modal/form-modal.component'
TableContainerComponent,
],
})
export class MembersComponent implements OnInit {
export class MembersComponent implements OnDestroy, OnInit {
private _organizationService = inject(OrganizationService)
private _dialog = inject(MatDialog)
private _orgId: number
private readonly _unsubscribeAll$ = new Subject<void>()

membersDataSource = new MatTableDataSource<OrganizationUser>([])
membersColumns = ['name', 'email', 'access level', 'access level instance', 'role', 'actions']

ngOnInit(): void {
this._organizationService.currentOrganization$.subscribe(({ org_id }) => {
this._orgId = org_id
this.getMembers(this._orgId)
})
this._organizationService.currentOrganization$
.pipe(
takeUntil(this._unsubscribeAll$),
tap(({ org_id }) => {
this._orgId = org_id
this.getMembers(this._orgId)
}),
).subscribe()
}

getMembers(orgId: number): void {
// fetch org users
this._organizationService.getOrganizationUsers(orgId)

// subscribe to org users stream and set members
this._organizationService.organizationUsers$.subscribe((orgUsers) => {
this.membersDataSource.data = orgUsers
})
this._organizationService.organizationUsers$
.pipe(
takeUntil(this._unsubscribeAll$),
tap((orgUsers) => { this.membersDataSource.data = orgUsers }),
).subscribe()
}

editMember(member: OrganizationUser): void {
Expand All @@ -54,9 +62,11 @@ export class MembersComponent implements OnInit {
data: { member, orgId: this._orgId },
})

dialogRef.afterClosed().subscribe(() => {
this.getMembers(this._orgId)
})
dialogRef.afterClosed()
.pipe(
takeUntil(this._unsubscribeAll$),
tap(() => { this.getMembers(this._orgId) }),
).subscribe()
}

deleteMember(member: OrganizationUser): void {
Expand All @@ -65,12 +75,19 @@ export class MembersComponent implements OnInit {
data: { member, orgId: this._orgId },
})

dialogRef.afterClosed().subscribe(() => {
this.getMembers(this._orgId)
})
dialogRef.afterClosed()
.pipe(
takeUntil(this._unsubscribeAll$),
tap(() => { this.getMembers(this._orgId) }),
).subscribe()
}

trackByFn(_index: number, { email }: OrganizationUser) {
return email
}

ngOnDestroy(): void {
this._unsubscribeAll$.next()
this._unsubscribeAll$.complete()
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { CommonModule } from '@angular/common'
import type { OnDestroy } from '@angular/core'
import { Component, inject } from '@angular/core'
import { MatButtonModule } from '@angular/material/button'
import { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from '@angular/material/dialog'
import { Subject, takeUntil, tap } from 'rxjs'
import { OrganizationService, type OrganizationUser } from '@seed/api/organization'
import { AlertComponent } from '@seed/components'

Expand All @@ -15,18 +17,20 @@ import { AlertComponent } from '@seed/components'
MatDialogModule,
],
})
export class DeleteModalComponent {
export class DeleteModalComponent implements OnDestroy {
private _organizationService = inject(OrganizationService)
private _dialogRef = inject(MatDialogRef<DeleteModalComponent>)
private readonly _unsubscribeAll$ = new Subject<void>()
errorMessage: string

data = inject(MAT_DIALOG_DATA) as { member: OrganizationUser; orgId: number }

onSubmit() {
this._organizationService.deleteOrganizationUser(this.data.member.user_id, this.data.orgId).subscribe({
next: () => { this.close('success') },
error: (error: string) => { this.errorMessage = error },
})
this._organizationService.deleteOrganizationUser(this.data.member.user_id, this.data.orgId)
.pipe(
takeUntil(this._unsubscribeAll$),
tap(() => { this.close('success') }),
).subscribe()
}

close(message: string) {
Expand All @@ -36,4 +40,9 @@ export class DeleteModalComponent {
dismiss() {
this._dialogRef.close()
}

ngOnDestroy(): void {
this._unsubscribeAll$.next()
this._unsubscribeAll$.complete()
}
}
Loading