1
0
mirror of https://github.com/bitwarden/browser.git synced 2024-12-22 16:29:09 +01:00

feat: improve async actions docs and guards (#4215)

* docs: clarify submit handler in standalone example

* docs: clarify protection against re-running actions

* docs: clarify that these directives replace click and ngSubmit

* docs: clarify `void`

* feat: disable action directive on bitsubmit disable

* docs: fix grammar

* docs: change to note

* feat: guard against double running bitAction
This commit is contained in:
Andreas Coroiu 2022-12-14 08:47:01 +01:00 committed by GitHub
parent b16d7a6f6e
commit 3ffeb684a7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 38 additions and 24 deletions

View File

@ -18,6 +18,8 @@ export class BitActionDirective implements OnDestroy {
private destroy$ = new Subject<void>();
private _loading$ = new BehaviorSubject<boolean>(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;
}

View File

@ -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;
});
}
}

View File

@ -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
<form [formGroup]="formGroup" [bitSubmit]="submit">...</form>
```
@ -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
<button type="submit" bitButton bitFormButton>{{ "submit" | i18n }}</button>
```
@ -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
<form [formGroup]="formGroup" [bitSubmit]="submit">...</form>
@ -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
<button type="button" bitFormButton bitButton [bitAction]="handler">Do action</button>
<button type="button" bitFormButton bitIconButton="bwi-star" [bitAction]="handler"></button>

View File

@ -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
<button bitButton [bitAction]="handler">Do action</button>