From ad523179bfbd14ddd79f259ce2dcd9cf89b70064 Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Thu, 5 Feb 2026 22:03:42 +0100 Subject: [PATCH] [PM-30677] Convert SendSearchComponent to OnPush (#18322) Converts SendSearchComponent to use OnPush change detection. --- .../app/tools/send-v2/send-v2.component.ts | 24 +--------- .../send-search/send-search.component.html | 8 +--- .../src/send-search/send-search.component.ts | 45 ++++++++++--------- 3 files changed, 27 insertions(+), 50 deletions(-) diff --git a/apps/desktop/src/app/tools/send-v2/send-v2.component.ts b/apps/desktop/src/app/tools/send-v2/send-v2.component.ts index 0df71a78412..271418ae5b2 100644 --- a/apps/desktop/src/app/tools/send-v2/send-v2.component.ts +++ b/apps/desktop/src/app/tools/send-v2/send-v2.component.ts @@ -1,14 +1,6 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { - ChangeDetectorRef, - Component, - computed, - effect, - inject, - signal, - viewChild, -} from "@angular/core"; +import { Component, computed, inject, signal, viewChild } from "@angular/core"; import { toSignal } from "@angular/core/rxjs-interop"; import { combineLatest, map, switchMap, lastValueFrom } from "rxjs"; @@ -92,7 +84,6 @@ export class SendV2Component { private dialogService = inject(DialogService); private toastService = inject(ToastService); private logService = inject(LogService); - private cdr = inject(ChangeDetectorRef); protected readonly useDrawerEditMode = toSignal( this.configService.getFeatureFlag$(FeatureFlag.DesktopUiMigrationMilestone2), @@ -137,17 +128,6 @@ export class SendV2Component { { initialValue: null }, ); - constructor() { - // WORKAROUND: Force change detection when data updates - // This is needed because SendSearchComponent (shared lib) hasn't migrated to OnPush yet - // and doesn't trigger CD properly when search/add operations complete - // TODO: Remove this once SendSearchComponent migrates to OnPush (tracked in CL-764) - effect(() => { - this.filteredSends(); - this.cdr.markForCheck(); - }); - } - protected readonly selectedSendType = computed(() => { const action = this.action(); @@ -171,8 +151,6 @@ export class SendV2Component { } else { this.action.set(Action.Add); this.sendId.set(null); - - this.cdr.detectChanges(); void this.addEditComponent()?.resetAndLoad(); } } diff --git a/libs/tools/send/send-ui/src/send-search/send-search.component.html b/libs/tools/send/send-ui/src/send-search/send-search.component.html index 7cf154c0ee8..fbbe436d158 100644 --- a/libs/tools/send/send-ui/src/send-search/send-search.component.html +++ b/libs/tools/send/send-ui/src/send-search/send-search.component.html @@ -1,7 +1 @@ - - + diff --git a/libs/tools/send/send-ui/src/send-search/send-search.component.ts b/libs/tools/send/send-ui/src/send-search/send-search.component.ts index 02cb5ef2eda..03eaf9b3430 100644 --- a/libs/tools/send/send-ui/src/send-search/send-search.component.ts +++ b/libs/tools/send/send-ui/src/send-search/send-search.component.ts @@ -1,50 +1,55 @@ -import { CommonModule } from "@angular/common"; -import { Component } from "@angular/core"; -import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; +import { ChangeDetectionStrategy, Component, inject, model } from "@angular/core"; +import { takeUntilDestroyed, toObservable } from "@angular/core/rxjs-interop"; import { FormsModule } from "@angular/forms"; -import { Subject, Subscription, debounceTime, filter } from "rxjs"; +import { debounceTime, filter } from "rxjs"; -import { JslibModule } from "@bitwarden/angular/jslib.module"; import { SearchModule } from "@bitwarden/components"; +import { I18nPipe } from "@bitwarden/ui-common"; import { SendItemsService } from "../services/send-items.service"; const SearchTextDebounceInterval = 200; -// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush -// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection +/** + * Search component for filtering Send items. + * + * Provides a search input that filters the Send list with debounced updates. + * Syncs with the service's latest search text to maintain state across navigation. + */ @Component({ - imports: [CommonModule, SearchModule, JslibModule, FormsModule], selector: "tools-send-search", templateUrl: "send-search.component.html", + imports: [FormsModule, I18nPipe, SearchModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class SendSearchComponent { - searchText: string = ""; + private sendListItemService = inject(SendItemsService); - private searchText$ = new Subject(); + /** The current search text entered by the user. */ + protected readonly searchText = model(""); - constructor(private sendListItemService: SendItemsService) { + constructor() { this.subscribeToLatestSearchText(); this.subscribeToApplyFilter(); } - onSearchTextChanged() { - this.searchText$.next(this.searchText); - } - - subscribeToLatestSearchText(): Subscription { - return this.sendListItemService.latestSearchText$ + private subscribeToLatestSearchText(): void { + this.sendListItemService.latestSearchText$ .pipe( takeUntilDestroyed(), filter((data) => !!data), ) .subscribe((text) => { - this.searchText = text; + this.searchText.set(text); }); } - subscribeToApplyFilter(): Subscription { - return this.searchText$ + /** + * Applies the search filter to the Send list with a debounce delay. + * This prevents excessive filtering while the user is still typing. + */ + private subscribeToApplyFilter(): void { + toObservable(this.searchText) .pipe(debounceTime(SearchTextDebounceInterval), takeUntilDestroyed()) .subscribe((data) => { this.sendListItemService.applyFilter(data);