Skip to content

Commit

Permalink
fix: updated DbxFormlyFormComponent to poll for touched changes
Browse files Browse the repository at this point in the history
- DbxFormlyFormComponent now polls for form touched changes and emits a new event, instead of waiting for the next changed event to occur
  • Loading branch information
dereekb committed Jun 28, 2022
1 parent b002f39 commit 51670e4
Show file tree
Hide file tree
Showing 6 changed files with 70 additions and 20 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Component } from '@angular/core';
import { provideFormlyContext, AbstractSyncFormlyFormDirective, nameField, dateTimeField, textField } from '@dereekb/dbx-form';
import { FormlyFieldConfig } from '@ngx-formly/core';

export interface DocActionFormExampleValue {
name: string;
date: Date;
}

@Component({
template: `
<dbx-formly></dbx-formly>
`,
selector: 'doc-action-form-example-form-two',
providers: [provideFormlyContext()]
})
export class DocActionFormExampleFormTwoComponent extends AbstractSyncFormlyFormDirective<DocActionFormExampleValue> {
readonly fields: FormlyFieldConfig[] = [textField({ key: 'name', label: 'Name', maxLength: 30, required: true, autocomplete: false })];
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,15 @@ <h4>formDisabledOnWorking</h4>
<doc-action-form-example-form dbxActionForm [dbxFormSource]="defaultValue$"></doc-action-form-example-form>
<dbx-button dbxActionButton text="Submit"></dbx-button>
</dbx-action-example-tools>

<h3>Other Examples</h3>
<p class="dbx-hint">This example was for pinning down an issue related to the form not being marked complete. Forms that are marked untouched are not considered complete yet, until a sufficient number of changes have been made on a field, or the field is untouched.</p>
<div dbxAction dbxActionEnforceModified [dbxActionHandler]="handleFormAction">
<doc-action-form-example-form-two dbxActionForm></doc-action-form-example-form-two>
<p></p>
<dbx-button [raised]="true" text="Create Side" dbxActionButton></dbx-button>
<dbx-error dbxActionError></dbx-error>
</div>
</doc-feature-example>
</doc-feature-layout>
</dbx-content-container>
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { DocActionFormExampleFormComponent } from './component/action.example.fo
import { DocActionMapComponent } from './container/map.component';
import { DocActionExamplePopoverComponent } from './component/action.example.popover.form.component';
import { DocActionExampleDialogComponent } from './component/action.example.dialog.component';
import { DocActionFormExampleFormTwoComponent } from './component/action.example.form.two.component';

@NgModule({
imports: [
Expand All @@ -25,6 +26,7 @@ import { DocActionExampleDialogComponent } from './component/action.example.dial
// component
DocActionExampleToolsComponent,
DocActionFormExampleFormComponent,
DocActionFormExampleFormTwoComponent,
DocActionExampleDialogComponent,
DocActionExamplePopoverComponent,
// container
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,8 @@ export class DbxActionFormDirective<T = object> implements OnInit, OnDestroy {
exhaustMap((value) => {
// Use both changes count and whether or not something was in the past to guage whether or not the item has been touched.
// Angular Form's untouched is whether or not focus has been lost but we can still receive value updates.
// More than a certain amount of updates implies that it is being typed into.
// More than a certain amount of updates implies that it is being typed into/used.
// 3 changes and 2 seconds are arbitrary values derived from guesses about any slow/late changes that may come from external directives for setup.
const isProbablyTouched = !event.untouched || ((event.changesCount ?? 0) > 3 && isPast(addSeconds(event.lastResetAt ?? new Date(), 2)));

let validatorObs: Observable<boolean>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
}

.dbx-form-repeat-array {
margin-bottom: 8px;

.dbx-form-repeat-array-field {
@include theming.elevation(1);

Expand Down
55 changes: 36 additions & 19 deletions packages/dbx-form/src/lib/formly/formly.form.component.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { Component, OnDestroy, OnInit } from '@angular/core';
import { FormGroup } from '@angular/forms';
import { FormlyFieldConfig, FormlyFormOptions } from '@ngx-formly/core';
import { distinctUntilChanged, map, throttleTime, startWith, BehaviorSubject, Observable, Subject, switchMap, shareReplay, of, scan } from 'rxjs';
import { distinctUntilChanged, map, throttleTime, startWith, BehaviorSubject, Observable, Subject, switchMap, shareReplay, of, scan, filter, timeout, timer, tap, first } from 'rxjs';
import { AbstractSubscriptionDirective } from '@dereekb/dbx-core';
import { DbxForm, DbxFormDisabledKey, DbxFormEvent, DbxFormState, DEFAULT_FORM_DISABLED_KEY, provideDbxMutableForm } from '../form/form';
import { DbxFormlyContext, DbxFormlyContextDelegate, DbxFormlyInitialize } from './formly.context';
import { cloneDeep } from 'lodash';
import { scanCount, switchMapMaybeObs, SubscriptionObject } from '@dereekb/rxjs';
import { scanCount, switchMapMaybeObs, SubscriptionObject, filterMaybe, tapLog } from '@dereekb/rxjs';
import { BooleanStringKeyArray, BooleanStringKeyArrayUtilityInstance, Maybe } from '@dereekb/util';

export interface DbxFormlyFormState {
Expand Down Expand Up @@ -79,26 +79,43 @@ export class DbxFormlyFormComponent<T> extends AbstractSubscriptionDirective imp
isFormDisabled: false
}
),
map(({ changesSinceLastResetCount, isFormValid, isFormDisabled }) => {
const isReset = changesSinceLastResetCount <= 1; // first emission after reset is the first value.
const complete = isFormValid;

const nextState: DbxFormEvent = {
isComplete: complete,
state: isReset ? DbxFormState.RESET : DbxFormState.USED,
status: this.form.status,
untouched: this.form.untouched,
pristine: this.form.pristine,
changesCount: changesSinceLastResetCount,
lastResetAt,
disabled: this.disabled,
isDisabled: isFormDisabled
};
switchMap(({ changesSinceLastResetCount, isFormValid, isFormDisabled }) => {
const nextState = () => {
const isReset = changesSinceLastResetCount <= 1; // first emission after reset is the first value.
const complete = isFormValid;

const nextState: DbxFormEvent = {
isComplete: complete,
state: isReset ? DbxFormState.RESET : DbxFormState.USED,
status: this.form.status,
untouched: this.form.untouched,
pristine: this.form.pristine,
changesCount: changesSinceLastResetCount,
lastResetAt,
disabled: this.disabled,
isDisabled: isFormDisabled
};

// console.log('Change: ', nextState);
return nextState;
};

return nextState;
const state = nextState();

if (isFormValid && this.form.untouched) {
return timer(150, 200).pipe(
// every 200 ms check if the form is now marked touched, then push a new state
filter(() => this.form.touched),
map(() => nextState()),
// only push the new state once
first(),
// send the first value immediately
startWith(state)
);
} else {
return of(state);
}
})
// tapLog('Change: ')
)
),
shareReplay(1)
Expand Down

0 comments on commit 51670e4

Please sign in to comment.