From 43ba4ab9dada084cfde5044f19dcc1810690fab0 Mon Sep 17 00:00:00 2001 From: Dylan Hunn Date: Thu, 5 May 2022 15:56:24 -0700 Subject: [PATCH] fix(forms): Allow NonNullableFormBuilder to be injected. (#45904) Based on early feedback, calling `fb.nonNullable.group(...)` continues to be clunky for a form with many such groups. Allowing `NonNullableFormBuilder` to be directly injected enables the following: ``` constructor(private fb: NonNullableFormBuilder) {} ``` PR Close #45904 --- goldens/public-api/forms/index.md | 12 +++-- packages/forms/src/form_builder.ts | 14 ++++-- packages/forms/test/form_builder_spec.ts | 59 +++++++++++++++++++++++- 3 files changed, 74 insertions(+), 11 deletions(-) diff --git a/goldens/public-api/forms/index.md b/goldens/public-api/forms/index.md index 2bbed766850f8..d65d558967b89 100644 --- a/goldens/public-api/forms/index.md +++ b/goldens/public-api/forms/index.md @@ -706,12 +706,16 @@ export class NgSelectOption implements OnDestroy { } // @public -export interface NonNullableFormBuilder { - array(controls: Array, validatorOrOpts?: ValidatorFn | ValidatorFn[] | AbstractControlOptions | null, asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[] | null): FormArray<ɵElement>; - control(formState: T | FormControlState, validatorOrOpts?: ValidatorFn | ValidatorFn[] | AbstractControlOptions | null, asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[] | null): FormControl; - group(controls: T, options?: AbstractControlOptions | null): FormGroup<{ +export abstract class NonNullableFormBuilder { + abstract array(controls: Array, validatorOrOpts?: ValidatorFn | ValidatorFn[] | AbstractControlOptions | null, asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[] | null): FormArray<ɵElement>; + abstract control(formState: T | FormControlState, validatorOrOpts?: ValidatorFn | ValidatorFn[] | AbstractControlOptions | null, asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[] | null): FormControl; + abstract group(controls: T, options?: AbstractControlOptions | null): FormGroup<{ [K in keyof T]: ɵElement; }>; + // (undocumented) + static ɵfac: i0.ɵɵFactoryDeclaration; + // (undocumented) + static ɵprov: i0.ɵɵInjectableDeclaration; } // @public diff --git a/packages/forms/src/form_builder.ts b/packages/forms/src/form_builder.ts index 0e86caf9bb48b..306b3e925a691 100644 --- a/packages/forms/src/form_builder.ts +++ b/packages/forms/src/form_builder.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {Injectable} from '@angular/core'; +import {inject, Injectable, InjectionToken} from '@angular/core'; import {AsyncValidatorFn, ValidatorFn} from './directives/validators'; import {ReactiveFormsModule} from './form_providers'; @@ -315,13 +315,17 @@ export class FormBuilder { * * @publicApi */ -export interface NonNullableFormBuilder { +@Injectable({ + providedIn: ReactiveFormsModule, + useFactory: () => inject(FormBuilder).nonNullable, +}) +export abstract class NonNullableFormBuilder { /** * Similar to {@see FormBuilder#group}, except any implicitly constructed `FormControl` * will be non-nullable (i.e. it will have `initialValueIsDefault` set to true). Note * that already-constructed controls will not be altered. */ - group( + abstract group( controls: T, options?: AbstractControlOptions|null, ): FormGroup<{[K in keyof T]: ɵElement}>; @@ -331,7 +335,7 @@ export interface NonNullableFormBuilder { * will be non-nullable (i.e. it will have `initialValueIsDefault` set to true). Note * that already-constructed controls will not be altered. */ - array( + abstract array( controls: Array, validatorOrOpts?: ValidatorFn|ValidatorFn[]|AbstractControlOptions|null, asyncValidator?: AsyncValidatorFn|AsyncValidatorFn[]|null): FormArray<ɵElement>; @@ -339,7 +343,7 @@ export interface NonNullableFormBuilder { * Similar to {@see FormBuilder#control}, except this overridden version of `control` forces * `initialValueIsDefault` to be `true`, resulting in the control always being non-nullable. */ - control( + abstract control( formState: T|FormControlState, validatorOrOpts?: ValidatorFn|ValidatorFn[]|AbstractControlOptions|null, asyncValidator?: AsyncValidatorFn|AsyncValidatorFn[]|null): FormControl; diff --git a/packages/forms/test/form_builder_spec.ts b/packages/forms/test/form_builder_spec.ts index e64364e541211..386b91285df58 100644 --- a/packages/forms/test/form_builder_spec.ts +++ b/packages/forms/test/form_builder_spec.ts @@ -5,8 +5,9 @@ * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -import {fakeAsync, tick} from '@angular/core/testing'; -import {FormBuilder, UntypedFormBuilder, Validators} from '@angular/forms'; +import {Component} from '@angular/core'; +import {fakeAsync, TestBed, tick} from '@angular/core/testing'; +import {FormBuilder, NonNullableFormBuilder, ReactiveFormsModule, UntypedFormBuilder, Validators} from '@angular/forms'; import {of} from 'rxjs'; (function() { @@ -197,6 +198,60 @@ describe('Form Builder', () => { expect(a.errors).toEqual({'sync1': true, 'sync2': true}); }); + it('should be injectable', () => { + @Component({ + standalone: true, + template: '...', + }) + class MyComp { + constructor(public fb: FormBuilder) {} + } + + TestBed.configureTestingModule({imports: [ReactiveFormsModule]}); + const fixture = TestBed.createComponent(MyComp); + + fixture.detectChanges(); + expect(fixture.componentInstance.fb).toBeInstanceOf(FormBuilder); + + const fc = fixture.componentInstance.fb.control('foo'); + { + // Check the type of the value by assigning in each direction + type ValueType = string|null; + let t: ValueType = fc.value; + let t1 = fc.value; + t1 = null as unknown as ValueType; + } + fc.reset(); + expect(fc.value).toEqual(null); + }); + + it('should be injectable as NonNullableFormBuilder', () => { + @Component({ + standalone: true, + template: '...', + }) + class MyComp { + constructor(public fb: NonNullableFormBuilder) {} + } + + TestBed.configureTestingModule({imports: [ReactiveFormsModule]}); + + const fixture = TestBed.createComponent(MyComp); + fixture.detectChanges(); + expect(fixture.componentInstance.fb).toBeInstanceOf(FormBuilder); + + const fc = fixture.componentInstance.fb.control('foo'); + { + // Check the type of the value by assigning in each direction + type ValueType = string; + let t: ValueType = fc.value; + let t1 = fc.value; + t1 = null as unknown as ValueType; + } + fc.reset(); + expect(fc.value).toEqual('foo'); + }); + describe('updateOn', () => { it('should default to on change', () => { const c = b.control('');