From e9e804fb029d7a38957eec864930ad3c94a453d5 Mon Sep 17 00:00:00 2001 From: Lars Gyrup Brink Nielsen Date: Wed, 20 Jun 2018 19:21:46 +0200 Subject: [PATCH] feat(forms): add updateOn option to FormBuilder (#24599) PR Close #24599 --- .../ts/formBuilder/form_builder_example.ts | 20 ++--- packages/forms/src/form_builder.ts | 66 +++++++++------ packages/forms/test/form_builder_spec.ts | 81 ++++++++++++++++++- tools/public_api_guard/forms/forms.d.ts | 6 +- 4 files changed, 137 insertions(+), 36 deletions(-) diff --git a/packages/examples/forms/ts/formBuilder/form_builder_example.ts b/packages/examples/forms/ts/formBuilder/form_builder_example.ts index e6fbd9c3eb..c71f8f0816 100644 --- a/packages/examples/forms/ts/formBuilder/form_builder_example.ts +++ b/packages/examples/forms/ts/formBuilder/form_builder_example.ts @@ -22,7 +22,7 @@ import {FormBuilder, FormControl, FormGroup, Validators} from '@angular/forms'; - +

Value: {{ form.value | json }}

Validation status: {{ form.status }}

` @@ -31,13 +31,15 @@ export class FormBuilderComp { form: FormGroup; constructor(@Inject(FormBuilder) fb: FormBuilder) { - this.form = fb.group({ - name: fb.group({ - first: ['Nancy', Validators.minLength(2)], - last: 'Drew', - }), - email: '', - }); + this.form = fb.group( + { + name: fb.group({ + first: ['Nancy', Validators.minLength(2)], + last: 'Drew', + }), + email: '', + }, + {updateOn: 'change'}); } } // #enddocregion @@ -56,4 +58,4 @@ export class DisabledFormControlComponent { this.control = fb.control({value: 'my val', disabled: true}); } } -// #enddocregion disabled-control \ No newline at end of file +// #enddocregion disabled-control diff --git a/packages/forms/src/form_builder.ts b/packages/forms/src/form_builder.ts index dc4825c3f9..3e611c55d1 100644 --- a/packages/forms/src/form_builder.ts +++ b/packages/forms/src/form_builder.ts @@ -9,7 +9,7 @@ import {Injectable} from '@angular/core'; import {AsyncValidatorFn, ValidatorFn} from './directives/validators'; -import {AbstractControl, FormArray, FormControl, FormGroup} from './model'; +import {AbstractControl, AbstractControlOptions, FormArray, FormControl, FormGroup, FormHooks} from './model'; /** * @description @@ -37,25 +37,43 @@ export class FormBuilder { * * `asyncValidator`: A single async validator or array of async validator functions * */ - group(controlsConfig: {[key: string]: any}, extra: {[key: string]: any}|null = null): FormGroup { + group(controlsConfig: {[key: string]: any}, legacyOrOpts: {[key: string]: any}|null = null): + FormGroup { const controls = this._reduceControls(controlsConfig); - const validator: ValidatorFn = extra != null ? extra['validator'] : null; - const asyncValidator: AsyncValidatorFn = extra != null ? extra['asyncValidator'] : null; - return new FormGroup(controls, validator, asyncValidator); + + let validators: ValidatorFn|ValidatorFn[]|null = null; + let asyncValidators: AsyncValidatorFn|AsyncValidatorFn[]|null = null; + let updateOn: FormHooks|undefined = undefined; + + if (legacyOrOpts != null && + (legacyOrOpts.asyncValidator !== undefined || legacyOrOpts.validator !== undefined)) { + // `legacyOrOpts` are legacy form group options + validators = legacyOrOpts.validator != null ? legacyOrOpts.validator : null; + asyncValidators = legacyOrOpts.asyncValidator != null ? legacyOrOpts.asyncValidator : null; + } else if (legacyOrOpts != null) { + // `legacyOrOpts` are `AbstractControlOptions` + validators = legacyOrOpts.validators != null ? legacyOrOpts.validators : null; + asyncValidators = legacyOrOpts.asyncValidators != null ? legacyOrOpts.asyncValidators : null; + updateOn = legacyOrOpts.updateOn != null ? legacyOrOpts.updateOn : undefined; + } + + return new FormGroup(controls, {asyncValidators, updateOn, validators}); } /** * @description - * Construct a new `FormControl` instance. + * Construct a new `FormControl` with the given state, validators and options. * - * @param formState Initializes the control with an initial value, - * or an object that defines the initial value and disabled state. + * @param formState Initializes the control with an initial state value, or + * with an object that contains both a value and a disabled status. * - * @param validator A synchronous validator function, or an array of synchronous validator + * @param validatorOrOpts A synchronous validator function, or an array of + * such functions, or an `AbstractControlOptions` object that contains + * validation functions and a validation trigger. + * + * @param asyncValidator A single async validator or array of async validator * functions. * - * @param asyncValidator A single async validator or array of async validator functions - * * @usageNotes * * ### Initialize a control as disabled @@ -65,31 +83,33 @@ export class FormBuilder { * * - * */ control( - formState: any, validator?: ValidatorFn|ValidatorFn[]|null, + formState: any, validatorOrOpts?: ValidatorFn|ValidatorFn[]|AbstractControlOptions|null, asyncValidator?: AsyncValidatorFn|AsyncValidatorFn[]|null): FormControl { - return new FormControl(formState, validator, asyncValidator); + return new FormControl(formState, validatorOrOpts, asyncValidator); } /** - * @description - * Construct a new `FormArray` instance. + * Constructs a new `FormArray` from the given array of configurations, + * validators and options. * - * @param controlsConfig An array of child controls. The key for each child control is its index - * in the array. + * @param controlsConfig An array of child controls or control configs. Each + * child control is given an index when it is registered. * - * @param validator A synchronous validator function, or an array of synchronous validator + * @param validatorOrOpts A synchronous validator function, or an array of + * such functions, or an `AbstractControlOptions` object that contains + * validation functions and a validation trigger. + * + * @param asyncValidator A single async validator or array of async validator * functions. - * - * @param asyncValidator A single async validator or array of async validator functions */ array( - controlsConfig: any[], validator?: ValidatorFn|ValidatorFn[]|null, + controlsConfig: any[], + validatorOrOpts?: ValidatorFn|ValidatorFn[]|AbstractControlOptions|null, asyncValidator?: AsyncValidatorFn|AsyncValidatorFn[]|null): FormArray { const controls = controlsConfig.map(c => this._createControl(c)); - return new FormArray(controls, validator, asyncValidator); + return new FormArray(controls, validatorOrOpts, asyncValidator); } /** @internal */ diff --git a/packages/forms/test/form_builder_spec.ts b/packages/forms/test/form_builder_spec.ts index bf7596eade..e04cd74677 100644 --- a/packages/forms/test/form_builder_spec.ts +++ b/packages/forms/test/form_builder_spec.ts @@ -7,7 +7,7 @@ */ import {fakeAsync, tick} from '@angular/core/testing'; import {beforeEach, describe, expect, it} from '@angular/core/testing/src/testing_internal'; -import {FormBuilder} from '@angular/forms'; +import {FormBuilder, Validators} from '@angular/forms'; import {of } from 'rxjs'; (function() { @@ -119,5 +119,84 @@ import {of } from 'rxjs'; expect(a.value).toEqual(['one', 'two']); expect(a.errors).toEqual({'sync1': true, 'sync2': true}); }); + + describe('updateOn', () => { + it('should default to on change', () => { + const c = b.control(''); + expect(c.updateOn).toEqual('change'); + }); + + it('should default to on change with an options obj', () => { + const c = b.control('', {validators: Validators.required}); + expect(c.updateOn).toEqual('change'); + }); + + it('should set updateOn when updating on blur', () => { + const c = b.control('', {updateOn: 'blur'}); + expect(c.updateOn).toEqual('blur'); + }); + + describe('in groups and arrays', () => { + it('should default to group updateOn when not set in control', () => { + const g = b.group({one: b.control(''), two: b.control('')}, {updateOn: 'blur'}); + + expect(g.get('one') !.updateOn).toEqual('blur'); + expect(g.get('two') !.updateOn).toEqual('blur'); + }); + + it('should default to array updateOn when not set in control', () => { + const a = b.array([b.control(''), b.control('')], {updateOn: 'blur'}); + + expect(a.get([0]) !.updateOn).toEqual('blur'); + expect(a.get([1]) !.updateOn).toEqual('blur'); + }); + + it('should set updateOn with nested groups', () => { + const g = b.group( + { + group: b.group({one: b.control(''), two: b.control('')}), + }, + {updateOn: 'blur'}); + + expect(g.get('group.one') !.updateOn).toEqual('blur'); + expect(g.get('group.two') !.updateOn).toEqual('blur'); + expect(g.get('group') !.updateOn).toEqual('blur'); + }); + + it('should set updateOn with nested arrays', () => { + const g = b.group( + { + arr: b.array([b.control(''), b.control('')]), + }, + {updateOn: 'blur'}); + + expect(g.get(['arr', 0]) !.updateOn).toEqual('blur'); + expect(g.get(['arr', 1]) !.updateOn).toEqual('blur'); + expect(g.get('arr') !.updateOn).toEqual('blur'); + }); + + it('should allow control updateOn to override group updateOn', () => { + const g = b.group( + {one: b.control('', {updateOn: 'change'}), two: b.control('')}, {updateOn: 'blur'}); + + expect(g.get('one') !.updateOn).toEqual('change'); + expect(g.get('two') !.updateOn).toEqual('blur'); + }); + + it('should set updateOn with complex setup', () => { + const g = b.group({ + group: b.group( + {one: b.control('', {updateOn: 'change'}), two: b.control('')}, {updateOn: 'blur'}), + groupTwo: b.group({one: b.control('')}, {updateOn: 'submit'}), + three: b.control('') + }); + + expect(g.get('group.one') !.updateOn).toEqual('change'); + expect(g.get('group.two') !.updateOn).toEqual('blur'); + expect(g.get('groupTwo.one') !.updateOn).toEqual('submit'); + expect(g.get('three') !.updateOn).toEqual('change'); + }); + }); + }); }); })(); diff --git a/tools/public_api_guard/forms/forms.d.ts b/tools/public_api_guard/forms/forms.d.ts index 1406341355..8744ac8a96 100644 --- a/tools/public_api_guard/forms/forms.d.ts +++ b/tools/public_api_guard/forms/forms.d.ts @@ -201,11 +201,11 @@ export declare class FormArrayName extends ControlContainer implements OnInit, O } export declare class FormBuilder { - array(controlsConfig: any[], validator?: ValidatorFn | ValidatorFn[] | null, asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[] | null): FormArray; - control(formState: any, validator?: ValidatorFn | ValidatorFn[] | null, asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[] | null): FormControl; + array(controlsConfig: any[], validatorOrOpts?: ValidatorFn | ValidatorFn[] | AbstractControlOptions | null, asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[] | null): FormArray; + control(formState: any, validatorOrOpts?: ValidatorFn | ValidatorFn[] | AbstractControlOptions | null, asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[] | null): FormControl; group(controlsConfig: { [key: string]: any; - }, extra?: { + }, legacyOrOpts?: { [key: string]: any; } | null): FormGroup; }