Skip to content

Commit

Permalink
feat(form-plugin): allow ngxsFormDebounce to be string
Browse files Browse the repository at this point in the history
  • Loading branch information
arturovt committed Mar 2, 2023
1 parent c0b40ba commit 2887c2b
Show file tree
Hide file tree
Showing 8 changed files with 91 additions and 27 deletions.
4 changes: 3 additions & 1 deletion .prettierrc.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
{
"singleQuote": true,
"printWidth": 95,
"tabWidth": 2
"tabWidth": 2,
"arrowParens": "avoid",
"trailingComma": "none"
}
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
- Feature: Storage Plugin - Enable providing storage engine individually [#1935](https://github.com/ngxs/store/pull/1935)
- Feature: Devtools Plugin - Add new options to the `NgxsDevtoolsOptions` interface [#1879](https://github.com/ngxs/store/pull/1879)
- Feature: Devtools Plugin - Add trace options to `NgxsDevtoolsOptions` [#1968](https://github.com/ngxs/store/pull/1968)
- Featuer: Form Plugin - Allow `ngxsFormDebounce` to be string [#1972](https://github.com/ngxs/store/pull/1972)
- Performance: Tree-shake patch errors [#1955](https://github.com/ngxs/store/pull/1955)
- Fix: Get descriptor explicitly when it's considered as a class property [#1961](https://github.com/ngxs/store/pull/1961)

Expand Down
78 changes: 63 additions & 15 deletions docs/plugins/form.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ In a nutshell, this plugin helps to keep your forms and state in sync.
## Installation

```bash
npm install @ngxs/form-plugin --save
npm install @ngxs/form-plugin

# or if you are using yarn
yarn add @ngxs/form-plugin
Expand Down Expand Up @@ -104,7 +104,9 @@ Now anytime your form updates, your state will also reflect the new state.

The directive also has two inputs you can utilize as well:

- `ngxsFormDebounce: number` - Debounce the value changes to the form. Default value: `100`. Ignored if `updateOn` is `blur` or `submit`.
- `ngxsFormDebounce: number | string` - Debounce the value changes from the form. Default value: `100`. Ignored if:
- the provided value is less than `0` (for instance, `ngxsFormDebounce="-1"` is valid)
- `updateOn` is `blur` or `submit`
- `ngxsFormClearOnDestroy: boolean` - Clear the state on destroy of the form.

### Actions
Expand Down Expand Up @@ -164,7 +166,7 @@ The state contains information about the new novel name and its authors. Let's c

```ts
import { Component } from '@angular/core';
import { FormBuilder, FormGroup } from '@angular/forms';
import { FormBuilder } from '@angular/forms';

@Component({
selector: 'new-novel-form',
Expand All @@ -184,18 +186,16 @@ import { FormBuilder, FormGroup } from '@angular/forms';
`
})
export class NewNovelComponent {
newNovelForm: FormGroup;

constructor(private fb: FormBuilder) {
this.newNovelForm = this.fb.group({
novelName: 'Zenith',
authors: this.fb.array([
this.fb.group({
name: 'Sasha Alsberg'
})
])
});
}
newNovelForm = this.fb.group({
novelName: 'Zenith',
authors: this.fb.array([
this.fb.group({
name: 'Sasha Alsberg'
})
])
});

constructor(private fb: FormBuilder) {}

onSubmit() {
//
Expand All @@ -216,3 +216,51 @@ store.dispatch(
})
);
```

### Debouncing

The `ngxsFormDebounce` is used alongside `debounceTime` and pipes form's `valueChanges` and `statusChanges`. This implies that state updates are asynchronous by default. Suppose you dispatch the `UpdateFormValue`, which should patch the form value. In that case, you won't get the updated state immediately because the `debounceTime` is set to `100` by default. Given the following example:

```ts
interface NovelsStateModel {
newNovelForm: {
model?: {
novelName: string;
paperBound: boolean;
};
};
}

export class NovelsState {
@Action(SubmitNovelsForm)
submitNovelsForm(ctx: StateContext<NovelsStateModel>) {
console.log(ctx.getState().newNovelForm.model);

ctx.dispatch(
new UpdateFormValue({
value: { paperBound: true },
path: 'novels.newNovelForm'
})
);

console.log(ctx.getState().newNovelForm.model);
}
}
```

You may expect to see `{ paperBound: true, novelName: null }` being logged. Still, the second `console.log` will log `{ paperBound: true }`, pretending the `novelName` value is lost. You'll see the final update state if you wrap the second `console.log` into a `setTimeout`:

```ts
ctx.dispatch(
new UpdateFormValue({
value: { paperBound: true },
path: 'novels.newNovelForm'
})
);

setTimeout(() => {
console.log(ctx.getState().newNovelForm.model);
}, 100);
```

If you need to get state updates synchronously, you may want to set the `ngxsFormDebounce` to `-1`; this won't pipe value changes with `debounceTime`.
21 changes: 14 additions & 7 deletions packages/form-plugin/src/directive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,21 +18,28 @@ export class FormDirective implements OnInit, OnDestroy {
path: string = null!;

@Input('ngxsFormDebounce')
debounce = 100;
set debounce(debounce: string | number | null | undefined) {
if (debounce != null) {
this._debounce = Number(debounce);
}
}
get debounce() {
return this._debounce;
}
private _debounce = 100;

@Input('ngxsFormClearOnDestroy')
set clearDestroy(val: boolean) {
this._clearDestroy = val != null && `${val}` !== 'false';
}

get clearDestroy(): boolean {
return this._clearDestroy;
}
private _clearDestroy = false;

_clearDestroy = false;
private _updating = false;

private readonly _destroy$ = new Subject<void>();
private _updating = false;

constructor(
private _actions$: Actions,
Expand Down Expand Up @@ -167,9 +174,9 @@ export class FormDirective implements OnInit, OnDestroy {
complete: () => (this._updating = false)
});
}

ngOnDestroy() {
this._destroy$.next();
this._destroy$.complete();

if (this.clearDestroy) {
this._store.dispatch(
Expand All @@ -186,12 +193,12 @@ export class FormDirective implements OnInit, OnDestroy {

private debounceChange() {
const skipDebounceTime =
this._formGroupDirective.control.updateOn !== 'change' || this.debounce < 0;
this._formGroupDirective.control.updateOn !== 'change' || this._debounce < 0;

return skipDebounceTime
? (change: Observable<any>) => change.pipe(takeUntil(this._destroy$))
: (change: Observable<any>) =>
change.pipe(debounceTime(this.debounce), takeUntil(this._destroy$));
change.pipe(debounceTime(this._debounce), takeUntil(this._destroy$));
}

private get form(): FormGroup {
Expand Down
6 changes: 4 additions & 2 deletions packages/form-plugin/tests/form.plugin.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ describe('NgxsFormPlugin', () => {
}
}
})
@Injectable()
class StudentState {
@Selector()
static getStudentForm(state: StudentStateModel): Form {
Expand Down Expand Up @@ -316,6 +317,7 @@ describe('NgxsFormPlugin', () => {
}
}
})
@Injectable()
class TodosState {}

TestBed.configureTestingModule({
Expand Down Expand Up @@ -612,7 +614,7 @@ describe('NgxsFormPlugin', () => {

@Component({
template: `
<form [formGroup]="form" ngxsForm="todos.todosForm" [ngxsFormDebounce]="-1">
<form [formGroup]="form" ngxsForm="todos.todosForm" ngxsFormDebounce="-1">
<input formControlName="text" /> <button type="submit">Add todo</button>
</form>
`
Expand Down Expand Up @@ -798,7 +800,7 @@ describe('NgxsFormPlugin', () => {

@Component({
template: `
<form [formGroup]="form" ngxsForm="todos.todosForm" [ngxsFormDebounce]="-1">
<form [formGroup]="form" ngxsForm="todos.todosForm" ngxsFormDebounce="-1">
<input formControlName="text" /> <button type="submit">Add todo</button>
</form>
`
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Injectable } from '@angular/core';
import { TestBed } from '@angular/core/testing';
import { NgxsModule, Selector, State, Store } from '@ngxs/store';

Expand All @@ -21,6 +22,7 @@ describe('UpdateFormValue with primitives (https://github.com/ngxs/store/issues/
}
}
})
@Injectable()
class PizzaState {
@Selector()
static getModel(state: PizzaStateModel) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { TestBed } from '@angular/core/testing';
import { Component } from '@angular/core';
import { Component, Injectable } from '@angular/core';
import { FormGroup, FormControl, ReactiveFormsModule } from '@angular/forms';
import { NgxsModule, State, Actions, ofActionDispatched, Store, Selector } from '@ngxs/store';

Expand All @@ -26,6 +26,7 @@ describe('Multiple `ngxsForm` bindings (https://github.com/ngxs/store/issues/182
}
}
})
@Injectable()
class UserState {
@Selector()
static getModel(state: UserStateModel) {
Expand Down
3 changes: 2 additions & 1 deletion packages/form-plugin/tests/property-path.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { TestBed } from '@angular/core/testing';
import { Component } from '@angular/core';
import { Component, Injectable } from '@angular/core';
import { By } from '@angular/platform-browser';
import { FormGroup, FormControl, FormArray, ReactiveFormsModule } from '@angular/forms';
import { State, NgxsModule, Store, Selector } from '@ngxs/store';
Expand Down Expand Up @@ -29,6 +29,7 @@ describe('UpdateFormValue.propertyPath', () => {
}
}
})
@Injectable()
class NovelsState {
@Selector()
static model(state: NovelsStateModel) {
Expand Down

0 comments on commit 2887c2b

Please sign in to comment.