mirror of
https://github.com/bitwarden/browser
synced 2025-12-17 00:33:44 +00:00
[EC-558] Reflecting async progress on buttons and forms (#3548)
* [EC-556] feat: convert button into component * [EC-556] feat: implement loading state * [EC-556] feat: remove loading from submit button * [EC-556] fix: add missing import * [EC-556] fix: disabling button using regular attribute * [EC-556] feat: implement bitFormButton * [EC-556] feat: use bitFormButton in submit button * [EC-556] fix: missing import * [EC-558] chore: rename file to match class name * [EC-558] feat: allow skipping bitButton on form buttons * [EC-558]: only show spinner on submit button * [EC-558] feat: add new bit async directive * [EC-558] feat: add functionToObservable util * [EC-558] feat: implement bitAction directive * [EC-558] refactor: simplify bitSubmit using functionToObservable * [EC-558] feat: connect bit action with form button * [EC-558] feat: execute function immediately to allow for form validation * [EC-558] feat: disable form on loading * [EC-558] chore: remove duplicate types * [EC-558] feat: move validation service to common * [EC-558] feat: add error handling using validation service * [EC-558] feat: add support for icon button * [EC-558] fix: icon button hover border styles * [EC-558] chore: refactor icon button story to show all styles * [EC-558] fix: better align loading spinner to middle * [EC-558] fix: simplify try catch * [EC-558] chore: reorganize async actions * [EC-558] chore: rename stories * [EC-558] docs: add documentation * [EC-558] feat: decouple buttons and form buttons * [EC-558] chore: rename button like abstraction * [EC-558] chore: remove null check * [EC-558] docs: add jsdocs to directives * [EC-558] fix: switch abs imports to relative * [EC-558] chore: add async actions module to web shared module * [EC-558] chore: remove unecessary null check * [EC-558] chore: apply suggestions from code review Co-authored-by: Oscar Hinton <Hinton@users.noreply.github.com> * [EC-558] fix: whitespaces * [EC-558] feat: dont disable form by default * [EC-558] fix: bug where form could be submit during a previous submit * [EC-558] feat: remove ability to disable form Co-authored-by: Oscar Hinton <Hinton@users.noreply.github.com>
This commit is contained in:
83
libs/components/src/async-actions/bit-submit.directive.ts
Normal file
83
libs/components/src/async-actions/bit-submit.directive.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
import { Directive, Input, OnDestroy, OnInit, Optional } from "@angular/core";
|
||||
import { FormGroupDirective } from "@angular/forms";
|
||||
import { BehaviorSubject, catchError, filter, of, Subject, switchMap, takeUntil } from "rxjs";
|
||||
|
||||
import { ValidationService } from "@bitwarden/common/abstractions/validation.service";
|
||||
|
||||
import { FunctionReturningAwaitable, functionToObservable } from "../utils/function-to-observable";
|
||||
|
||||
/**
|
||||
* Allow a form to perform async actions on submit, disabling the form while the action is processing.
|
||||
*/
|
||||
@Directive({
|
||||
selector: "[formGroup][bitSubmit]",
|
||||
})
|
||||
export class BitSubmitDirective implements OnInit, OnDestroy {
|
||||
private destroy$ = new Subject<void>();
|
||||
private _loading$ = new BehaviorSubject<boolean>(false);
|
||||
private _disabled$ = new BehaviorSubject<boolean>(false);
|
||||
|
||||
@Input("bitSubmit") protected handler: FunctionReturningAwaitable;
|
||||
@Input("disableFormOnLoading") protected disableFormOnLoading = false;
|
||||
|
||||
readonly loading$ = this._loading$.asObservable();
|
||||
readonly disabled$ = this._disabled$.asObservable();
|
||||
|
||||
constructor(
|
||||
private formGroupDirective: FormGroupDirective,
|
||||
@Optional() validationService?: ValidationService
|
||||
) {
|
||||
formGroupDirective.ngSubmit
|
||||
.pipe(
|
||||
filter(() => !this.disabled),
|
||||
switchMap(() => {
|
||||
// Calling functionToObservable exectues the sync part of the handler
|
||||
// allowing the function to check form validity before it gets disabled.
|
||||
const awaitable = functionToObservable(this.handler);
|
||||
|
||||
// Disable form
|
||||
this.loading = true;
|
||||
|
||||
return awaitable.pipe(
|
||||
catchError((err: unknown) => {
|
||||
validationService?.showError(err);
|
||||
return of(undefined);
|
||||
})
|
||||
);
|
||||
}),
|
||||
takeUntil(this.destroy$)
|
||||
)
|
||||
.subscribe({
|
||||
next: () => (this.loading = false),
|
||||
complete: () => (this.loading = false),
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.formGroupDirective.statusChanges
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe((c) => this._disabled$.next(c === "DISABLED"));
|
||||
}
|
||||
|
||||
get disabled() {
|
||||
return this._disabled$.value;
|
||||
}
|
||||
|
||||
set disabled(value: boolean) {
|
||||
this._disabled$.next(value);
|
||||
}
|
||||
|
||||
get loading() {
|
||||
return this._loading$.value;
|
||||
}
|
||||
|
||||
set loading(value: boolean) {
|
||||
this.disabled = value;
|
||||
this._loading$.next(value);
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user