1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-10 13:23:34 +00:00

Migrate UIF to use takeuntilDestroyed (#15777)

This commit is contained in:
Oscar Hinton
2025-07-29 10:17:30 +02:00
committed by GitHub
parent 33ed9ac6ac
commit e3d5385661
6 changed files with 51 additions and 76 deletions

View File

@@ -1,8 +1,9 @@
// FIXME: Update this file to be type safe and remove this and next line // FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore // @ts-strict-ignore
import { ChangeDetectorRef, Component, OnDestroy, OnInit } from "@angular/core"; import { ChangeDetectorRef, Component, OnInit, inject, DestroyRef } from "@angular/core";
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
import { ActivatedRoute, Data, NavigationEnd, Router, RouterModule } from "@angular/router"; import { ActivatedRoute, Data, NavigationEnd, Router, RouterModule } from "@angular/router";
import { Subject, filter, switchMap, takeUntil, tap } from "rxjs"; import { filter, switchMap, tap } from "rxjs";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
@@ -51,9 +52,7 @@ export interface AnonLayoutWrapperData {
templateUrl: "anon-layout-wrapper.component.html", templateUrl: "anon-layout-wrapper.component.html",
imports: [AnonLayoutComponent, RouterModule], imports: [AnonLayoutComponent, RouterModule],
}) })
export class AnonLayoutWrapperComponent implements OnInit, OnDestroy { export class AnonLayoutWrapperComponent implements OnInit {
private destroy$ = new Subject<void>();
protected pageTitle: string; protected pageTitle: string;
protected pageSubtitle: string; protected pageSubtitle: string;
protected pageIcon: Icon; protected pageIcon: Icon;
@@ -70,6 +69,8 @@ export class AnonLayoutWrapperComponent implements OnInit, OnDestroy {
private changeDetectorRef: ChangeDetectorRef, private changeDetectorRef: ChangeDetectorRef,
) {} ) {}
private readonly destroyRef = inject(DestroyRef);
ngOnInit(): void { ngOnInit(): void {
// Set the initial page data on load // Set the initial page data on load
this.setAnonLayoutWrapperDataFromRouteData(this.route.snapshot.firstChild?.data); this.setAnonLayoutWrapperDataFromRouteData(this.route.snapshot.firstChild?.data);
@@ -85,7 +86,7 @@ export class AnonLayoutWrapperComponent implements OnInit, OnDestroy {
// reset page data on page changes // reset page data on page changes
tap(() => this.resetPageData()), tap(() => this.resetPageData()),
switchMap(() => this.route.firstChild?.data || null), switchMap(() => this.route.firstChild?.data || null),
takeUntil(this.destroy$), takeUntilDestroyed(this.destroyRef),
) )
.subscribe((firstChildRouteData: Data | null) => { .subscribe((firstChildRouteData: Data | null) => {
this.setAnonLayoutWrapperDataFromRouteData(firstChildRouteData); this.setAnonLayoutWrapperDataFromRouteData(firstChildRouteData);
@@ -121,7 +122,7 @@ export class AnonLayoutWrapperComponent implements OnInit, OnDestroy {
private listenForServiceDataChanges() { private listenForServiceDataChanges() {
this.anonLayoutWrapperDataService this.anonLayoutWrapperDataService
.anonLayoutWrapperData$() .anonLayoutWrapperData$()
.pipe(takeUntil(this.destroy$)) .pipe(takeUntilDestroyed(this.destroyRef))
.subscribe((data: AnonLayoutWrapperData) => { .subscribe((data: AnonLayoutWrapperData) => {
this.setAnonLayoutWrapperData(data); this.setAnonLayoutWrapperData(data);
}); });
@@ -180,9 +181,4 @@ export class AnonLayoutWrapperComponent implements OnInit, OnDestroy {
this.hideCardWrapper = null; this.hideCardWrapper = null;
this.hideIcon = null; this.hideIcon = null;
} }
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
} }

View File

@@ -1,7 +1,8 @@
// FIXME: Update this file to be type safe and remove this and next line // FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore // @ts-strict-ignore
import { Directive, HostListener, model, OnDestroy, Optional } from "@angular/core"; import { Directive, HostListener, model, Optional, inject, DestroyRef } from "@angular/core";
import { BehaviorSubject, finalize, Subject, takeUntil, tap } from "rxjs"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
import { BehaviorSubject, finalize, tap } from "rxjs";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service";
@@ -16,8 +17,7 @@ import { FunctionReturningAwaitable, functionToObservable } from "../utils/funct
@Directive({ @Directive({
selector: "[bitAction]", selector: "[bitAction]",
}) })
export class BitActionDirective implements OnDestroy { export class BitActionDirective {
private destroy$ = new Subject<void>();
private _loading$ = new BehaviorSubject<boolean>(false); private _loading$ = new BehaviorSubject<boolean>(false);
/** /**
@@ -40,6 +40,8 @@ export class BitActionDirective implements OnDestroy {
readonly handler = model<FunctionReturningAwaitable>(undefined, { alias: "bitAction" }); readonly handler = model<FunctionReturningAwaitable>(undefined, { alias: "bitAction" });
private readonly destroyRef = inject(DestroyRef);
constructor( constructor(
private buttonComponent: ButtonLikeAbstraction, private buttonComponent: ButtonLikeAbstraction,
@Optional() private validationService?: ValidationService, @Optional() private validationService?: ValidationService,
@@ -62,13 +64,8 @@ export class BitActionDirective implements OnDestroy {
}, },
}), }),
finalize(() => (this.loading = false)), finalize(() => (this.loading = false)),
takeUntil(this.destroy$), takeUntilDestroyed(this.destroyRef),
) )
.subscribe(); .subscribe();
} }
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
}
} }

View File

@@ -1,8 +1,9 @@
// FIXME: Update this file to be type safe and remove this and next line // FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore // @ts-strict-ignore
import { Directive, OnDestroy, OnInit, Optional, input } from "@angular/core"; import { Directive, OnInit, Optional, input, inject, DestroyRef } from "@angular/core";
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
import { FormGroupDirective } from "@angular/forms"; import { FormGroupDirective } from "@angular/forms";
import { BehaviorSubject, catchError, filter, of, Subject, switchMap, takeUntil } from "rxjs"; import { BehaviorSubject, catchError, filter, of, switchMap } from "rxjs";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service";
@@ -15,8 +16,9 @@ import { FunctionReturningAwaitable, functionToObservable } from "../utils/funct
@Directive({ @Directive({
selector: "[formGroup][bitSubmit]", selector: "[formGroup][bitSubmit]",
}) })
export class BitSubmitDirective implements OnInit, OnDestroy { export class BitSubmitDirective implements OnInit {
private destroy$ = new Subject<void>(); private readonly destroyRef = inject(DestroyRef);
private _loading$ = new BehaviorSubject<boolean>(false); private _loading$ = new BehaviorSubject<boolean>(false);
private _disabled$ = new BehaviorSubject<boolean>(false); private _disabled$ = new BehaviorSubject<boolean>(false);
@@ -51,7 +53,7 @@ export class BitSubmitDirective implements OnInit, OnDestroy {
}), }),
); );
}), }),
takeUntil(this.destroy$), takeUntilDestroyed(),
) )
.subscribe({ .subscribe({
next: () => (this.loading = false), next: () => (this.loading = false),
@@ -60,13 +62,15 @@ export class BitSubmitDirective implements OnInit, OnDestroy {
} }
ngOnInit(): void { ngOnInit(): void {
this.formGroupDirective.statusChanges.pipe(takeUntil(this.destroy$)).subscribe((c) => { this.formGroupDirective.statusChanges
if (this.allowDisabledFormSubmit()) { .pipe(takeUntilDestroyed(this.destroyRef))
this._disabled$.next(false); .subscribe((c) => {
} else { if (this.allowDisabledFormSubmit()) {
this._disabled$.next(c === "DISABLED"); this._disabled$.next(false);
} } else {
}); this._disabled$.next(c === "DISABLED");
}
});
} }
get disabled() { get disabled() {
@@ -85,9 +89,4 @@ export class BitSubmitDirective implements OnInit, OnDestroy {
this.disabled = value; this.disabled = value;
this._loading$.next(value); this._loading$.next(value);
} }
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
}
} }

View File

@@ -1,7 +1,7 @@
// FIXME: Update this file to be type safe and remove this and next line // FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore // @ts-strict-ignore
import { Directive, OnDestroy, Optional, input } from "@angular/core"; import { Directive, Optional, input } from "@angular/core";
import { Subject, takeUntil } from "rxjs"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
import { ButtonLikeAbstraction } from "../shared/button-like.abstraction"; import { ButtonLikeAbstraction } from "../shared/button-like.abstraction";
@@ -26,9 +26,7 @@ import { BitSubmitDirective } from "./bit-submit.directive";
@Directive({ @Directive({
selector: "button[bitFormButton]", selector: "button[bitFormButton]",
}) })
export class BitFormButtonDirective implements OnDestroy { export class BitFormButtonDirective {
private destroy$ = new Subject<void>();
readonly type = input<string>(); readonly type = input<string>();
readonly disabled = input<boolean>(); readonly disabled = input<boolean>();
@@ -38,7 +36,7 @@ export class BitFormButtonDirective implements OnDestroy {
@Optional() actionDirective?: BitActionDirective, @Optional() actionDirective?: BitActionDirective,
) { ) {
if (submitDirective && buttonComponent) { if (submitDirective && buttonComponent) {
submitDirective.loading$.pipe(takeUntil(this.destroy$)).subscribe((loading) => { submitDirective.loading$.pipe(takeUntilDestroyed()).subscribe((loading) => {
if (this.type() === "submit") { if (this.type() === "submit") {
buttonComponent.loading.set(loading); buttonComponent.loading.set(loading);
} else { } else {
@@ -46,7 +44,7 @@ export class BitFormButtonDirective implements OnDestroy {
} }
}); });
submitDirective.disabled$.pipe(takeUntil(this.destroy$)).subscribe((disabled) => { submitDirective.disabled$.pipe(takeUntilDestroyed()).subscribe((disabled) => {
const disabledValue = this.disabled(); const disabledValue = this.disabled();
if (disabledValue !== false) { if (disabledValue !== false) {
buttonComponent.disabled.set(disabledValue || disabled); buttonComponent.disabled.set(disabledValue || disabled);
@@ -55,18 +53,13 @@ export class BitFormButtonDirective implements OnDestroy {
} }
if (submitDirective && actionDirective) { if (submitDirective && actionDirective) {
actionDirective.loading$.pipe(takeUntil(this.destroy$)).subscribe((disabled) => { actionDirective.loading$.pipe(takeUntilDestroyed()).subscribe((disabled) => {
submitDirective.disabled = disabled; submitDirective.disabled = disabled;
}); });
submitDirective.disabled$.pipe(takeUntil(this.destroy$)).subscribe((disabled) => { submitDirective.disabled$.pipe(takeUntilDestroyed()).subscribe((disabled) => {
actionDirective.disabled = disabled; actionDirective.disabled = disabled;
}); });
} }
} }
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
}
} }

View File

@@ -11,13 +11,14 @@ import {
ContentChildren, ContentChildren,
EventEmitter, EventEmitter,
Input, Input,
OnDestroy,
Output, Output,
QueryList, QueryList,
ViewChildren, ViewChildren,
input, input,
inject,
DestroyRef,
} from "@angular/core"; } from "@angular/core";
import { Subject, takeUntil } from "rxjs"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
import { TabHeaderComponent } from "../shared/tab-header.component"; import { TabHeaderComponent } from "../shared/tab-header.component";
import { TabListContainerDirective } from "../shared/tab-list-container.directive"; import { TabListContainerDirective } from "../shared/tab-list-container.directive";
@@ -40,11 +41,10 @@ let nextId = 0;
TabBodyComponent, TabBodyComponent,
], ],
}) })
export class TabGroupComponent export class TabGroupComponent implements AfterContentChecked, AfterContentInit, AfterViewInit {
implements AfterContentChecked, AfterContentInit, AfterViewInit, OnDestroy private readonly destroyRef = inject(DestroyRef);
{
private readonly _groupId: number; private readonly _groupId: number;
private readonly destroy$ = new Subject<void>();
private _indexToSelect: number | null = 0; private _indexToSelect: number | null = 0;
/** /**
@@ -150,7 +150,7 @@ export class TabGroupComponent
ngAfterContentInit() { ngAfterContentInit() {
// Subscribe to any changes in the number of tabs, in order to be able // Subscribe to any changes in the number of tabs, in order to be able
// to re-render content when new tabs are added or removed. // to re-render content when new tabs are added or removed.
this.tabs.changes.pipe(takeUntil(this.destroy$)).subscribe(() => { this.tabs.changes.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(() => {
const indexToSelect = this._clampTabIndex(this._indexToSelect); const indexToSelect = this._clampTabIndex(this._indexToSelect);
// If the selected tab didn't explicitly change, keep the previously // If the selected tab didn't explicitly change, keep the previously
@@ -183,11 +183,6 @@ export class TabGroupComponent
}); });
} }
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
private _clampTabIndex(index: number): number { private _clampTabIndex(index: number): number {
return Math.min(this.tabs.length - 1, Math.max(index || 0, 0)); return Math.min(this.tabs.length - 1, Math.max(index || 0, 0));
} }

View File

@@ -6,12 +6,13 @@ import {
Component, Component,
HostListener, HostListener,
Input, Input,
OnDestroy,
ViewChild, ViewChild,
input, input,
inject,
DestroyRef,
} from "@angular/core"; } from "@angular/core";
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
import { IsActiveMatchOptions, RouterLinkActive, RouterModule } from "@angular/router"; import { IsActiveMatchOptions, RouterLinkActive, RouterModule } from "@angular/router";
import { Subject, takeUntil } from "rxjs";
import { TabListItemDirective } from "../shared/tab-list-item.directive"; import { TabListItemDirective } from "../shared/tab-list-item.directive";
@@ -22,9 +23,8 @@ import { TabNavBarComponent } from "./tab-nav-bar.component";
templateUrl: "tab-link.component.html", templateUrl: "tab-link.component.html",
imports: [TabListItemDirective, RouterModule], imports: [TabListItemDirective, RouterModule],
}) })
export class TabLinkComponent implements FocusableOption, AfterViewInit, OnDestroy { export class TabLinkComponent implements FocusableOption, AfterViewInit {
private destroy$ = new Subject<void>(); private readonly destroyRef = inject(DestroyRef);
@ViewChild(TabListItemDirective) tabItem: TabListItemDirective; @ViewChild(TabListItemDirective) tabItem: TabListItemDirective;
@ViewChild("rla") routerLinkActive: RouterLinkActive; @ViewChild("rla") routerLinkActive: RouterLinkActive;
@@ -61,12 +61,7 @@ export class TabLinkComponent implements FocusableOption, AfterViewInit, OnDestr
// The active state of tab links are tracked via the routerLinkActive directive // The active state of tab links are tracked via the routerLinkActive directive
// We need to watch for changes to tell the parent nav group when the tab is active // We need to watch for changes to tell the parent nav group when the tab is active
this.routerLinkActive.isActiveChange this.routerLinkActive.isActiveChange
.pipe(takeUntil(this.destroy$)) .pipe(takeUntilDestroyed(this.destroyRef))
.subscribe((_) => this._tabNavBar.updateActiveLink()); .subscribe((_) => this._tabNavBar.updateActiveLink());
} }
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
} }