diff --git a/libs/components/src/async-actions/bit-action.directive.ts b/libs/components/src/async-actions/bit-action.directive.ts index 0ea479f190..640063971b 100644 --- a/libs/components/src/async-actions/bit-action.directive.ts +++ b/libs/components/src/async-actions/bit-action.directive.ts @@ -18,6 +18,8 @@ export class BitActionDirective implements OnDestroy { private destroy$ = new Subject(); private _loading$ = new BehaviorSubject(false); + disabled = false; + @Input("bitAction") protected handler: FunctionReturningAwaitable; readonly loading$ = this._loading$.asObservable(); @@ -39,7 +41,7 @@ export class BitActionDirective implements OnDestroy { @HostListener("click") protected async onClick() { - if (!this.handler) { + if (!this.handler || this.loading || this.disabled || this.buttonComponent.disabled) { return; } diff --git a/libs/components/src/async-actions/form-button.directive.ts b/libs/components/src/async-actions/form-button.directive.ts index 20ca289f7b..b1b5a778c1 100644 --- a/libs/components/src/async-actions/form-button.directive.ts +++ b/libs/components/src/async-actions/form-button.directive.ts @@ -14,8 +14,10 @@ import { BitActionDirective } from "."; * - Activates the button loading effect while the form is processing an async submit action. * - Disables the button while a `bitAction` directive on another button is being processed. * - * When attached to a standalone button with `bitAction` directive: - * - Disables the form while the `bitAction` directive is processing an async submit action. + * When attached to a button with `bitAction` directive inside of a form: + * - Disables the button while the `bitSubmit` directive is processing an async submit action. + * - Disables the button while a `bitAction` directive on another button is being processed. + * - Disables form submission while the `bitAction` directive is processing an async action. */ @Directive({ selector: "button[bitFormButton]", @@ -48,6 +50,10 @@ export class BitFormButtonDirective implements OnDestroy { actionDirective.loading$.pipe(takeUntil(this.destroy$)).subscribe((disabled) => { submitDirective.disabled = disabled; }); + + submitDirective.disabled$.pipe(takeUntil(this.destroy$)).subscribe((disabled) => { + actionDirective.disabled = disabled; + }); } } diff --git a/libs/components/src/async-actions/in-forms.stories.mdx b/libs/components/src/async-actions/in-forms.stories.mdx index 75bedda8eb..6a07fcda6e 100644 --- a/libs/components/src/async-actions/in-forms.stories.mdx +++ b/libs/components/src/async-actions/in-forms.stories.mdx @@ -16,14 +16,16 @@ Adding async actions to submit buttons requires the following 3 steps ### 1. Add a handler to your `Component` A handler is a function that returns a promise or an observable. Functions that return `void` are also supported which is -useful for aborting an action. +useful because `return;` can be used to abort an action. -**NOTE:** +**NOTE:** Defining the handlers as arrow-functions assigned to variables is mandatory if the handler needs access to the parent +component using the variable `this`. -- Defining the handlers as arrow-functions assigned to variables is mandatory if the handler needs access to the parent - component using the variable `this`. -- `formGroup.invalid` will always return `true` after the first `await` operation, event if the form is not actually - invalid. This is due to the form getting disabled by the `bitSubmit` directive while waiting for the async action to complete. +**NOTE:** `formGroup.invalid` will always return `true` after the first `await` operation, event if the form is not actually +invalid. This is due to the form getting disabled by the `bitSubmit` directive while waiting for the async action to complete. + +**NOTE:** Handlers do not need to check if any previous requests have finished because the directives have built in protection against +users attempting to trigger new actions before the previous ones have finished. ```ts @Component({...}) @@ -52,6 +54,8 @@ Add the `bitSubmit` directive and supply the handler defined in step 1. **NOTE:** The `directive` is defined using the input syntax: `[input]="handler"`. This is different from how submit handlers are usually defined with the output syntax `(ngSubmit)="handler()"`. +**NOTE:** `[bitSubmit]` is used instead of `(ngSubmit)`. Using both is not supported. + ```html
...
``` @@ -60,6 +64,8 @@ This is different from how submit handlers are usually defined with the output s Add both `bitButton` and `bitFormButton` directives to the button. +**NOTE:** A summary of what each directive does can be found inside the source code. + ```html ``` @@ -76,21 +82,22 @@ useful for aborting an action. **NOTE:** Defining the handlers as arrow-functions assigned to variables is mandatory if the handler needs access to the parent component using the variable `this`. +**NOTE:** Handlers do not need to check if any previous requests have finished because the directives have built in protection against +users attempting to trigger new actions before the previous ones have finished. + ```ts @Component({...}) class Component { formGroup = this.formBuilder.group({...}); submit = async () => { - // not relevant for this example + // contents of this handler are not relevant for this example + // as this handler will not be trigger by standalone buttons using + // `bitAction` } // action can also return Observable instead of Promise handler = async () => { - if (/* perform guard check */) { - return; - } - await this.apiService.post(/* ... */); }; } @@ -98,7 +105,7 @@ class Component { ### 2. Add directive to the `form` element -The `bitSubmit` directive is required beacuse of its coordinating role. +The `bitSubmit` directive is required beacuse of its coordinating role inside of a form. ```html
...
@@ -108,6 +115,8 @@ The `bitSubmit` directive is required beacuse of its coordinating role. Add `bitButton`, `bitFormButton`, `bitAction` directives to the button. Make sure to supply a handler. +**NOTE:** A summary of what each directive does can be found inside the source code. + ```html diff --git a/libs/components/src/async-actions/standalone.stories.mdx b/libs/components/src/async-actions/standalone.stories.mdx index 7ed5c46ffd..9ff5753388 100644 --- a/libs/components/src/async-actions/standalone.stories.mdx +++ b/libs/components/src/async-actions/standalone.stories.mdx @@ -14,21 +14,20 @@ Adding async actions to standalone buttons requires the following 2 steps ### 1. Add a handler to your `Component` A handler is a function that returns a promise or an observable. Functions that return `void` are also supported which is -useful for aborting an action. +useful because `return;` can be used to abort an action. **NOTE:** Defining the handlers as arrow-functions assigned to variables is mandatory if the handler needs access to the parent component using the variable `this`. +**NOTE:** Handlers do not need to check if any previous requests have finished because the directives have built in protection against +users attempting to trigger new actions before the previous ones have finished. + #### Example using promises ```ts @Component({...}) class PromiseExampleComponent { handler = async () => { - if (/* perform guard check */) { - return; - } - await this.apiService.post(/* ... */); }; } @@ -40,10 +39,6 @@ class PromiseExampleComponent { @Component({...}) class Component { handler = () => { - if (/* perform guard check */) { - return; - } - return this.apiService.post$(/* ... */); }; } @@ -56,6 +51,8 @@ Add the `bitAction` directive and supply the handler defined in step 1. **NOTE:** The `directive` is defined using the input syntax: `[input]="handler"`. This is different from how click handlers are usually defined with the output syntax `(click)="handler()"`. +**NOTE:** `[bitAction]` is used instead of `(click)`. Using both is not supported. + ```html