diff --git a/frontend/app/src/app/modules/assessments/detail/assessments-detail.component.ts b/frontend/app/src/app/modules/assessments/detail/assessments-detail.component.ts
index bca37c5e..c935c230 100644
--- a/frontend/app/src/app/modules/assessments/detail/assessments-detail.component.ts
+++ b/frontend/app/src/app/modules/assessments/detail/assessments-detail.component.ts
@@ -13,21 +13,23 @@
* this program. If not, see .
*/
-import {Component, Input, OnDestroy, OnInit, ViewChild} from '@angular/core';
+import {Component, DestroyRef, inject, Input, OnInit, ViewChild} from '@angular/core';
import {ActivatedRoute} from '@angular/router';
import {AssessmentsProvider} from '../assessments.provider';
import {DataDetailComponent, ExternalDataLoadEvent} from '../../data/detail/data-detail.component';
import {NavController, ViewWillEnter} from '@ionic/angular';
-import {Subscription} from 'rxjs';
import {DataRoutingService} from '../../data/data-routing.service';
import {SCAssessment} from '@openstapps/core';
+import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
@Component({
selector: 'assessments-detail',
templateUrl: 'assessments-detail.html',
styleUrls: ['assessments-detail.scss'],
})
-export class AssessmentsDetailComponent implements ViewWillEnter, OnInit, OnDestroy {
+export class AssessmentsDetailComponent implements ViewWillEnter, OnInit {
+ destroy$ = inject(DestroyRef);
+
constructor(
readonly route: ActivatedRoute,
readonly assessmentsProvider: AssessmentsProvider,
@@ -36,8 +38,6 @@ export class AssessmentsDetailComponent implements ViewWillEnter, OnInit, OnDest
readonly activatedRoute: ActivatedRoute,
) {}
- subscriptions: Subscription[] = [];
-
@Input() dataPathAutoRouting = true;
@ViewChild(DataDetailComponent)
@@ -47,19 +47,16 @@ export class AssessmentsDetailComponent implements ViewWillEnter, OnInit, OnDest
ngOnInit() {
if (!this.dataPathAutoRouting) return;
- this.subscriptions.push(
- this.dataRoutingService.pathSelectListener().subscribe(item => {
+ this.dataRoutingService
+ .pathSelectListener()
+ .pipe(takeUntilDestroyed(this.destroy$))
+ .subscribe(item => {
void this.navController.navigateBack(['assessments', 'detail', item.uid], {
queryParams: {
token: this.activatedRoute.snapshot.queryParamMap.get('token'),
},
});
- }),
- );
- }
-
- ngOnDestroy() {
- for (const sub of this.subscriptions) sub.unsubscribe();
+ });
}
getItem(event: ExternalDataLoadEvent) {
diff --git a/frontend/app/src/app/modules/assessments/list/assessments-simple-data-list.component.ts b/frontend/app/src/app/modules/assessments/list/assessments-simple-data-list.component.ts
index f78afe99..30a7dd5a 100644
--- a/frontend/app/src/app/modules/assessments/list/assessments-simple-data-list.component.ts
+++ b/frontend/app/src/app/modules/assessments/list/assessments-simple-data-list.component.ts
@@ -13,18 +13,18 @@
* this program. If not, see .
*/
-import {Component, Input, OnDestroy, OnInit} from '@angular/core';
+import {Component, DestroyRef, inject, Input, OnInit} from '@angular/core';
import {SCThings} from '@openstapps/core';
-import {Subscription} from 'rxjs';
import {DataRoutingService} from '../../data/data-routing.service';
import {ActivatedRoute, Router} from '@angular/router';
+import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
@Component({
selector: 'assessments-simple-data-list',
templateUrl: 'assessments-simple-data-list.html',
styleUrls: ['assessments-simple-data-list.scss'],
})
-export class AssessmentsSimpleDataListComponent implements OnInit, OnDestroy {
+export class AssessmentsSimpleDataListComponent implements OnInit {
/**
* All SCThings to display
*/
@@ -44,7 +44,7 @@ export class AssessmentsSimpleDataListComponent implements OnInit, OnDestroy {
this._items = new Promise(resolve => resolve(items));
}
- subscriptions: Subscription[] = [];
+ destroy$ = inject(DestroyRef);
constructor(
readonly dataRoutingService: DataRoutingService,
@@ -53,18 +53,15 @@ export class AssessmentsSimpleDataListComponent implements OnInit, OnDestroy {
) {}
ngOnInit() {
- this.subscriptions.push(
- this.dataRoutingService.itemSelectListener().subscribe(thing => {
+ this.dataRoutingService
+ .itemSelectListener()
+ .pipe(takeUntilDestroyed(this.destroy$))
+ .subscribe(thing => {
void this.router.navigate(['assessments', 'detail', thing.uid], {
queryParams: {
token: this.activatedRoute.snapshot.queryParamMap.get('token'),
},
});
- }),
- );
- }
-
- ngOnDestroy() {
- for (const subscription of this.subscriptions) subscription.unsubscribe();
+ });
}
}
diff --git a/frontend/app/src/app/modules/assessments/page/assessments-page.component.ts b/frontend/app/src/app/modules/assessments/page/assessments-page.component.ts
index 3e1be673..759ea087 100644
--- a/frontend/app/src/app/modules/assessments/page/assessments-page.component.ts
+++ b/frontend/app/src/app/modules/assessments/page/assessments-page.component.ts
@@ -12,17 +12,17 @@
* You should have received a copy of the GNU General Public License along with
* this program. If not, see .
*/
-import {AfterViewInit, Component, OnDestroy, OnInit, ViewChild} from '@angular/core';
+import {AfterViewInit, Component, DestroyRef, inject, OnInit, ViewChild} from '@angular/core';
import {AssessmentsProvider} from '../assessments.provider';
import {SCAssessment, SCCourseOfStudy} from '@openstapps/core';
import {ActivatedRoute, Router} from '@angular/router';
-import {Subscription} from 'rxjs';
import {NGXLogger} from 'ngx-logger';
import {materialSharedAxisX} from '../../../animation/material-motion';
import {SharedAxisChoreographer} from '../../../animation/animation-choreographer';
import {DataProvider, DataScope} from '../../data/data.provider';
import {DataRoutingService} from '../../data/data-routing.service';
import {groupBy, mapValues} from '@openstapps/collection-utils';
+import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
@Component({
selector: 'app-assessments-page',
@@ -30,7 +30,7 @@ import {groupBy, mapValues} from '@openstapps/collection-utils';
styleUrls: ['assessments-page.scss'],
animations: [materialSharedAxisX],
})
-export class AssessmentsPageComponent implements OnInit, AfterViewInit, OnDestroy {
+export class AssessmentsPageComponent implements OnInit, AfterViewInit {
assessments: Promise<
Record<
string,
@@ -43,12 +43,12 @@ export class AssessmentsPageComponent implements OnInit, AfterViewInit, OnDestro
assessmentKeys: string[] = [];
- routingSubscription: Subscription;
-
@ViewChild('segment') segmentView!: HTMLIonSegmentElement;
sharedAxisChoreographer: SharedAxisChoreographer = new SharedAxisChoreographer('', []);
+ destroy$ = inject(DestroyRef);
+
constructor(
readonly logger: NGXLogger,
readonly assessmentsProvider: AssessmentsProvider,
@@ -62,18 +62,17 @@ export class AssessmentsPageComponent implements OnInit, AfterViewInit, OnDestro
this.segmentView.value = this.sharedAxisChoreographer.currentValue;
}
- ngOnDestroy() {
- this.routingSubscription.unsubscribe();
- }
-
ngOnInit() {
- this.routingSubscription = this.dataRoutingService.itemSelectListener().subscribe(thing => {
- void this.router.navigate(['assessments', 'detail', thing.uid], {
- queryParams: {
- token: this.activatedRoute.snapshot.queryParamMap.get('token'),
- },
+ this.dataRoutingService
+ .itemSelectListener()
+ .pipe(takeUntilDestroyed(this.destroy$))
+ .subscribe(thing => {
+ void this.router.navigate(['assessments', 'detail', thing.uid], {
+ queryParams: {
+ token: this.activatedRoute.snapshot.queryParamMap.get('token'),
+ },
+ });
});
- });
this.activatedRoute.queryParams.subscribe(parameters => {
try {
diff --git a/frontend/app/src/app/modules/auth/auth-callback/page/auth-callback-page.component.ts b/frontend/app/src/app/modules/auth/auth-callback/page/auth-callback-page.component.ts
index 3305de70..9b815412 100644
--- a/frontend/app/src/app/modules/auth/auth-callback/page/auth-callback-page.component.ts
+++ b/frontend/app/src/app/modules/auth/auth-callback/page/auth-callback-page.component.ts
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 StApps
+ * Copyright (C) 2023 StApps
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation, version 3.
@@ -12,41 +12,29 @@
* You should have received a copy of the GNU General Public License along with
* this program. If not, see .
*/
-
-import {OnInit, OnDestroy, Component} from '@angular/core';
+import {Component} from '@angular/core';
import {NavController} from '@ionic/angular';
import {Router} from '@angular/router';
import {AuthActions, IAuthAction} from 'ionic-appauth';
-import {Subscription} from 'rxjs';
import {SCAuthorizationProviderType} from '@openstapps/core';
import {AuthHelperService} from '../../auth-helper.service';
+import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
+import {Observable} from 'rxjs';
+import {IPAIAAuthAction} from '../../paia/paia-auth-action';
@Component({
templateUrl: 'auth-callback-page.component.html',
styleUrls: ['auth-callback-page.component.scss'],
})
-export class AuthCallbackPageComponent implements OnInit, OnDestroy {
+export class AuthCallbackPageComponent {
PROVIDER_TYPE: SCAuthorizationProviderType = 'default';
- private authEvents: Subscription;
+ constructor(private navCtrl: NavController, private router: Router, private authHelper: AuthHelperService) {
+ const provider = this.authHelper.getProvider(this.PROVIDER_TYPE);
+ const events: Observable = provider.events$;
- constructor(
- private navCtrl: NavController,
- private router: Router,
- private authHelper: AuthHelperService,
- ) {}
-
- ngOnInit() {
- this.authEvents = this.authHelper
- .getProvider(this.PROVIDER_TYPE)
- .events$.subscribe((action: IAuthAction) => this.postCallback(action));
- this.authHelper
- .getProvider(this.PROVIDER_TYPE)
- .authorizationCallback(window.location.origin + this.router.url);
- }
-
- ngOnDestroy() {
- this.authEvents.unsubscribe();
+ events.pipe(takeUntilDestroyed()).subscribe((action: IAuthAction) => this.postCallback(action));
+ provider.authorizationCallback(window.location.origin + this.router.url);
}
async postCallback(action: IAuthAction) {
diff --git a/frontend/app/src/app/modules/background/schedule/schedule-sync.service.ts b/frontend/app/src/app/modules/background/schedule/schedule-sync.service.ts
index da88dc54..3ceaa38b 100644
--- a/frontend/app/src/app/modules/background/schedule/schedule-sync.service.ts
+++ b/frontend/app/src/app/modules/background/schedule/schedule-sync.service.ts
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 StApps
+ * Copyright (C) 2023 StApps
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation, version 3.
@@ -12,8 +12,7 @@
* You should have received a copy of the GNU General Public License along with
* this program. If not, see .
*/
-
-import {Injectable, OnDestroy} from '@angular/core';
+import {DestroyRef, inject, Injectable} from '@angular/core';
import {
DateSeriesRelevantData,
dateSeriesRelevantKeys,
@@ -28,7 +27,6 @@ import {BackgroundFetch} from '@transistorsoft/capacitor-background-fetch';
import {StorageProvider} from '../../storage/storage.provider';
import {CalendarService} from '../../calendar/calendar.service';
import {toICal} from '../../calendar/ical/ical';
-import {Subscription} from 'rxjs';
import {ChangesOf} from './changes';
import {hashStringToInt} from './hash';
import {
@@ -38,9 +36,12 @@ import {
} from '../../settings/page/calendar-sync-settings-keys';
import {filter} from 'rxjs/operators';
import {Capacitor} from '@capacitor/core';
+import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
@Injectable()
-export class ScheduleSyncService implements OnDestroy {
+export class ScheduleSyncService {
+ destroy$ = inject(DestroyRef);
+
constructor(
private scheduleProvider: ScheduleProvider,
private storageProvider: StorageProvider,
@@ -51,20 +52,19 @@ export class ScheduleSyncService implements OnDestroy {
) {}
init() {
- this.scheduleProvider.uuids$.pipe(filter(uuids => uuids?.length > 0)).subscribe(uuids => {
- this.uuids = uuids;
- void this.syncNativeCalendar();
- });
+ this.scheduleProvider.uuids$
+ .pipe(
+ takeUntilDestroyed(this.destroy$),
+ filter(uuids => uuids?.length > 0),
+ )
+ .subscribe(uuids => {
+ this.uuids = uuids;
+ void this.syncNativeCalendar();
+ });
}
uuids: SCUuid[];
- uuidSubscription: Subscription;
-
- ngOnDestroy() {
- this.uuidSubscription?.unsubscribe();
- }
-
private async isSyncEnabled(): Promise {
return getCalendarSetting(this.storageProvider, CALENDAR_SYNC_ENABLED_KEY);
}
diff --git a/frontend/app/src/app/modules/calendar/schedule.provider.ts b/frontend/app/src/app/modules/calendar/schedule.provider.ts
index 5c6c2cba..c94eef2c 100644
--- a/frontend/app/src/app/modules/calendar/schedule.provider.ts
+++ b/frontend/app/src/app/modules/calendar/schedule.provider.ts
@@ -13,7 +13,7 @@
* this program. If not, see .
*/
/* eslint-disable unicorn/no-null */
-import {Injectable, OnDestroy} from '@angular/core';
+import {DestroyRef, inject, Injectable, OnDestroy} from '@angular/core';
import {
Bounds,
SCDateSeries,
@@ -23,11 +23,12 @@ import {
SCThingType,
SCUuid,
} from '@openstapps/core';
-import {BehaviorSubject, Observable, Subscription} from 'rxjs';
+import {BehaviorSubject, Observable} from 'rxjs';
import {DataProvider} from '../data/data.provider';
import {map} from 'rxjs/operators';
import {DateFormatPipe, DurationPipe} from 'ngx-moment';
import {pick} from '@openstapps/collection-utils';
+import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
/**
*
@@ -73,7 +74,7 @@ export class ScheduleProvider implements OnDestroy {
private _partialEvents$?: BehaviorSubject;
- private _partialEventsSubscription?: Subscription;
+ destroy$ = inject(DestroyRef);
constructor(private readonly dataProvider: DataProvider) {
window.addEventListener('storage', this.storageListener);
@@ -125,7 +126,7 @@ export class ScheduleProvider implements OnDestroy {
const data = ScheduleProvider.get(ScheduleProvider.partialEventsStorageKey);
this._partialEvents$ = new BehaviorSubject(data ?? []);
- this._partialEventsSubscription = this._partialEvents$.subscribe(result => {
+ this._partialEvents$.pipe(takeUntilDestroyed(this.destroy$)).subscribe(result => {
ScheduleProvider.set(ScheduleProvider.partialEventsStorageKey, result);
});
}
@@ -257,7 +258,6 @@ export class ScheduleProvider implements OnDestroy {
* TODO
*/
ngOnDestroy(): void {
- this._partialEventsSubscription?.unsubscribe();
window.removeEventListener('storage', this.storageListener);
}
}
diff --git a/frontend/app/src/app/modules/catalog/catalog.component.ts b/frontend/app/src/app/modules/catalog/catalog.component.ts
index beefbf4c..2a1fa921 100644
--- a/frontend/app/src/app/modules/catalog/catalog.component.ts
+++ b/frontend/app/src/app/modules/catalog/catalog.component.ts
@@ -12,22 +12,22 @@
* You should have received a copy of the GNU General Public License along with
* this program. If not, see .
*/
-import {Component, OnInit, OnDestroy} from '@angular/core';
+import {Component, OnInit} from '@angular/core';
import {Router, ActivatedRoute} from '@angular/router';
import {SCCatalog, SCSemester} from '@openstapps/core';
import moment from 'moment';
-import {Subscription} from 'rxjs';
import {CatalogProvider} from './catalog.provider';
import {NGXLogger} from 'ngx-logger';
import {Location} from '@angular/common';
import {DataRoutingService} from '../data/data-routing.service';
+import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
@Component({
selector: 'app-catalog',
templateUrl: './catalog.component.html',
styleUrls: ['./catalog.component.scss'],
})
-export class CatalogComponent implements OnInit, OnDestroy {
+export class CatalogComponent implements OnInit {
/**
* SCSemester to show
*/
@@ -48,11 +48,6 @@ export class CatalogComponent implements OnInit, OnDestroy {
*/
catalogs: SCCatalog[] | undefined;
- /**
- * Array of all subscriptions to Observables
- */
- subscriptions: Subscription[] = [];
-
/**
* Supercatalog (SCCatalog) to refer to
*/
@@ -66,11 +61,12 @@ export class CatalogComponent implements OnInit, OnDestroy {
protected router: Router,
public location: Location,
) {
- this.subscriptions.push(
- this.dataRoutingService.itemSelectListener().subscribe(item => {
+ this.dataRoutingService
+ .itemSelectListener()
+ .pipe(takeUntilDestroyed())
+ .subscribe(item => {
void this.router.navigate(['data-detail', item.uid]);
- }),
- );
+ });
}
ngOnInit() {
@@ -78,15 +74,6 @@ export class CatalogComponent implements OnInit, OnDestroy {
void this.fetchCatalog();
}
- /**
- * Remove subscriptions when the component is removed
- */
- ngOnDestroy() {
- for (const sub of this.subscriptions) {
- sub.unsubscribe();
- }
- }
-
async fetchCatalog() {
try {
if (this.availableSemesters.length === 0) {
diff --git a/frontend/app/src/app/modules/dashboard/dashboard.component.ts b/frontend/app/src/app/modules/dashboard/dashboard.component.ts
index e4fe0f3d..b5fa34b5 100644
--- a/frontend/app/src/app/modules/dashboard/dashboard.component.ts
+++ b/frontend/app/src/app/modules/dashboard/dashboard.component.ts
@@ -12,21 +12,18 @@
* You should have received a copy of the GNU General Public License along with
* this program. If not, see .
*/
-import {Component, ElementRef, NgZone, OnDestroy, OnInit, ViewChild} from '@angular/core';
+import {Component, DestroyRef, ElementRef, inject, NgZone, OnDestroy, OnInit, ViewChild} from '@angular/core';
import {Router} from '@angular/router';
import {Location} from '@angular/common';
-import {Subscription} from 'rxjs';
import moment from 'moment';
import {SCDateSeries, SCUuid} from '@openstapps/core';
import {SplashScreen} from '@capacitor/splash-screen';
-
import {DataRoutingService} from '../data/data-routing.service';
import {ScheduleProvider} from '../calendar/schedule.provider';
import {AnimationController, IonContent} from '@ionic/angular';
import {DashboardCollapse} from './dashboard-collapse';
import {BreakpointObserver} from '@angular/cdk/layout';
-
-// const scrollTimeline = new ScrollTimeline();
+import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
@Component({
selector: 'app-dashboard',
@@ -34,11 +31,6 @@ import {BreakpointObserver} from '@angular/cdk/layout';
styleUrls: ['./dashboard.component.scss', '/dashboard.collapse.component.scss'],
})
export class DashboardComponent implements OnInit, OnDestroy {
- /**
- * Array of all subscriptions to Observables
- */
- subscriptions: Subscription[] = [];
-
@ViewChild('toolbar', {read: ElementRef}) toolbarRef: ElementRef;
@ViewChild('schedule', {read: ElementRef}) scheduleRef: ElementRef;
@@ -47,11 +39,6 @@ export class DashboardComponent implements OnInit, OnDestroy {
collapseAnimation: DashboardCollapse;
- /**
- * UUID subscription
- */
- private _eventUuidSubscription: Subscription;
-
/**
* The events to display
*/
@@ -74,6 +61,8 @@ export class DashboardComponent implements OnInit, OnDestroy {
},
};
+ destroy$ = inject(DestroyRef);
+
constructor(
private readonly dataRoutingService: DataRoutingService,
private scheduleProvider: ScheduleProvider,
@@ -83,15 +72,16 @@ export class DashboardComponent implements OnInit, OnDestroy {
private breakpointObserver: BreakpointObserver,
private zone: NgZone,
) {
- this.subscriptions.push(
- this.dataRoutingService.itemSelectListener().subscribe(item => {
+ this.dataRoutingService
+ .itemSelectListener()
+ .pipe(takeUntilDestroyed())
+ .subscribe(item => {
void this.router.navigate(['data-detail', item.uid]);
- }),
- );
+ });
}
async ngOnInit() {
- this._eventUuidSubscription = this.scheduleProvider.uuids$.subscribe(async result => {
+ this.scheduleProvider.uuids$.pipe(takeUntilDestroyed(this.destroy$)).subscribe(async result => {
this.eventUuids = result;
await this.loadNextEvent();
});
@@ -105,12 +95,13 @@ export class DashboardComponent implements OnInit, OnDestroy {
this.scheduleRef.nativeElement,
);
- this.subscriptions.push(
- this.breakpointObserver.observe(['(min-width: 768px)']).subscribe(async state => {
+ this.breakpointObserver
+ .observe(['(min-width: 768px)'])
+ .pipe(takeUntilDestroyed(this.destroy$))
+ .subscribe(async state => {
await this.collapseAnimation.ready;
this.collapseAnimation.active = !state.matches;
- }),
- );
+ });
}
async loadNextEvent() {
@@ -133,14 +124,7 @@ export class DashboardComponent implements OnInit, OnDestroy {
.find(({time}) => !!time)?.series;
}
- /**
- * Remove subscriptions when the component is removed
- */
ngOnDestroy() {
- for (const sub of this.subscriptions) {
- sub.unsubscribe();
- }
- this._eventUuidSubscription.unsubscribe();
this.collapseAnimation.destroy();
}
}
diff --git a/frontend/app/src/app/modules/data/chips/data/add-event-action-chip.component.ts b/frontend/app/src/app/modules/data/chips/data/add-event-action-chip.component.ts
index 39a2dfe1..aa962081 100644
--- a/frontend/app/src/app/modules/data/chips/data/add-event-action-chip.component.ts
+++ b/frontend/app/src/app/modules/data/chips/data/add-event-action-chip.component.ts
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 StApps
+ * Copyright (C) 2023 StApps
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation, version 3.
@@ -12,9 +12,7 @@
* You should have received a copy of the GNU General Public License along with
* this program. If not, see .
*/
-
-/* tslint:disable:prefer-function-over-method */
-import {Component, Input, OnDestroy, ViewChild} from '@angular/core';
+import {Component, DestroyRef, inject, Input, ViewChild} from '@angular/core';
import {IonRouterOutlet, ModalController} from '@ionic/angular';
import {SCDateSeries, SCThing, SCThingType, SCUuid} from '@openstapps/core';
import {Subscription} from 'rxjs';
@@ -27,6 +25,7 @@ import {
import {AddEventStates, AddEventStatesMap} from './add-event-action-chip.config';
import {EditEventSelectionComponent} from '../edit-event-selection.component';
import {AddEventReviewModalComponent} from '../../../calendar/add-event-review-modal.component';
+import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
/**
* Shows a horizontal list of action chips
@@ -37,7 +36,7 @@ import {AddEventReviewModalComponent} from '../../../calendar/add-event-review-m
styleUrls: ['add-event-action-chip.scss'],
animations: [chipSkeletonTransition, chipTransition],
})
-export class AddEventActionChipComponent implements OnDestroy {
+export class AddEventActionChipComponent {
/**
* Associated date series
*/
@@ -91,6 +90,8 @@ export class AddEventActionChipComponent implements OnDestroy {
@ViewChild('selection', {static: false})
selection: EditEventSelectionComponent;
+ destroy$ = inject(DestroyRef);
+
constructor(
readonly dataProvider: CoordinatedSearchProvider,
readonly modalController: ModalController,
@@ -111,13 +112,6 @@ export class AddEventActionChipComponent implements OnDestroy {
this.color = color;
}
- /**
- * TODO
- */
- ngOnDestroy() {
- this.uuidSubscription?.unsubscribe();
- }
-
async export() {
const modal = await this.modalController.create({
component: AddEventReviewModalComponent,
@@ -180,28 +174,30 @@ export class AddEventActionChipComponent implements OnDestroy {
})
.then(it => it.data as SCDateSeries[]);
- this.uuidSubscription = this.scheduleProvider.uuids$.subscribe(async result => {
- this.uuids = result;
- const associatedDateSeries = await this.associatedDateSeries;
- if (associatedDateSeries.length === 0) {
- this.applyState(AddEventStates.UNAVAILABLE);
+ this.uuidSubscription = this.scheduleProvider.uuids$
+ .pipe(takeUntilDestroyed(this.destroy$))
+ .subscribe(async result => {
+ this.uuids = result;
+ const associatedDateSeries = await this.associatedDateSeries;
+ if (associatedDateSeries.length === 0) {
+ this.applyState(AddEventStates.UNAVAILABLE);
- return;
- }
- switch (associatedDateSeries.map(it => it.uid).filter(it => !this.uuids.includes(it)).length) {
- case 0: {
- this.applyState(AddEventStates.ADDED_ALL);
- break;
+ return;
}
- case associatedDateSeries.length: {
- this.applyState(AddEventStates.REMOVED_ALL);
- break;
+ switch (associatedDateSeries.map(it => it.uid).filter(it => !this.uuids.includes(it)).length) {
+ case 0: {
+ this.applyState(AddEventStates.ADDED_ALL);
+ break;
+ }
+ case associatedDateSeries.length: {
+ this.applyState(AddEventStates.REMOVED_ALL);
+ break;
+ }
+ default: {
+ this.applyState(AddEventStates.ADDED_SOME);
+ break;
+ }
}
- default: {
- this.applyState(AddEventStates.ADDED_SOME);
- break;
- }
- }
- });
+ });
}
}
diff --git a/frontend/app/src/app/modules/data/detail/data-path.component.ts b/frontend/app/src/app/modules/data/detail/data-path.component.ts
index 04749168..d65069ff 100644
--- a/frontend/app/src/app/modules/data/detail/data-path.component.ts
+++ b/frontend/app/src/app/modules/data/detail/data-path.component.ts
@@ -12,27 +12,26 @@
* You should have received a copy of the GNU General Public License along with
* this program. If not, see .
*/
-import {Component, Input, OnDestroy, OnInit} from '@angular/core';
+import {Component, DestroyRef, inject, Input, OnInit} from '@angular/core';
import {RoutingStackService} from '../../../util/routing-stack.service';
import {SCCatalog, SCThings, SCThingType, SCThingWithoutReferences} from '@openstapps/core';
import {DataProvider, DataScope} from '../data.provider';
-import {fromEvent, Observable, Subscription} from 'rxjs';
+import {fromEvent, Observable} from 'rxjs';
import {map, startWith} from 'rxjs/operators';
import {DataRoutingService} from '../data-routing.service';
import {NavController} from '@ionic/angular';
+import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
@Component({
selector: 'stapps-data-path',
templateUrl: './data-path.html',
styleUrls: ['./data-path.scss'],
})
-export class DataPathComponent implements OnInit, OnDestroy {
+export class DataPathComponent implements OnInit {
path: Promise;
$width: Observable;
- subscriptions: Subscription[] = [];
-
@Input() autoRouting = true;
@Input() maxItems = 2;
@@ -74,6 +73,8 @@ export class DataPathComponent implements OnInit, OnDestroy {
}
}
+ destroy$ = inject(DestroyRef);
+
constructor(
readonly dataRoutingService: DataRoutingService,
readonly navController: NavController,
@@ -88,14 +89,11 @@ export class DataPathComponent implements OnInit, OnDestroy {
);
if (!this.autoRouting) return;
- this.subscriptions.push(
- this.dataRoutingService.pathSelectListener().subscribe(item => {
+ this.dataRoutingService
+ .pathSelectListener()
+ .pipe(takeUntilDestroyed(this.destroy$))
+ .subscribe(item => {
void this.navController.navigateBack(['data-detail', item.uid]);
- }),
- );
- }
-
- ngOnDestroy() {
- for (const sub of this.subscriptions) sub.unsubscribe();
+ });
}
}
diff --git a/frontend/app/src/app/modules/data/list/data-list.component.ts b/frontend/app/src/app/modules/data/list/data-list.component.ts
index bc7f8c22..a8883cc1 100644
--- a/frontend/app/src/app/modules/data/list/data-list.component.ts
+++ b/frontend/app/src/app/modules/data/list/data-list.component.ts
@@ -15,11 +15,12 @@
import {
Component,
ContentChild,
+ DestroyRef,
EventEmitter,
HostListener,
+ inject,
Input,
OnChanges,
- OnDestroy,
OnInit,
Output,
SimpleChanges,
@@ -27,8 +28,9 @@ import {
ViewChild,
} from '@angular/core';
import {SCThings} from '@openstapps/core';
-import {BehaviorSubject, Observable, Subscription} from 'rxjs';
+import {BehaviorSubject, Observable} from 'rxjs';
import {IonInfiniteScroll} from '@ionic/angular';
+import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
export interface DataListContext {
$implicit: T;
@@ -42,7 +44,7 @@ export interface DataListContext {
templateUrl: 'data-list.html',
styleUrls: ['data-list.scss'],
})
-export class DataListComponent implements OnChanges, OnInit, OnDestroy {
+export class DataListComponent implements OnChanges, OnInit {
/**
* All SCThings to display
*/
@@ -76,11 +78,6 @@ export class DataListComponent implements OnChanges, OnInit, OnDestroy {
*/
skeletonItems: number;
- /**
- * Array of all subscriptions to Observables
- */
- subscriptions: Subscription[] = [];
-
@ViewChild(IonInfiniteScroll) infiniteScroll: IonInfiniteScroll;
/**
@@ -88,6 +85,8 @@ export class DataListComponent implements OnChanges, OnInit, OnDestroy {
*/
@Input() loading = true;
+ destroy$ = inject(DestroyRef);
+
/**
* Calculate how many items would fill the screen
*/
@@ -112,20 +111,12 @@ export class DataListComponent implements OnChanges, OnInit, OnDestroy {
}
}
- ngOnDestroy(): void {
- for (const subscription of this.subscriptions) {
- subscription.unsubscribe();
- }
- }
-
ngOnInit(): void {
this.calcSkeletonItems();
if (this.resetToTop !== undefined) {
- this.subscriptions.push(
- this.resetToTop.subscribe(() => {
- // this.viewPort.scrollToIndex(0);
- }),
- );
+ this.resetToTop.pipe(takeUntilDestroyed(this.destroy$)).subscribe(() => {
+ // this.viewPort.scrollToIndex(0);
+ });
}
}
diff --git a/frontend/app/src/app/modules/data/list/food-data-list.component.ts b/frontend/app/src/app/modules/data/list/food-data-list.component.ts
index 95e6afbc..4c72f9dc 100644
--- a/frontend/app/src/app/modules/data/list/food-data-list.component.ts
+++ b/frontend/app/src/app/modules/data/list/food-data-list.component.ts
@@ -12,11 +12,13 @@
* You should have received a copy of the GNU General Public License along with
* this program. If not, see .
*/
-import {Component, OnDestroy, OnInit} from '@angular/core';
+import {Component, OnInit} from '@angular/core';
import {MapPosition} from '../../map/position.service';
import {SearchPageComponent} from './search-page.component';
import {Geolocation} from '@capacitor/geolocation';
-import {Subscription} from 'rxjs';
+import {BehaviorSubject} from 'rxjs';
+import {pauseWhen} from '../../../util/pause-when';
+import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
/**
* Presents a list of places for eating/drinking
@@ -25,19 +27,29 @@ import {Subscription} from 'rxjs';
templateUrl: 'search-page.html',
styleUrls: ['../../data/list/search-page.scss'],
})
-export class FoodDataListComponent extends SearchPageComponent implements OnInit, OnDestroy {
+export class FoodDataListComponent extends SearchPageComponent implements OnInit {
title = 'canteens.title';
showNavigation = false;
- locationWatch?: Subscription;
+ isNotInView$ = new BehaviorSubject(true);
/**
* Sets the forced filter to present only places for eating/drinking
*/
ngOnInit() {
- this.locationWatch?.unsubscribe();
- this.locationWatch = this.createLocationWatch();
+ this.positionService
+ .watchCurrentLocation({enableHighAccuracy: false, maximumAge: 1000})
+ .pipe(pauseWhen(this.isNotInView$), takeUntilDestroyed(this.destroy$))
+ .subscribe({
+ next: (position: MapPosition) => {
+ this.positionService.position = position;
+ },
+ error: async _error => {
+ this.positionService.position = undefined;
+ await Geolocation.checkPermissions();
+ },
+ });
this.showDefaultData = true;
this.sortQuery = [
@@ -101,32 +113,12 @@ export class FoodDataListComponent extends SearchPageComponent implements OnInit
super.ngOnInit();
}
- private createLocationWatch(): Subscription {
- return this.positionService
- .watchCurrentLocation(this.constructor.name, {enableHighAccuracy: false, maximumAge: 1000})
- .subscribe({
- next: (position: MapPosition) => {
- this.positionService.position = position;
- },
- error: async _error => {
- this.positionService.position = undefined;
- await Geolocation.checkPermissions();
- },
- });
- }
-
async ionViewWillEnter() {
await super.ionViewWillEnter();
- this.locationWatch?.unsubscribe();
- this.locationWatch = this.createLocationWatch();
+ this.isNotInView$.next(false);
}
ionViewWillLeave() {
- this.locationWatch?.unsubscribe();
- }
-
- ngOnDestroy() {
- super.ngOnDestroy();
- this.locationWatch?.unsubscribe();
+ this.isNotInView$.next(true);
}
}
diff --git a/frontend/app/src/app/modules/data/list/search-page.component.ts b/frontend/app/src/app/modules/data/list/search-page.component.ts
index f108a5bb..22a4af8c 100644
--- a/frontend/app/src/app/modules/data/list/search-page.component.ts
+++ b/frontend/app/src/app/modules/data/list/search-page.component.ts
@@ -12,7 +12,7 @@
* You should have received a copy of the GNU General Public License along with
* this program. If not, see .
*/
-import {Component, Input, OnDestroy, OnInit} from '@angular/core';
+import {Component, DestroyRef, inject, Input, OnInit} from '@angular/core';
import {ActivatedRoute, Router} from '@angular/router';
import {Keyboard} from '@capacitor/keyboard';
import {AlertController, AnimationBuilder, AnimationController} from '@ionic/angular';
@@ -26,7 +26,7 @@ import {
SCThings,
} from '@openstapps/core';
import {NGXLogger} from 'ngx-logger';
-import {combineLatest, Subject, Subscription} from 'rxjs';
+import {combineLatest, Subject} from 'rxjs';
import {debounceTime, distinctUntilChanged, startWith} from 'rxjs/operators';
import {ContextMenuService} from '../../menu/context/context-menu.service';
import {SettingsProvider} from '../../settings/settings.provider';
@@ -35,6 +35,7 @@ import {DataProvider} from '../data.provider';
import {PositionService} from '../../map/position.service';
import {ConfigProvider} from '../../config/config.provider';
import {searchPageSwitchAnimation} from './search-page-switch-animation';
+import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
/**
* SearchPageComponent queries things and shows list of things as search results and filter as context menu
@@ -45,7 +46,7 @@ import {searchPageSwitchAnimation} from './search-page-switch-animation';
styleUrls: ['search-page.scss'],
providers: [ContextMenuService],
})
-export class SearchPageComponent implements OnInit, OnDestroy {
+export class SearchPageComponent implements OnInit {
@Input() title = 'search.title';
@Input() placeholder = 'search.search_bar.placeholder';
@@ -141,10 +142,7 @@ export class SearchPageComponent implements OnInit, OnDestroy {
*/
sortQuery: SCSearchSort[] | undefined;
- /**
- * Array of all subscriptions to Observables
- */
- subscriptions: Subscription[] = [];
+ destroy$ = inject(DestroyRef);
routeAnimation: AnimationBuilder;
@@ -286,7 +284,7 @@ export class SearchPageComponent implements OnInit, OnDestroy {
this.contextMenuService.updateContextFilter(facets);
}
- ngOnInit() {
+ ngOnInit(defaultListeners = true) {
this.initialize();
this.contextMenuService.setContextSort({
name: 'sort',
@@ -308,7 +306,7 @@ export class SearchPageComponent implements OnInit, OnDestroy {
],
});
- this.subscriptions.push(
+ if (defaultListeners) {
combineLatest([
this.queryTextChanged.pipe(
debounceTime(this.searchQueryDueTime),
@@ -317,30 +315,37 @@ export class SearchPageComponent implements OnInit, OnDestroy {
),
this.contextMenuService.filterQueryChanged$.pipe(startWith(this.filterQuery)),
this.contextMenuService.sortQueryChanged$.pipe(startWith(this.sortQuery)),
- ]).subscribe(async query => {
- this.queryText = query[0];
- this.filterQuery = query[1];
- this.sortQuery = query[2];
- this.from = 0;
- if (this.filterQuery !== undefined || this.queryText?.length > 0 || this.showDefaultData) {
- await this.fetchAndUpdateItems();
- this.queryChanged.next();
- }
- }),
- this.settingsProvider.settingsActionChanged$.subscribe(({type, payload}) => {
- if (type === 'stapps.settings.changed') {
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
- const {category, name, value} = payload!;
- this.logger.log(`received event "settings.changed" with category:
+ ])
+ .pipe(takeUntilDestroyed(this.destroy$))
+ .subscribe(async query => {
+ this.queryText = query[0];
+ this.filterQuery = query[1];
+ this.sortQuery = query[2];
+ this.from = 0;
+ if (this.filterQuery !== undefined || this.queryText?.length > 0 || this.showDefaultData) {
+ await this.fetchAndUpdateItems();
+ this.queryChanged.next();
+ }
+ });
+ this.settingsProvider.settingsActionChanged$
+ .pipe(takeUntilDestroyed(this.destroy$))
+ .subscribe(({type, payload}) => {
+ if (type === 'stapps.settings.changed') {
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+ const {category, name, value} = payload!;
+ this.logger.log(`received event "settings.changed" with category:
${category}, name: ${name}, value: ${JSON.stringify(value)}`);
- }
- }),
- this.dataRoutingService.itemSelectListener().subscribe(item => {
- if (this.itemRouting) {
- void this.router.navigate(['/data-detail', item.uid]);
- }
- }),
- );
+ }
+ });
+ this.dataRoutingService
+ .itemSelectListener()
+ .pipe(takeUntilDestroyed(this.destroy$))
+ .subscribe(item => {
+ if (this.itemRouting) {
+ void this.router.navigate(['/data-detail', item.uid]);
+ }
+ });
+ }
try {
const features = this.configProvider.getValue('features') as SCFeatureConfiguration;
this.isHebisAvailable = !!features.plugins?.['hebis-plugin']?.urlPath;
@@ -359,10 +364,4 @@ export class SearchPageComponent implements OnInit, OnDestroy {
this.searchStringChanged(term);
}
}
-
- ngOnDestroy() {
- for (const subscription of this.subscriptions) {
- subscription.unsubscribe();
- }
- }
}
diff --git a/frontend/app/src/app/modules/data/list/simple-data-list.component.ts b/frontend/app/src/app/modules/data/list/simple-data-list.component.ts
index 9e4b0f80..7cf789cd 100644
--- a/frontend/app/src/app/modules/data/list/simple-data-list.component.ts
+++ b/frontend/app/src/app/modules/data/list/simple-data-list.component.ts
@@ -12,12 +12,12 @@
* You should have received a copy of the GNU General Public License along with
* this program. If not, see .
*/
-import {Component, ContentChild, Input, OnDestroy, OnInit, TemplateRef} from '@angular/core';
+import {Component, ContentChild, DestroyRef, inject, Input, OnInit, TemplateRef} from '@angular/core';
import {SCThings} from '@openstapps/core';
-import {Subscription} from 'rxjs';
import {Router} from '@angular/router';
import {DataRoutingService} from '../data-routing.service';
import {DataListContext} from './data-list.component';
+import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
/**
* Shows the list of items
@@ -27,7 +27,7 @@ import {DataListContext} from './data-list.component';
templateUrl: 'simple-data-list.html',
styleUrls: ['simple-data-list.scss'],
})
-export class SimpleDataListComponent implements OnInit, OnDestroy {
+export class SimpleDataListComponent implements OnInit {
@Input() items?: Promise;
/**
@@ -51,28 +51,17 @@ export class SimpleDataListComponent implements OnInit, OnDestroy {
*/
skeletonItems = 6;
- /**
- * Array of all subscriptions to Observables
- */
- subscriptions: Subscription[] = [];
+ destroy$ = inject(DestroyRef);
constructor(protected router: Router, private readonly dataRoutingService: DataRoutingService) {}
ngOnInit(): void {
if (!this.autoRouting) return;
- this.subscriptions.push(
- this.dataRoutingService.itemSelectListener().subscribe(item => {
+ this.dataRoutingService
+ .itemSelectListener()
+ .pipe(takeUntilDestroyed(this.destroy$))
+ .subscribe(item => {
void this.router.navigate(['/data-detail', item.uid]);
- }),
- );
- }
-
- /**
- * Remove subscriptions when the component is removed
- */
- ngOnDestroy() {
- for (const sub of this.subscriptions) {
- sub.unsubscribe();
- }
+ });
}
}
diff --git a/frontend/app/src/app/modules/data/types/place/place-detail-content.component.ts b/frontend/app/src/app/modules/data/types/place/place-detail-content.component.ts
index 48fbcb3f..79f35af7 100644
--- a/frontend/app/src/app/modules/data/types/place/place-detail-content.component.ts
+++ b/frontend/app/src/app/modules/data/types/place/place-detail-content.component.ts
@@ -12,42 +12,30 @@
* You should have received a copy of the GNU General Public License along with
* this program. If not, see .
*/
-import {Component, Input, OnDestroy, OnInit} from '@angular/core';
+import {Component, Input, OnInit} from '@angular/core';
import {SCBuilding, SCFloor, SCPointOfInterest, SCRoom, SCThings} from '@openstapps/core';
import {DataProvider} from '../../data.provider';
import {hasValidLocation, isSCFloor} from './place-types';
import {DataRoutingService} from '../../data-routing.service';
import {Router} from '@angular/router';
-import {Subscription} from 'rxjs';
+import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
-/**
- * TODO
- */
@Component({
providers: [DataProvider],
styleUrls: ['place-detail-content.scss'],
selector: 'stapps-place-detail-content',
templateUrl: 'place-detail-content.html',
})
-export class PlaceDetailContentComponent implements OnInit, OnDestroy {
- /**
- * TODO
- */
+export class PlaceDetailContentComponent implements OnInit {
@Input() item: SCBuilding | SCRoom | SCPointOfInterest | SCFloor;
@Input() openAsModal = false;
/**
- * Does it have valid location or not (for showing in in a map widget)
+ * Does it have a valid location or not (for showing in a map widget)
*/
hasValidLocation = false;
- itemRouting: Subscription;
-
- /**
- * TODO
- * @param item TODO
- */
hasCategories(item: SCThings): item is SCThings & {categories: string[]} {
return (item as {categories: string[]}).categories !== undefined;
}
@@ -67,16 +55,15 @@ export class PlaceDetailContentComponent implements OnInit, OnDestroy {
}
constructor(dataRoutingService: DataRoutingService, router: Router) {
- this.itemRouting = dataRoutingService.itemSelectListener().subscribe(item => {
- void router.navigate(['/data-detail', item.uid]);
- });
+ dataRoutingService
+ .itemSelectListener()
+ .pipe(takeUntilDestroyed())
+ .subscribe(item => {
+ void router.navigate(['/data-detail', item.uid]);
+ });
}
ngOnInit() {
this.hasValidLocation = !isSCFloor(this.item) && hasValidLocation(this.item);
}
-
- ngOnDestroy() {
- this.itemRouting.unsubscribe();
- }
}
diff --git a/frontend/app/src/app/modules/data/types/place/special/mensa/place-mensa-detail.component.ts b/frontend/app/src/app/modules/data/types/place/special/mensa/place-mensa-detail.component.ts
index bb54238a..b709e7d5 100644
--- a/frontend/app/src/app/modules/data/types/place/special/mensa/place-mensa-detail.component.ts
+++ b/frontend/app/src/app/modules/data/types/place/special/mensa/place-mensa-detail.component.ts
@@ -13,14 +13,14 @@
* this program. If not, see .
*/
import moment, {Moment} from 'moment';
-import {AfterViewInit, Component, Input, OnDestroy} from '@angular/core';
+import {AfterViewInit, Component, DestroyRef, inject, Input} from '@angular/core';
import {SCDish, SCISO8601Date, SCPlace} from '@openstapps/core';
import {PlaceMensaService} from './place-mensa-service';
import {Router} from '@angular/router';
-import {Subscription} from 'rxjs';
import {IonRouterOutlet} from '@ionic/angular';
import {DataRoutingService} from '../../../../data-routing.service';
import {groupBy} from '@openstapps/collection-utils';
+import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
/**
* TODO
@@ -31,7 +31,7 @@ import {groupBy} from '@openstapps/collection-utils';
templateUrl: 'place-mensa.html',
styleUrls: ['place-mensa.scss'],
})
-export class PlaceMensaDetailComponent implements AfterViewInit, OnDestroy {
+export class PlaceMensaDetailComponent implements AfterViewInit {
/**
* Map of dishes for each day
*/
@@ -44,9 +44,6 @@ export class PlaceMensaDetailComponent implements AfterViewInit, OnDestroy {
*/
@Input() displayRange = 7;
- /**
- * TODO
- */
@Input() item: SCPlace;
@Input() openAsModal = false;
@@ -61,10 +58,7 @@ export class PlaceMensaDetailComponent implements AfterViewInit, OnDestroy {
*/
startingDay: Moment;
- /**
- * Array of all subscriptions to Observables
- */
- subscriptions: Subscription[] = [];
+ destroy$ = inject(DestroyRef);
constructor(
private readonly mensaService: PlaceMensaService,
@@ -75,16 +69,14 @@ export class PlaceMensaDetailComponent implements AfterViewInit, OnDestroy {
this.startingDay = moment().startOf('day');
}
- /**
- * TODO
- */
ngAfterViewInit() {
if (!this.openAsModal) {
- this.subscriptions.push(
- this.dataRoutingService.itemSelectListener().subscribe(item => {
+ this.dataRoutingService
+ .itemSelectListener()
+ .pipe(takeUntilDestroyed(this.destroy$))
+ .subscribe(item => {
void this.router.navigate(['/data-detail', item.uid]);
- }),
- );
+ });
}
const dishesByDay = this.mensaService.getAllDishes(this.item, this.displayRange);
@@ -111,13 +103,4 @@ export class PlaceMensaDetailComponent implements AfterViewInit, OnDestroy {
}
});
}
-
- /**
- * Remove subscriptions when the component is removed
- */
- ngOnDestroy() {
- for (const sub of this.subscriptions) {
- sub.unsubscribe();
- }
- }
}
diff --git a/frontend/app/src/app/modules/favorites/favorites-page.component.ts b/frontend/app/src/app/modules/favorites/favorites-page.component.ts
index 5f6b59af..ee549979 100644
--- a/frontend/app/src/app/modules/favorites/favorites-page.component.ts
+++ b/frontend/app/src/app/modules/favorites/favorites-page.component.ts
@@ -27,6 +27,7 @@ import {DataProvider} from '../data/data.provider';
import {SettingsProvider} from '../settings/settings.provider';
import {PositionService} from '../map/position.service';
import {ConfigProvider} from '../config/config.provider';
+import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
/**
* The page for showing favorites
@@ -71,23 +72,21 @@ export class FavoritesPageComponent extends SearchPageComponent implements OnIni
}
ngOnInit() {
- super.ngOnInit();
- for (const subscription of this.subscriptions) {
- subscription.unsubscribe();
- }
+ super.ngOnInit(false);
// Recreate subscriptions to handle different routing
- this.subscriptions.push(
- combineLatest([
- this.queryTextChanged.pipe(
- debounceTime(this.searchQueryDueTime),
- distinctUntilChanged(),
- startWith(this.queryText),
- ),
- this.contextMenuService.filterQueryChanged$.pipe(startWith(this.filterQuery)),
- this.contextMenuService.sortQueryChanged$.pipe(startWith(this.sortQuery)),
- this.favoritesService.favoritesChanged$,
- ]).subscribe(async query => {
+ combineLatest([
+ this.queryTextChanged.pipe(
+ debounceTime(this.searchQueryDueTime),
+ distinctUntilChanged(),
+ startWith(this.queryText),
+ ),
+ this.contextMenuService.filterQueryChanged$.pipe(startWith(this.filterQuery)),
+ this.contextMenuService.sortQueryChanged$.pipe(startWith(this.sortQuery)),
+ this.favoritesService.favoritesChanged$,
+ ])
+ .pipe(takeUntilDestroyed(this.destroy$))
+ .subscribe(async query => {
this.queryText = query[0];
this.filterQuery = query[1];
this.sortQuery = query[2];
@@ -96,16 +95,21 @@ export class FavoritesPageComponent extends SearchPageComponent implements OnIni
await this.fetchAndUpdateItems();
this.queryChanged.next();
}
- }),
- this.settingsProvider.settingsActionChanged$.subscribe(({type, payload}) => {
+ });
+ this.settingsProvider.settingsActionChanged$
+ .pipe(takeUntilDestroyed(this.destroy$))
+ .subscribe(({type, payload}) => {
if (type === 'stapps.settings.changed') {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const {category, name, value} = payload!;
this.logger.log(`received event "settings.changed" with category:
${category}, name: ${name}, value: ${JSON.stringify(value)}`);
}
- }),
- this.dataRoutingService.itemSelectListener().subscribe(item => {
+ });
+ this.dataRoutingService
+ .itemSelectListener()
+ .pipe(takeUntilDestroyed(this.destroy$))
+ .subscribe(item => {
if (this.itemRouting) {
if ([SCThingType.Book, SCThingType.Periodical, SCThingType.Article].includes(item.type)) {
void this.router.navigate([
@@ -116,8 +120,7 @@ export class FavoritesPageComponent extends SearchPageComponent implements OnIni
void this.router.navigate(['data-detail', item.uid]);
}
}
- }),
- );
+ });
}
/**
diff --git a/frontend/app/src/app/modules/hebis/list/hebis-search-page.component.ts b/frontend/app/src/app/modules/hebis/list/hebis-search-page.component.ts
index 18672e19..eb79648f 100644
--- a/frontend/app/src/app/modules/hebis/list/hebis-search-page.component.ts
+++ b/frontend/app/src/app/modules/hebis/list/hebis-search-page.component.ts
@@ -12,7 +12,7 @@
* You should have received a copy of the GNU General Public License along with
* this program. If not, see .
*/
-import {Component, Input, OnDestroy, OnInit} from '@angular/core';
+import {Component, Input, OnInit} from '@angular/core';
import {ActivatedRoute, Router} from '@angular/router';
import {AlertController, AnimationController} from '@ionic/angular';
import {NGXLogger} from 'ngx-logger';
@@ -25,6 +25,7 @@ import {SearchPageComponent} from '../../data/list/search-page.component';
import {HebisDataProvider} from '../hebis-data.provider';
import {PositionService} from '../../map/position.service';
import {ConfigProvider} from '../../config/config.provider';
+import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
/**
* HebisSearchPageComponent queries things and shows list of things as search results and filter as context menu
@@ -34,7 +35,7 @@ import {ConfigProvider} from '../../config/config.provider';
templateUrl: 'hebis-search-page.html',
styleUrls: ['../../data/list/search-page.scss'],
})
-export class HebisSearchPageComponent extends SearchPageComponent implements OnInit, OnDestroy {
+export class HebisSearchPageComponent extends SearchPageComponent implements OnInit {
/**
* If routing should be done if the user clicks on an item
*/
@@ -144,43 +145,42 @@ export class HebisSearchPageComponent extends SearchPageComponent implements OnI
//this.fetchAndUpdateItems();
this.initialize();
- this.subscriptions.push(
- combineLatest([
- this.queryTextChanged.pipe(
- debounceTime(this.searchQueryDueTime),
- distinctUntilChanged(),
- startWith(this.queryText),
- ),
- ]).subscribe(async query => {
+ combineLatest([
+ this.queryTextChanged.pipe(
+ debounceTime(this.searchQueryDueTime),
+ distinctUntilChanged(),
+ startWith(this.queryText),
+ ),
+ ])
+ .pipe(takeUntilDestroyed(this.destroy$))
+ .subscribe(async query => {
this.queryText = query[0];
this.page = 0;
if (this.queryText?.length > 0 || this.showDefaultData) {
await this.fetchAndUpdateItems();
this.queryChanged.next();
}
- }),
- this.settingsProvider.settingsActionChanged$.subscribe(({type, payload}) => {
+ });
+ this.settingsProvider.settingsActionChanged$
+ .pipe(takeUntilDestroyed(this.destroy$))
+ .subscribe(({type, payload}) => {
if (type === 'stapps.settings.changed') {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const {category, name, value} = payload!;
this.logger.log(`received event "settings.changed" with category:
${category}, name: ${name}, value: ${JSON.stringify(value)}`);
}
- }),
- this.dataRoutingService.itemSelectListener().subscribe(async item => {
+ });
+ this.dataRoutingService
+ .itemSelectListener()
+ .pipe(takeUntilDestroyed(this.destroy$))
+ .subscribe(async item => {
if (this.itemRouting) {
void this.router.navigate([
'hebis-detail',
(item.origin && 'originalId' in item.origin && item.origin['originalId']) || '',
]);
}
- }),
- );
- }
-
- ngOnDestroy() {
- for (const subscription of this.subscriptions) {
- subscription.unsubscribe();
- }
+ });
}
}
diff --git a/frontend/app/src/app/modules/map/page/map-page.component.ts b/frontend/app/src/app/modules/map/page/map-page.component.ts
index d28b182c..373a9892 100644
--- a/frontend/app/src/app/modules/map/page/map-page.component.ts
+++ b/frontend/app/src/app/modules/map/page/map-page.component.ts
@@ -13,20 +13,22 @@
* this program. If not, see .
*/
import {Location} from '@angular/common';
-import {ChangeDetectorRef, Component, ElementRef, ViewChild} from '@angular/core';
+import {ChangeDetectorRef, Component, DestroyRef, ElementRef, inject, OnInit, ViewChild} from '@angular/core';
import {ActivatedRoute, Router} from '@angular/router';
import {Keyboard} from '@capacitor/keyboard';
import {AlertController, IonRouterOutlet, ModalController} from '@ionic/angular';
import {TranslateService} from '@ngx-translate/core';
import {SCBuilding, SCPlace, SCRoom, SCSearchFilter, SCUuid} from '@openstapps/core';
import {featureGroup, geoJSON, LatLng, Layer, Map, MapOptions, Marker, tileLayer} from 'leaflet';
-import {Subscription} from 'rxjs';
+import {BehaviorSubject} from 'rxjs';
import {DataRoutingService} from '../../data/data-routing.service';
import {ContextMenuService} from '../../menu/context/context-menu.service';
import {MapProvider} from '../map.provider';
import {MapPosition, PositionService} from '../position.service';
import {Geolocation, PermissionStatus} from '@capacitor/geolocation';
import {Capacitor} from '@capacitor/core';
+import {pauseWhen} from '../../../util/pause-when';
+import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
/**
* The main page of the map
@@ -36,7 +38,7 @@ import {Capacitor} from '@capacitor/core';
templateUrl: './map-page.html',
providers: [ContextMenuService],
})
-export class MapPageComponent {
+export class MapPageComponent implements OnInit {
/**
* Default map zoom level
*/
@@ -115,10 +117,9 @@ export class MapPageComponent {
*/
queryText: string;
- /**
- * Subscriptions used by the page
- */
- subscriptions: Subscription[] = [];
+ isNotInView$ = new BehaviorSubject(true);
+
+ destroy$ = inject(DestroyRef);
constructor(
private translateService: TranslateService,
@@ -148,6 +149,34 @@ export class MapPageComponent {
};
}
+ ngOnInit() {
+ this.dataRoutingService
+ .itemSelectListener()
+ .pipe(pauseWhen(this.isNotInView$), takeUntilDestroyed(this.destroy$))
+ .subscribe(async item => {
+ // in case the list item is clicked
+ if (this.items.length > 1) {
+ await Promise.all([this.modalController.dismiss(), this.showItem(item.uid)]);
+ } else {
+ void this.router.navigate(['/data-detail', item.uid]);
+ }
+ });
+ this.positionService
+ .watchCurrentLocation({enableHighAccuracy: true, maximumAge: 1000})
+ .pipe(pauseWhen(this.isNotInView$), takeUntilDestroyed(this.destroy$))
+ .subscribe({
+ next: (position: MapPosition) => {
+ this.position = position;
+ this.positionMarker = MapProvider.getPositionMarker(position, 'stapps-device-location', 32);
+ },
+ error: async _error => {
+ this.locationStatus = await Geolocation.checkPermissions();
+ // eslint-disable-next-line unicorn/no-null
+ this.position = null;
+ },
+ });
+ }
+
/**
* Animate to coordinates
* @param latLng Coordinates to animate to
@@ -257,47 +286,18 @@ export class MapPageComponent {
* Subscribe to needed observables and get the location status when user is entering the page
*/
async ionViewWillEnter() {
+ this.isNotInView$.next(false);
if (this.positionService.position) {
this.position = this.positionService.position;
this.positionMarker = MapProvider.getPositionMarker(this.position, 'stapps-device-location', 32);
}
- this.subscriptions.push(
- this.dataRoutingService.itemSelectListener().subscribe(async item => {
- // in case the list item is clicked
- if (this.items.length > 1) {
- await Promise.all([this.modalController.dismiss(), this.showItem(item.uid)]);
- } else {
- void this.router.navigate(['/data-detail', item.uid]);
- }
- }),
- this.positionService
- .watchCurrentLocation(this.constructor.name, {enableHighAccuracy: true, maximumAge: 1000})
- .subscribe({
- next: (position: MapPosition) => {
- this.position = position;
- this.positionMarker = MapProvider.getPositionMarker(position, 'stapps-device-location', 32);
- },
- error: async _error => {
- this.locationStatus = await Geolocation.checkPermissions();
- // eslint-disable-next-line unicorn/no-null
- this.position = null;
- },
- }),
- );
-
// get detailed location status (diagnostics only supports devices)
this.locationStatus = await Geolocation.checkPermissions();
}
- /**
- * Unsubscribe from all subscriptions when user leaves page
- */
ionViewWillLeave() {
- void this.positionService.clearWatcher(this.constructor.name);
- for (const sub of this.subscriptions) {
- sub.unsubscribe();
- }
+ this.isNotInView$.next(true);
}
/**
@@ -322,12 +322,12 @@ export class MapPageComponent {
this.addToMap(this.items, true, uid !== null);
this.contextMenuService.updateContextFilter(response.facets);
- this.subscriptions.push(
- this.contextMenuService.filterQueryChanged$.subscribe(query => {
+ this.contextMenuService.filterQueryChanged$
+ .pipe(pauseWhen(this.isNotInView$), takeUntilDestroyed(this.destroy$))
+ .subscribe(query => {
this.filterQuery = query;
this.fetchAndUpdateItems(false, true);
- }),
- );
+ });
this.distance = this.positionService.getDistance(this.items[0].geo.point);
}
diff --git a/frontend/app/src/app/modules/map/position.service.spec.ts b/frontend/app/src/app/modules/map/position.service.spec.ts
index fa231449..a7e2abc7 100644
--- a/frontend/app/src/app/modules/map/position.service.spec.ts
+++ b/frontend/app/src/app/modules/map/position.service.spec.ts
@@ -19,6 +19,8 @@ import {StorageModule} from '../storage/storage.module';
import {MapPosition, PositionService} from './position.service';
import {ConfigProvider} from '../config/config.provider';
import {LoggerTestingModule} from 'ngx-logger/testing';
+import {firstValueFrom} from 'rxjs';
+import {Geolocation} from '@capacitor/geolocation';
describe('PositionService', () => {
let positionService: PositionService;
@@ -54,31 +56,17 @@ describe('PositionService', () => {
expect(currentLocation).toEqual(sampleMapPosition);
});
- it('should continuously provide (watch) location of the device', done => {
- positionService.watchCurrentLocation('testCaller').subscribe(location => {
- expect(location).toBeDefined();
- done();
- });
+ it('should continuously provide (watch) location of the device', async () => {
+ expect(await firstValueFrom(positionService.watchCurrentLocation())).toBeDefined();
});
- it('should stop to continuously provide (watch) location of the device', done => {
- positionService.watchers.set(
- 'clearWatch',
- new Promise(resolve => {
- setTimeout(function () {
- resolve(`watcherID123`);
- }, 20);
- }),
- );
- positionService
- .clearWatcher('clearWatch')
- .then(result => {
- expect(result).toBeUndefined();
- done();
- })
- .catch(error => {
- expect(error).toBeUndefined();
- done();
- });
+ it('should stop to continuously provide (watch) location of the device', async () => {
+ const watchPosition = spyOn(Geolocation, 'watchPosition').and.resolveTo('abc');
+ const clearWatch = spyOn(Geolocation, 'clearWatch').and.callThrough();
+ const subscription = positionService.watchCurrentLocation().subscribe();
+ expect(watchPosition).toHaveBeenCalled();
+ subscription.unsubscribe();
+ await new Promise(resolve => setTimeout(resolve, 100));
+ expect(clearWatch).toHaveBeenCalledWith({id: 'abc'});
});
});
diff --git a/frontend/app/src/app/modules/map/position.service.ts b/frontend/app/src/app/modules/map/position.service.ts
index bb89f5f0..f29c96cd 100644
--- a/frontend/app/src/app/modules/map/position.service.ts
+++ b/frontend/app/src/app/modules/map/position.service.ts
@@ -85,11 +85,10 @@ export class PositionService {
}
/**
- * Watches (continuously gets) current coordinates information of the device
- * @param caller Identifier for later reference. (I.e use of `clearWatcher`)
- * @param options Options which define which data should be provided (e.g. how accurate or how old)
+ * Watches (continuously gets) the current coordinates information of the device
+ * @param options Options which define which data should be provided (e.g., how accurate or how old)
*/
- watchCurrentLocation(caller: string, options: PositionOptions = {}): Observable {
+ watchCurrentLocation(options: PositionOptions = {}): Observable {
return new Observable(subscriber => {
const watcherID = Geolocation.watchPosition(options, (position, error) => {
if (error) {
@@ -106,18 +105,15 @@ export class PositionService {
subscriber.next(this.position);
}
});
- this.watchers.set(caller, watcherID);
+ watcherID.then(console.log);
+ return {
+ unsubscribe() {
+ watcherID.then(id => {
+ console.log(id);
+ void Geolocation.clearWatch({id});
+ });
+ },
+ };
});
}
-
- /**
- * Clears watcher for a certain caller
- * @param caller Identifier of the caller wanting to clear the watcher
- */
- async clearWatcher(caller: string): Promise {
- const watcherID = await this.watchers.get(caller);
- if (watcherID) {
- Geolocation.clearWatch({id: watcherID});
- }
- }
}
diff --git a/frontend/app/src/app/modules/menu/context/context-menu.component.ts b/frontend/app/src/app/modules/menu/context/context-menu.component.ts
index 89270d69..fe00d10d 100644
--- a/frontend/app/src/app/modules/menu/context/context-menu.component.ts
+++ b/frontend/app/src/app/modules/menu/context/context-menu.component.ts
@@ -12,12 +12,12 @@
* You should have received a copy of the GNU General Public License along with
* this program. If not, see .
*/
-import {Component, Input, OnDestroy} from '@angular/core';
+import {Component, Input} from '@angular/core';
import {LangChangeEvent, TranslateService} from '@ngx-translate/core';
import {SCLanguage, SCThingTranslator, SCThingType, SCTranslations} from '@openstapps/core';
-import {Subscription} from 'rxjs';
import {ContextMenuService} from './context-menu.service';
import {FilterContext, FilterFacet, SortContext, SortContextOption} from './context-type.js';
+import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
/**
* The context menu
@@ -32,7 +32,7 @@ import {FilterContext, FilterFacet, SortContext, SortContextOption} from './cont
selector: 'stapps-context',
templateUrl: 'context-menu.html',
})
-export class ContextMenuComponent implements OnDestroy {
+export class ContextMenuComponent {
/**
* Id of the content the menu is used for
*/
@@ -77,11 +77,6 @@ export class ContextMenuComponent implements OnDestroy {
*/
sortOption: SortContext;
- /**
- * Array of all Subscriptions
- */
- subscriptions: Subscription[] = [];
-
/**
* Core translator
*/
@@ -94,18 +89,16 @@ export class ContextMenuComponent implements OnDestroy {
this.language = this.translateService.currentLang as keyof SCTranslations;
this.translator = new SCThingTranslator(this.language);
- this.subscriptions.push(
- this.translateService.onLangChange.subscribe((event: LangChangeEvent) => {
- this.language = event.lang as keyof SCTranslations;
- this.translator = new SCThingTranslator(this.language);
- }),
- this.contextMenuService.filterContextChanged$.subscribe(filterContext => {
- this.filterOption = filterContext;
- }),
- this.contextMenuService.sortOptions.subscribe(sortContext => {
- this.sortOption = sortContext;
- }),
- );
+ this.translateService.onLangChange.pipe(takeUntilDestroyed()).subscribe((event: LangChangeEvent) => {
+ this.language = event.lang as keyof SCTranslations;
+ this.translator = new SCThingTranslator(this.language);
+ });
+ this.contextMenuService.filterContextChanged$.pipe(takeUntilDestroyed()).subscribe(filterContext => {
+ this.filterOption = filterContext;
+ });
+ this.contextMenuService.sortOptions.pipe(takeUntilDestroyed()).subscribe(sortContext => {
+ this.sortOption = sortContext;
+ });
}
/**
@@ -122,15 +115,6 @@ export class ContextMenuComponent implements OnDestroy {
return this.translator.translatedPropertyValue(onlyForType, field, key);
}
- /**
- * Unsubscribe from Observables
- */
- ngOnDestroy() {
- for (const sub of this.subscriptions) {
- sub.unsubscribe();
- }
- }
-
/**
* Resets filter options
*/
diff --git a/frontend/app/src/app/modules/menu/navigation/offline-notice.component.ts b/frontend/app/src/app/modules/menu/navigation/offline-notice.component.ts
index 758fc8b3..a7ff281e 100644
--- a/frontend/app/src/app/modules/menu/navigation/offline-notice.component.ts
+++ b/frontend/app/src/app/modules/menu/navigation/offline-notice.component.ts
@@ -12,40 +12,35 @@
* You should have received a copy of the GNU General Public License along with
* this program. If not, see .
*/
-
-import {Component, ElementRef, HostBinding, OnDestroy, ViewChild} from '@angular/core';
+import {Component, ElementRef, HostBinding, ViewChild} from '@angular/core';
import {InternetConnectionService} from '../../../util/internet-connection.service';
-import {Subscription} from 'rxjs';
import {Router} from '@angular/router';
import {NGXLogger} from 'ngx-logger';
+import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
@Component({
selector: 'stapps-offline-notice',
templateUrl: 'offline-notice.html',
styleUrls: ['offline-notice.scss'],
})
-export class OfflineNoticeComponent implements OnDestroy {
+export class OfflineNoticeComponent {
@HostBinding('class.is-offline') isOffline = false;
@HostBinding('class.has-error') hasError = false;
@ViewChild('spinIcon', {read: ElementRef}) spinIcon: ElementRef;
- readonly subscriptions: Subscription[];
-
constructor(
readonly offlineProvider: InternetConnectionService,
readonly router: Router,
readonly logger: NGXLogger,
) {
- this.subscriptions = [
- this.offlineProvider.offline$.subscribe(isOffline => {
- this.isOffline = isOffline;
- }),
- this.offlineProvider.error$.subscribe(hasError => {
- this.hasError = hasError;
- }),
- ];
+ this.offlineProvider.offline$.pipe(takeUntilDestroyed()).subscribe(isOffline => {
+ this.isOffline = isOffline;
+ });
+ this.offlineProvider.error$.pipe(takeUntilDestroyed()).subscribe(hasError => {
+ this.hasError = hasError;
+ });
}
retry() {
@@ -54,10 +49,4 @@ export class OfflineNoticeComponent implements OnDestroy {
this.spinIcon.nativeElement.classList.add('spin');
this.offlineProvider.retry();
}
-
- ngOnDestroy() {
- for (const subscription of this.subscriptions) {
- subscription.unsubscribe();
- }
- }
}
diff --git a/frontend/app/src/app/modules/menu/navigation/root-link.directive.ts b/frontend/app/src/app/modules/menu/navigation/root-link.directive.ts
index 28345a40..5522072a 100644
--- a/frontend/app/src/app/modules/menu/navigation/root-link.directive.ts
+++ b/frontend/app/src/app/modules/menu/navigation/root-link.directive.ts
@@ -12,28 +12,26 @@
* You should have received a copy of the GNU General Public License along with
* this program. If not, see .
*/
-import {Directive, ElementRef, Input, OnDestroy, OnInit, Renderer2} from '@angular/core';
+import {DestroyRef, Directive, ElementRef, inject, Input, OnInit, Renderer2} from '@angular/core';
import {AnimationController, NavController} from '@ionic/angular';
import {Router, RouterEvent} from '@angular/router';
import {tabsTransition} from './tabs-transition';
-import {Subscription} from 'rxjs';
+import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
@Directive({
selector: '[rootLink]',
})
-export class RootLinkDirective implements OnInit, OnDestroy {
+export class RootLinkDirective implements OnInit {
@Input() rootLink: string;
@Input() redirectedFrom: string;
- dispose: () => void;
-
- subscriptions: Subscription[] = [];
-
private readonly classNames = ['tab-selected', 'link-active'];
private needsInit = true;
+ destroy$ = inject(DestroyRef);
+
constructor(
private element: ElementRef,
private renderer: Renderer2,
@@ -52,28 +50,28 @@ export class RootLinkDirective implements OnInit, OnDestroy {
this.needsInit = false;
}
- this.subscriptions.push(
- this.router.events.subscribe(event => {
- if (
- event instanceof RouterEvent &&
- // @ts-expect-error access private member
- (this.navController.direction === 'root' || this.needsInit)
- ) {
- if (event.url === this.rootLink || (this.redirectedFrom && event.url === this.redirectedFrom)) {
- this.setActive();
- } else {
- this.setInactive();
- }
- this.needsInit = false;
+ this.router.events.pipe(takeUntilDestroyed(this.destroy$)).subscribe(event => {
+ if (
+ event instanceof RouterEvent &&
+ // @ts-expect-error access private member
+ (this.navController.direction === 'root' || this.needsInit)
+ ) {
+ if (event.url === this.rootLink || (this.redirectedFrom && event.url === this.redirectedFrom)) {
+ this.setActive();
+ } else {
+ this.setInactive();
}
+ this.needsInit = false;
+ }
+ });
+
+ this.destroy$.onDestroy(
+ this.renderer.listen(this.element.nativeElement, 'click', () => {
+ this.setActive();
+ this.navController.setDirection('root', true, 'back', animation);
+ void this.router.navigate([this.rootLink]);
}),
);
-
- this.dispose = this.renderer.listen(this.element.nativeElement, 'click', () => {
- this.setActive();
- this.navController.setDirection('root', true, 'back', animation);
- void this.router.navigate([this.rootLink]);
- });
}
setActive() {
@@ -87,11 +85,4 @@ export class RootLinkDirective implements OnInit, OnDestroy {
this.renderer.removeClass(this.element.nativeElement, className);
}
}
-
- ngOnDestroy() {
- this.dispose();
- for (const subscription of this.subscriptions) {
- subscription.unsubscribe();
- }
- }
}
diff --git a/frontend/app/src/app/modules/profile/page/profile-page-section.component.ts b/frontend/app/src/app/modules/profile/page/profile-page-section.component.ts
index f9103d52..7ff56bd3 100644
--- a/frontend/app/src/app/modules/profile/page/profile-page-section.component.ts
+++ b/frontend/app/src/app/modules/profile/page/profile-page-section.component.ts
@@ -12,20 +12,20 @@
* You should have received a copy of the GNU General Public License along with
* this program. If not, see .
*/
-
-import {Component, Input, OnDestroy, OnInit} from '@angular/core';
+import {Component, DestroyRef, inject, Input, OnInit} from '@angular/core';
import {SCSection} from '../../../../config/profile-page-sections';
import {AuthHelperService} from '../../auth/auth-helper.service';
-import {Observable, Subscription} from 'rxjs';
+import {Observable} from 'rxjs';
import {SCAuthorizationProviderType} from '@openstapps/core';
import Swiper from 'swiper';
+import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
@Component({
selector: 'stapps-profile-page-section',
templateUrl: 'profile-page-section.html',
styleUrls: ['profile-page-section.scss'],
})
-export class ProfilePageSectionComponent implements OnInit, OnDestroy {
+export class ProfilePageSectionComponent implements OnInit {
@Input() item: SCSection;
@Input() minSlideWidth = 110;
@@ -36,8 +36,6 @@ export class ProfilePageSectionComponent implements OnInit, OnDestroy {
isBeginning = true;
- subscriptions: Subscription[] = [];
-
slidesPerView: number;
slidesFillScreen = false;
@@ -53,15 +51,17 @@ export class ProfilePageSectionComponent implements OnInit, OnDestroy {
},
};
+ destroy$ = inject(DestroyRef);
+
constructor(private authHelper: AuthHelperService) {}
ngOnInit() {
if (this.item.authProvider) {
- this.subscriptions.push(
- this.data[this.item.authProvider].loggedIn$.subscribe(loggedIn => {
+ this.data[this.item.authProvider].loggedIn$
+ .pipe(takeUntilDestroyed(this.destroy$))
+ .subscribe(loggedIn => {
this.isLoggedIn = loggedIn;
- }),
- );
+ });
}
}
@@ -96,10 +96,4 @@ export class ProfilePageSectionComponent implements OnInit, OnDestroy {
await this.authHelper.getProvider(providerType).signOut();
await this.authHelper.endBrowserSession(providerType);
}
-
- ngOnDestroy() {
- for (const subscription of this.subscriptions) {
- subscription.unsubscribe();
- }
- }
}
diff --git a/frontend/app/src/app/modules/profile/page/profile-page.component.ts b/frontend/app/src/app/modules/profile/page/profile-page.component.ts
index ffdd4154..5bbe4068 100644
--- a/frontend/app/src/app/modules/profile/page/profile-page.component.ts
+++ b/frontend/app/src/app/modules/profile/page/profile-page.component.ts
@@ -12,9 +12,8 @@
* You should have received a copy of the GNU General Public License along with
* this program. If not, see .
*/
-
import {Component, OnInit} from '@angular/core';
-import {Observable, of, Subscription} from 'rxjs';
+import {firstValueFrom, Observable, of, Subscription} from 'rxjs';
import {AuthHelperService} from '../../auth/auth-helper.service';
import {SCAuthorizationProviderType, SCDateSeries, SCUserConfiguration} from '@openstapps/core';
import {ActivatedRoute} from '@angular/router';
@@ -93,22 +92,20 @@ export class ProfilePageComponent implements OnInit {
}
async getMyCourses() {
- const uuidSubscription = this.scheduleProvider.uuids$.subscribe(async result => {
- const courses = await this.scheduleProvider.getDateSeries(result);
+ const result = await firstValueFrom(this.scheduleProvider.uuids$);
+ const courses = await this.scheduleProvider.getDateSeries(result);
- for (const course of courses.dates) {
- for (const date of course.dates) {
- if (moment(date).startOf('day').format() === this.todayDate) {
- this.myCoursesToday[this.myCoursesToday.length] = {
- startTime: moment(date).format('LT'),
- endTime: moment(date).add(course.duration).format('LT'),
- course,
- };
- }
+ for (const course of courses.dates) {
+ for (const date of course.dates) {
+ if (moment(date).startOf('day').format() === this.todayDate) {
+ this.myCoursesToday[this.myCoursesToday.length] = {
+ startTime: moment(date).format('LT'),
+ endTime: moment(date).add(course.duration).format('LT'),
+ course,
+ };
}
}
- uuidSubscription.unsubscribe();
- });
+ }
}
async signIn(providerType: SCAuthorizationProviderType) {
diff --git a/frontend/app/src/app/modules/schedule/page/calendar-view.component.ts b/frontend/app/src/app/modules/schedule/page/calendar-view.component.ts
index 68faff56..31799277 100644
--- a/frontend/app/src/app/modules/schedule/page/calendar-view.component.ts
+++ b/frontend/app/src/app/modules/schedule/page/calendar-view.component.ts
@@ -12,7 +12,7 @@
* You should have received a copy of the GNU General Public License along with
* this program. If not, see .
*/
-import {AfterViewInit, Component, Input, OnDestroy, OnInit, ViewChild} from '@angular/core';
+import {AfterViewInit, Component, Input, OnInit, ViewChild} from '@angular/core';
import {ActivatedRoute} from '@angular/router';
import moment from 'moment';
import {materialFade, materialManualFade, materialSharedAxisX} from '../../../animation/material-motion';
@@ -22,6 +22,7 @@ import {CalendarComponent} from './components/calendar.component';
import {CalendarService} from '../../calendar/calendar.service';
import {InfiniteSwiperComponent} from './grid/infinite-swiper.component';
import {IonContent} from '@ionic/angular';
+import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
/**
* Component that displays the schedule
@@ -32,7 +33,7 @@ import {IonContent} from '@ionic/angular';
styleUrls: ['calendar-view.scss', './components/calendar-component.scss'],
animations: [materialFade, materialSharedAxisX, materialManualFade],
})
-export class CalendarViewComponent extends CalendarComponent implements OnInit, OnDestroy, AfterViewInit {
+export class CalendarViewComponent extends CalendarComponent implements OnInit, AfterViewInit {
@ViewChild('mainSwiper') mainSwiper: InfiniteSwiperComponent;
@ViewChild('headerSwiper') headerSwiper: InfiniteSwiperComponent;
@@ -73,11 +74,8 @@ export class CalendarViewComponent extends CalendarComponent implements OnInit,
* Initialize
*/
ngOnInit() {
- super.onInit();
- if (this.calendarServiceSubscription) {
- this.calendarServiceSubscription.unsubscribe();
- }
- this.calendarServiceSubscription = this.calendarService.goToDateClicked.subscribe(async newIndex => {
+ super.ngOnInit();
+ this.calendarService.goToDateClicked.pipe(takeUntilDestroyed(this.destroy$)).subscribe(async newIndex => {
await this.mainSwiper.goToIndex(newIndex);
this.setDateRange(newIndex);
await this.scrollCursorIntoView(this.content);
@@ -88,13 +86,6 @@ export class CalendarViewComponent extends CalendarComponent implements OnInit,
void this.scrollCursorIntoView(this.content);
}
- /**
- * OnDestroy
- */
- ngOnDestroy(): void {
- super.onDestroy();
- }
-
/**
* Load events
*/
diff --git a/frontend/app/src/app/modules/schedule/page/components/calendar.component.ts b/frontend/app/src/app/modules/schedule/page/components/calendar.component.ts
index f116459f..ae6ce37e 100644
--- a/frontend/app/src/app/modules/schedule/page/components/calendar.component.ts
+++ b/frontend/app/src/app/modules/schedule/page/components/calendar.component.ts
@@ -12,7 +12,7 @@
* You should have received a copy of the GNU General Public License along with
* this program. If not, see .
*/
-import {Component, Input, OnDestroy, OnInit} from '@angular/core';
+import {Component, DestroyRef, inject, Input, OnInit} from '@angular/core';
import {ActivatedRoute} from '@angular/router';
import {SCISO8601Date, SCUuid} from '@openstapps/core';
import moment, {Moment} from 'moment';
@@ -22,9 +22,9 @@ import {ScheduleEvent, ScheduleResponsiveBreakpoint} from '../schema/schema';
import {SwiperComponent} from 'swiper/angular';
import {InfiniteSwiperComponent} from '../grid/infinite-swiper.component';
import {IonContent, IonDatetime} from '@ionic/angular';
-import {Subscription} from 'rxjs';
import {CalendarService} from '../../../calendar/calendar.service';
import {getScheduleCursorOffset} from '../grid/schedule-cursor-offset';
+import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
/**
* Component that displays the schedule
@@ -35,7 +35,7 @@ import {getScheduleCursorOffset} from '../grid/schedule-cursor-offset';
styleUrls: ['calendar-component.scss'],
animations: [materialFade, materialSharedAxisX, materialManualFade],
})
-export class CalendarComponent implements OnInit, OnDestroy {
+export class CalendarComponent implements OnInit {
/**
* The day that the schedule started out on
*/
@@ -52,8 +52,6 @@ export class CalendarComponent implements OnInit, OnDestroy {
endDate: '',
};
- calendarServiceSubscription: Subscription;
-
prevHeaderIndex = 0;
/**
@@ -98,15 +96,12 @@ export class CalendarComponent implements OnInit, OnDestroy {
*/
@Input() uuids: SCUuid[];
- /**
- * UUID subscription
- */
- uuidSubscription: Subscription;
-
@Input() useInfiniteSwiper = true;
@Input() weekDates: Array;
+ destroy$ = inject(DestroyRef);
+
constructor(
protected readonly activatedRoute: ActivatedRoute,
protected readonly calendarService: CalendarService,
@@ -114,14 +109,6 @@ export class CalendarComponent implements OnInit, OnDestroy {
) {}
ngOnInit() {
- this.onInit();
- }
-
- ngOnDestroy() {
- this.onDestroy();
- }
-
- onInit() {
let dayString: string | number | null = this.activatedRoute.snapshot.paramMap.get('date');
if (dayString == undefined || dayString === 'now') {
const fragments = window.location.href.split('/');
@@ -133,7 +120,7 @@ export class CalendarComponent implements OnInit, OnDestroy {
this.baselineDate = moment(dayString).startOf('day');
this.initialSlideIndex = new Promise(resolve => {
- this.uuidSubscription = this.scheduleProvider.uuids$.subscribe(async result => {
+ this.scheduleProvider.uuids$.pipe(takeUntilDestroyed(this.destroy$)).subscribe(async result => {
this.uuids = result;
resolve(await this.loadEvents());
});
@@ -150,13 +137,6 @@ export class CalendarComponent implements OnInit, OnDestroy {
});
}
- onDestroy() {
- this.uuidSubscription.unsubscribe();
- if (this.calendarServiceSubscription) {
- this.calendarServiceSubscription.unsubscribe();
- }
- }
-
/**
* Get date from baseline date and index of current slide.
* @param index number
diff --git a/frontend/app/src/app/modules/schedule/page/schedule-single-events.component.ts b/frontend/app/src/app/modules/schedule/page/schedule-single-events.component.ts
index c26026f4..f4f8a6f4 100644
--- a/frontend/app/src/app/modules/schedule/page/schedule-single-events.component.ts
+++ b/frontend/app/src/app/modules/schedule/page/schedule-single-events.component.ts
@@ -12,14 +12,14 @@
* You should have received a copy of the GNU General Public License along with
* this program. If not, see .
*/
-import {Component, Input, OnDestroy, OnInit} from '@angular/core';
+import {Component, DestroyRef, inject, Input, OnInit} from '@angular/core';
import {SCDateSeries, SCUuid} from '@openstapps/core';
import moment from 'moment';
-import {Subscription} from 'rxjs';
import {materialFade} from '../../../animation/material-motion';
import {ScheduleProvider} from '../../calendar/schedule.provider';
import {ScheduleEvent} from './schema/schema';
import {groupBy, omit, stringSortBy} from '@openstapps/collection-utils';
+import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
/**
* A single event
@@ -31,7 +31,7 @@ export interface ScheduleSingleEvent {
day: string;
/**
- * Event the date is referring to
+ * The event the date is referring to
*/
event: ScheduleEvent;
}
@@ -45,12 +45,7 @@ export interface ScheduleSingleEvent {
styleUrls: ['schedule-single-events.scss'],
animations: [materialFade],
})
-export class ScheduleSingleEventsComponent implements OnInit, OnDestroy {
- /**
- * UUID subscription
- */
- private _uuidSubscription: Subscription;
-
+export class ScheduleSingleEventsComponent implements OnInit {
/**
* The events to display
*/
@@ -66,6 +61,8 @@ export class ScheduleSingleEventsComponent implements OnInit, OnDestroy {
*/
@Input() scale = 60;
+ destroy$ = inject(DestroyRef);
+
/**
* Sorts dates to a list of days with events on each
*/
@@ -116,24 +113,13 @@ export class ScheduleSingleEventsComponent implements OnInit, OnDestroy {
);
// TODO: replace with filter
- const test = ScheduleSingleEventsComponent.groupDateSeriesToDays(
+ return ScheduleSingleEventsComponent.groupDateSeriesToDays(
dateSeries.dates.filter(it => !it.repeatFrequency),
);
- return test;
}
- /**
- * OnDestroy
- */
- ngOnDestroy(): void {
- this._uuidSubscription.unsubscribe();
- }
-
- /**
- * Initialize
- */
ngOnInit() {
- this._uuidSubscription = this.scheduleProvider.uuids$.subscribe(async result => {
+ this.scheduleProvider.uuids$.pipe(takeUntilDestroyed(this.destroy$)).subscribe(async result => {
this.uuids = result;
this.events = this.fetchDateSeries();
});
diff --git a/frontend/app/src/app/modules/schedule/page/schedule-view.component.ts b/frontend/app/src/app/modules/schedule/page/schedule-view.component.ts
index 10729688..235814cf 100644
--- a/frontend/app/src/app/modules/schedule/page/schedule-view.component.ts
+++ b/frontend/app/src/app/modules/schedule/page/schedule-view.component.ts
@@ -12,7 +12,7 @@
* You should have received a copy of the GNU General Public License along with
* this program. If not, see .
*/
-import {AfterViewInit, Component, Input, OnDestroy, OnInit, ViewChild} from '@angular/core';
+import {AfterViewInit, Component, Input, OnInit, ViewChild} from '@angular/core';
import {ActivatedRoute} from '@angular/router';
import moment, {Moment} from 'moment';
import {materialFade, materialManualFade, materialSharedAxisX} from '../../../animation/material-motion';
@@ -23,6 +23,7 @@ import {CalendarService} from '../../calendar/calendar.service';
import {CalendarComponent} from './components/calendar.component';
import {IonContent, IonDatetime} from '@ionic/angular';
import {SwiperComponent} from 'swiper/angular';
+import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
/**
* Component that displays the schedule
@@ -33,10 +34,7 @@ import {SwiperComponent} from 'swiper/angular';
styleUrls: ['schedule-view.scss', './components/calendar-component.scss'],
animations: [materialFade, materialSharedAxisX, materialManualFade],
})
-export class ScheduleViewComponent
- extends CalendarComponent
- implements OnInit, AfterViewInit, OnDestroy, AfterViewInit
-{
+export class ScheduleViewComponent extends CalendarComponent implements OnInit, AfterViewInit {
@ViewChild('mainSwiper') mainSwiper: SwiperComponent;
@ViewChild('headerSwiper') headerSwiper: SwiperComponent;
@@ -99,11 +97,8 @@ export class ScheduleViewComponent
* Initialize
*/
ngOnInit() {
- super.onInit();
- if (this.calendarServiceSubscription) {
- this.calendarServiceSubscription.unsubscribe();
- }
- this.calendarServiceSubscription = this.calendarService.goToDateClicked.subscribe(() => {
+ super.ngOnInit();
+ this.calendarService.goToDateClicked.pipe(takeUntilDestroyed(this.destroy$)).subscribe(() => {
this.slideToToday();
});
}
@@ -112,13 +107,6 @@ export class ScheduleViewComponent
this.slideToToday();
}
- /**
- * OnDestroy
- */
- ngOnDestroy(): void {
- super.onDestroy();
- }
-
/**
* Slide today into view.
*/
diff --git a/frontend/app/src/app/translation/thing-translate.service.ts b/frontend/app/src/app/translation/thing-translate.service.ts
index 1ae833d2..b1f16544 100644
--- a/frontend/app/src/app/translation/thing-translate.service.ts
+++ b/frontend/app/src/app/translation/thing-translate.service.ts
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 StApps
+ * Copyright (C) 2023 StApps
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation, version 3.
@@ -12,8 +12,7 @@
* You should have received a copy of the GNU General Public License along with
* this program. If not, see .
*/
-
-import {Injectable, OnDestroy} from '@angular/core';
+import {Injectable} from '@angular/core';
import {LangChangeEvent, TranslateService} from '@ngx-translate/core';
import {
SCLanguage,
@@ -24,8 +23,8 @@ import {
SCTranslations,
} from '@openstapps/core';
import moment from 'moment';
-import {Subscription} from 'rxjs';
import {isDefined, ThingTranslateParser} from './thing-translate.parser';
+import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
// export const DEFAULT_LANGUAGE = new InjectionToken('DEFAULT_LANGUAGE');
@@ -34,9 +33,7 @@ import {isDefined, ThingTranslateParser} from './thing-translate.parser';
@Injectable({
providedIn: 'root',
})
-export class ThingTranslateService implements OnDestroy {
- onLangChange: Subscription;
-
+export class ThingTranslateService {
translator: SCThingTranslator;
/**
@@ -49,7 +46,7 @@ export class ThingTranslateService implements OnDestroy {
(translateService.currentLang ?? translateService.defaultLang) as SCLanguageCode,
);
/** set the default language from configuration */
- this.onLangChange = this.translateService.onLangChange.subscribe((event: LangChangeEvent) => {
+ this.translateService.onLangChange.pipe(takeUntilDestroyed()).subscribe((event: LangChangeEvent) => {
this.translator.language = event.lang as keyof SCTranslations;
moment.locale(event.lang);
});
@@ -104,10 +101,4 @@ export class ThingTranslateService implements OnDestroy {
return this.getParsedResult(translatedPropertyNames, keyPath);
}
-
- ngOnDestroy() {
- if (!this.onLangChange.closed) {
- this.onLangChange.unsubscribe();
- }
- }
}
diff --git a/frontend/app/src/app/translation/translate-simple.pipe.ts b/frontend/app/src/app/translation/translate-simple.pipe.ts
index 11edf52f..a0ea2cc8 100644
--- a/frontend/app/src/app/translation/translate-simple.pipe.ts
+++ b/frontend/app/src/app/translation/translate-simple.pipe.ts
@@ -12,17 +12,18 @@
* You should have received a copy of the GNU General Public License along with
* this program. If not, see .
*/
-import {Injectable, OnDestroy, Pipe, PipeTransform} from '@angular/core';
+import {DestroyRef, inject, Injectable, Pipe, PipeTransform} from '@angular/core';
import {TranslateService} from '@ngx-translate/core';
import {get} from '@openstapps/collection-utils';
import {Subscription} from 'rxjs';
+import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
@Injectable()
@Pipe({
name: 'translateSimple',
pure: false,
})
-export class TranslateSimplePipe implements PipeTransform, OnDestroy {
+export class TranslateSimplePipe implements PipeTransform {
value: unknown;
query: unknown;
@@ -31,6 +32,8 @@ export class TranslateSimplePipe implements PipeTransform, OnDestroy {
onLangChange: Subscription;
+ destroy$ = inject(DestroyRef);
+
constructor(private readonly translate: TranslateService) {}
// eslint-disable-next-line @typescript-eslint/ban-types
@@ -58,14 +61,12 @@ export class TranslateSimplePipe implements PipeTransform, OnDestroy {
this.updateValue();
- this.onLangChange ??= this.translate.onLangChange.subscribe(() => {
- this.updateValue();
- });
+ this.onLangChange ??= this.translate.onLangChange
+ .pipe(takeUntilDestroyed(this.destroy$))
+ .subscribe(() => {
+ this.updateValue();
+ });
return this.value as never;
}
-
- ngOnDestroy(): void {
- this.onLangChange?.unsubscribe();
- }
}
diff --git a/frontend/app/src/app/util/ion-icon/ion-back-button.directive.ts b/frontend/app/src/app/util/ion-icon/ion-back-button.directive.ts
index dd9df2bc..1b742ba9 100644
--- a/frontend/app/src/app/util/ion-icon/ion-back-button.directive.ts
+++ b/frontend/app/src/app/util/ion-icon/ion-back-button.directive.ts
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 StApps
+ * Copyright (C) 2023 StApps
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation, version 3.
@@ -12,20 +12,29 @@
* You should have received a copy of the GNU General Public License along with
* this program. If not, see .
*/
-
-import {Directive, ElementRef, Host, Optional, Self, ViewContainerRef} from '@angular/core';
+import {
+ DestroyRef,
+ Directive,
+ ElementRef,
+ Host,
+ inject,
+ OnInit,
+ Optional,
+ Self,
+ ViewContainerRef,
+} from '@angular/core';
import {SCIcon} from './icon';
import {IconReplacer} from './replace-util';
import {TranslateService} from '@ngx-translate/core';
-import {Subscription} from 'rxjs';
import {IonBackButton} from '@ionic/angular';
import {TitleCasePipe} from '@angular/common';
+import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
@Directive({
selector: 'ion-back-button',
})
-export class IonBackButtonDirective extends IconReplacer {
- private subscriptions: Subscription[] = [];
+export class IonBackButtonDirective extends IconReplacer implements OnInit {
+ destroy$ = inject(DestroyRef);
constructor(
element: ElementRef,
@@ -45,17 +54,13 @@ export class IonBackButtonDirective extends IconReplacer {
});
}
- init() {
- this.subscriptions.push(
- this.translateService.stream('back').subscribe((value: string) => {
+ async ngOnInit() {
+ await super.ngOnInit();
+ this.translateService
+ .stream('back')
+ .pipe(takeUntilDestroyed(this.destroy$))
+ .subscribe((value: string) => {
this.ionBackButton.text = this.titleCasePipe.transform(value);
- }),
- );
- }
-
- destroy() {
- for (const subscription of this.subscriptions) {
- subscription.unsubscribe();
- }
+ });
}
}
diff --git a/frontend/app/src/app/util/ion-icon/ion-breadcrumb.directive.ts b/frontend/app/src/app/util/ion-icon/ion-breadcrumb.directive.ts
index e9139ae1..2e9de757 100644
--- a/frontend/app/src/app/util/ion-icon/ion-breadcrumb.directive.ts
+++ b/frontend/app/src/app/util/ion-icon/ion-breadcrumb.directive.ts
@@ -12,7 +12,6 @@
* You should have received a copy of the GNU General Public License along with
* this program. If not, see .
*/
-
import {Directive, ElementRef, ViewContainerRef} from '@angular/core';
import {SCIcon} from './icon';
import {IconReplacer} from './replace-util';
diff --git a/frontend/app/src/app/util/ion-icon/ion-reorder.directive.ts b/frontend/app/src/app/util/ion-icon/ion-reorder.directive.ts
index bbac84b9..2a8f74e9 100644
--- a/frontend/app/src/app/util/ion-icon/ion-reorder.directive.ts
+++ b/frontend/app/src/app/util/ion-icon/ion-reorder.directive.ts
@@ -12,7 +12,6 @@
* You should have received a copy of the GNU General Public License along with
* this program. If not, see .
*/
-
import {Directive, ElementRef, ViewContainerRef} from '@angular/core';
import {SCIcon} from './icon';
import {IconReplacer} from './replace-util';
diff --git a/frontend/app/src/app/util/ion-icon/replace-util.ts b/frontend/app/src/app/util/ion-icon/replace-util.ts
index cdadf44a..5dd03d32 100644
--- a/frontend/app/src/app/util/ion-icon/replace-util.ts
+++ b/frontend/app/src/app/util/ion-icon/replace-util.ts
@@ -65,24 +65,7 @@ export abstract class IconReplacer implements OnInit, OnDestroy {
*/
abstract replace(): void;
- /**
- * If any additional work needs to be done, this
- * is called during ngOnInit
- */
- init() {
- // noop
- }
-
- /**
- * If you need to do cleanup, this method is called during ngOnDestroy
- */
- destroy() {
- // noop
- }
-
async ngOnInit() {
- this.init();
-
if (this.host) {
this.attachObserver();
} else {
@@ -110,8 +93,8 @@ export abstract class IconReplacer implements OnInit, OnDestroy {
scIcon.location.nativeElement.classList.add(...icon.classList);
if (this.iconDomLocation === 'shadow') {
- // shadow dom needs to utilize slotting, to put it outside
- // the shadow dom, otherwise it won't receive any css data
+ // shadow dom needs to utilize slotting, to put it outside the shadow dom
+ // otherwise it won't receive any css data
const slot = document.createElement('slot');
slot.name = this.slotName + slotName;
icon.replaceWith(slot);
@@ -137,6 +120,5 @@ export abstract class IconReplacer implements OnInit, OnDestroy {
ngOnDestroy() {
this.mutationObserver?.disconnect();
- this.destroy();
}
}
diff --git a/frontend/app/src/app/util/pause-when.ts b/frontend/app/src/app/util/pause-when.ts
new file mode 100644
index 00000000..4e89b7cb
--- /dev/null
+++ b/frontend/app/src/app/util/pause-when.ts
@@ -0,0 +1,12 @@
+import {filter, MonoTypeOperatorFunction, Observable, repeat, takeUntil} from 'rxjs';
+
+/**
+ * Pause the observable if the notifier emits true, and resume when it emits false
+ */
+export function pauseWhen(notifier: Observable): MonoTypeOperatorFunction {
+ return value =>
+ value.pipe(
+ takeUntil(notifier.pipe(filter(it => it))),
+ repeat({delay: () => notifier.pipe(filter(it => !it))}),
+ );
+}