From 8bdcad553333051e97bb6005b6e0de6c54985c76 Mon Sep 17 00:00:00 2001
From: Kenrick Tandrian <60643640+KenTandrian@users.noreply.github.com>
Date: Sat, 21 Feb 2026 14:33:24 +0700
Subject: [PATCH] Task/improve type safety in activities filter component
(#6358)
* fix(lib): resolve typescript errors
* feat(lib): migrate to takeUntilDestroyed
* fix(lib): resolve type errors
* feat(lib): implement output signal
* feat(lib): clean up variables
* fix(lib): resolve input is deprecated
* feat(lib): implement input signal on placeholder
* feat(lib): implement input signal on isLoading
---
.../activities-filter.component.html | 8 +-
.../activities-filter.component.ts | 83 +++++++++----------
2 files changed, 43 insertions(+), 48 deletions(-)
diff --git a/libs/ui/src/lib/activities-filter/activities-filter.component.html b/libs/ui/src/lib/activities-filter/activities-filter.component.html
index fd22ed351..d87ce16ce 100644
--- a/libs/ui/src/lib/activities-filter/activities-filter.component.html
+++ b/libs/ui/src/lib/activities-filter/activities-filter.component.html
@@ -10,7 +10,7 @@
[removable]="true"
(removed)="onRemoveFilter(filter)"
>
- {{ filter.label | gfSymbol }}
+ {{ filter.label ?? '' | gfSymbol }}
@@ -23,7 +23,7 @@
[matAutocomplete]="autocomplete"
[matChipInputFor]="chipList"
[matChipInputSeparatorKeyCodes]="separatorKeysCodes"
- [placeholder]="placeholder"
+ [placeholder]="placeholder()"
(matChipInputTokenEnd)="onAddFilter($event)"
/>
@@ -35,7 +35,7 @@
@for (filter of filterGroup.filters; track filter) {
- {{ filter.label | gfSymbol }}
+ {{ filter.label ?? '' | gfSymbol }}
}
@@ -46,7 +46,7 @@
disabled
mat-icon-button
matSuffix
- [ngClass]="{ 'd-none': !isLoading }"
+ [ngClass]="{ 'd-none': !isLoading() }"
>
diff --git a/libs/ui/src/lib/activities-filter/activities-filter.component.ts b/libs/ui/src/lib/activities-filter/activities-filter.component.ts
index 34f883c67..25fad683d 100644
--- a/libs/ui/src/lib/activities-filter/activities-filter.component.ts
+++ b/libs/ui/src/lib/activities-filter/activities-filter.component.ts
@@ -8,14 +8,14 @@ import {
ChangeDetectionStrategy,
Component,
ElementRef,
- EventEmitter,
Input,
OnChanges,
- OnDestroy,
- Output,
SimpleChanges,
- ViewChild
+ ViewChild,
+ input,
+ output
} from '@angular/core';
+import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { FormControl, ReactiveFormsModule } from '@angular/forms';
import {
MatAutocomplete,
@@ -30,8 +30,7 @@ import { IonIcon } from '@ionic/angular/standalone';
import { addIcons } from 'ionicons';
import { closeOutline, searchOutline } from 'ionicons/icons';
import { groupBy } from 'lodash';
-import { BehaviorSubject, Observable, Subject } from 'rxjs';
-import { takeUntil } from 'rxjs/operators';
+import { BehaviorSubject } from 'rxjs';
import { translate } from '../i18n';
@@ -53,28 +52,26 @@ import { translate } from '../i18n';
styleUrls: ['./activities-filter.component.scss'],
templateUrl: './activities-filter.component.html'
})
-export class GfActivitiesFilterComponent implements OnChanges, OnDestroy {
+export class GfActivitiesFilterComponent implements OnChanges {
@Input() allFilters: Filter[];
- @Input() isLoading: boolean;
- @Input() placeholder: string;
- @Output() valueChanged = new EventEmitter();
+ @ViewChild('autocomplete') protected matAutocomplete: MatAutocomplete;
+ @ViewChild('searchInput') protected searchInput: ElementRef;
- @ViewChild('autocomplete') matAutocomplete: MatAutocomplete;
- @ViewChild('searchInput') searchInput: ElementRef;
+ public readonly isLoading = input.required();
+ public readonly placeholder = input.required();
+ public readonly valueChanged = output();
- public filterGroups$: Subject = new BehaviorSubject([]);
- public filters$: Subject = new BehaviorSubject([]);
- public filters: Observable = this.filters$.asObservable();
- public searchControl = new FormControl(undefined);
- public selectedFilters: Filter[] = [];
- public separatorKeysCodes: number[] = [ENTER, COMMA];
-
- private unsubscribeSubject = new Subject();
+ protected readonly filterGroups$ = new BehaviorSubject([]);
+ protected readonly searchControl = new FormControl(
+ null
+ );
+ protected selectedFilters: Filter[] = [];
+ protected readonly separatorKeysCodes: number[] = [ENTER, COMMA];
public constructor() {
this.searchControl.valueChanges
- .pipe(takeUntil(this.unsubscribeSubject))
+ .pipe(takeUntilDestroyed())
.subscribe((filterOrSearchTerm) => {
if (filterOrSearchTerm) {
const searchTerm =
@@ -97,41 +94,39 @@ export class GfActivitiesFilterComponent implements OnChanges, OnDestroy {
}
}
- public onAddFilter({ input, value }: MatChipInputEvent) {
+ public onAddFilter({ chipInput, value }: MatChipInputEvent) {
if (value?.trim()) {
this.updateFilters();
}
// Reset the input value
- if (input) {
- input.value = '';
+ if (chipInput.inputElement) {
+ chipInput.inputElement.value = '';
}
- this.searchControl.setValue(undefined);
+ this.searchControl.setValue(null);
}
public onRemoveFilter(aFilter: Filter) {
- this.selectedFilters = this.selectedFilters.filter((filter) => {
- return filter.id !== aFilter.id;
+ this.selectedFilters = this.selectedFilters.filter(({ id }) => {
+ return id !== aFilter.id;
});
this.updateFilters();
}
public onSelectFilter(event: MatAutocompleteSelectedEvent) {
- this.selectedFilters.push(
- this.allFilters.find((filter) => {
- return filter.id === event.option.value;
- })
- );
+ const filter = this.allFilters.find(({ id }) => {
+ return id === event.option.value;
+ });
+
+ if (filter) {
+ this.selectedFilters.push(filter);
+ }
+
this.updateFilters();
this.searchInput.nativeElement.value = '';
- this.searchControl.setValue(undefined);
- }
-
- public ngOnDestroy() {
- this.unsubscribeSubject.next();
- this.unsubscribeSubject.complete();
+ this.searchControl.setValue(null);
}
private getGroupedFilters(searchTerm?: string) {
@@ -139,23 +134,23 @@ export class GfActivitiesFilterComponent implements OnChanges, OnDestroy {
this.allFilters
.filter((filter) => {
// Filter selected filters
- return !this.selectedFilters.some((selectedFilter) => {
- return selectedFilter.id === filter.id;
+ return !this.selectedFilters.some(({ id }) => {
+ return id === filter.id;
});
})
.filter((filter) => {
if (searchTerm) {
// Filter by search term
return filter.label
- .toLowerCase()
+ ?.toLowerCase()
.includes(searchTerm.toLowerCase());
}
return filter;
})
- .sort((a, b) => a.label?.localeCompare(b.label)),
- (filter) => {
- return filter.type;
+ .sort((a, b) => (a.label ?? '').localeCompare(b.label ?? '')),
+ ({ type }) => {
+ return type;
}
);