Skip to content

Commit 43ba4ab

Browse files
dylhunnAndrewKushnir
authored andcommitted
fix(forms): Allow NonNullableFormBuilder to be injected. (angular#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 angular#45904
1 parent 216a966 commit 43ba4ab

File tree

3 files changed

+74
-11
lines changed

3 files changed

+74
-11
lines changed

goldens/public-api/forms/index.md

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -706,12 +706,16 @@ export class NgSelectOption implements OnDestroy {
706706
}
707707

708708
// @public
709-
export interface NonNullableFormBuilder {
710-
array<T>(controls: Array<T>, validatorOrOpts?: ValidatorFn | ValidatorFn[] | AbstractControlOptions | null, asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[] | null): FormArrayElement<T, never>>;
711-
control<T>(formState: T | FormControlState<T>, validatorOrOpts?: ValidatorFn | ValidatorFn[] | AbstractControlOptions | null, asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[] | null): FormControl<T>;
712-
group<T extends {}>(controls: T, options?: AbstractControlOptions | null): FormGroup<{
709+
export abstract class NonNullableFormBuilder {
710+
abstract array<T>(controls: Array<T>, validatorOrOpts?: ValidatorFn | ValidatorFn[] | AbstractControlOptions | null, asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[] | null): FormArrayElement<T, never>>;
711+
abstract control<T>(formState: T | FormControlState<T>, validatorOrOpts?: ValidatorFn | ValidatorFn[] | AbstractControlOptions | null, asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[] | null): FormControl<T>;
712+
abstract group<T extends {}>(controls: T, options?: AbstractControlOptions | null): FormGroup<{
713713
[K in keyof T]: ɵElement<T[K], never>;
714714
}>;
715+
// (undocumented)
716+
static ɵfac: i0.ɵɵFactoryDeclaration<NonNullableFormBuilder, never>;
717+
// (undocumented)
718+
static ɵprov: i0.ɵɵInjectableDeclaration<NonNullableFormBuilder>;
715719
}
716720

717721
// @public

packages/forms/src/form_builder.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
import {Injectable} from '@angular/core';
9+
import {inject, Injectable, InjectionToken} from '@angular/core';
1010

1111
import {AsyncValidatorFn, ValidatorFn} from './directives/validators';
1212
import {ReactiveFormsModule} from './form_providers';
@@ -315,13 +315,17 @@ export class FormBuilder {
315315
*
316316
* @publicApi
317317
*/
318-
export interface NonNullableFormBuilder {
318+
@Injectable({
319+
providedIn: ReactiveFormsModule,
320+
useFactory: () => inject(FormBuilder).nonNullable,
321+
})
322+
export abstract class NonNullableFormBuilder {
319323
/**
320324
* Similar to {@see FormBuilder#group}, except any implicitly constructed `FormControl`
321325
* will be non-nullable (i.e. it will have `initialValueIsDefault` set to true). Note
322326
* that already-constructed controls will not be altered.
323327
*/
324-
group<T extends {}>(
328+
abstract group<T extends {}>(
325329
controls: T,
326330
options?: AbstractControlOptions|null,
327331
): FormGroup<{[K in keyof T]: ɵElement<T[K], never>}>;
@@ -331,15 +335,15 @@ export interface NonNullableFormBuilder {
331335
* will be non-nullable (i.e. it will have `initialValueIsDefault` set to true). Note
332336
* that already-constructed controls will not be altered.
333337
*/
334-
array<T>(
338+
abstract array<T>(
335339
controls: Array<T>, validatorOrOpts?: ValidatorFn|ValidatorFn[]|AbstractControlOptions|null,
336340
asyncValidator?: AsyncValidatorFn|AsyncValidatorFn[]|null): FormArray<ɵElement<T, never>>;
337341

338342
/**
339343
* Similar to {@see FormBuilder#control}, except this overridden version of `control` forces
340344
* `initialValueIsDefault` to be `true`, resulting in the control always being non-nullable.
341345
*/
342-
control<T>(
346+
abstract control<T>(
343347
formState: T|FormControlState<T>,
344348
validatorOrOpts?: ValidatorFn|ValidatorFn[]|AbstractControlOptions|null,
345349
asyncValidator?: AsyncValidatorFn|AsyncValidatorFn[]|null): FormControl<T>;

packages/forms/test/form_builder_spec.ts

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@
55
* Use of this source code is governed by an MIT-style license that can be
66
* found in the LICENSE file at https://angular.io/license
77
*/
8-
import {fakeAsync, tick} from '@angular/core/testing';
9-
import {FormBuilder, UntypedFormBuilder, Validators} from '@angular/forms';
8+
import {Component} from '@angular/core';
9+
import {fakeAsync, TestBed, tick} from '@angular/core/testing';
10+
import {FormBuilder, NonNullableFormBuilder, ReactiveFormsModule, UntypedFormBuilder, Validators} from '@angular/forms';
1011
import {of} from 'rxjs';
1112

1213
(function() {
@@ -197,6 +198,60 @@ describe('Form Builder', () => {
197198
expect(a.errors).toEqual({'sync1': true, 'sync2': true});
198199
});
199200

201+
it('should be injectable', () => {
202+
@Component({
203+
standalone: true,
204+
template: '...',
205+
})
206+
class MyComp {
207+
constructor(public fb: FormBuilder) {}
208+
}
209+
210+
TestBed.configureTestingModule({imports: [ReactiveFormsModule]});
211+
const fixture = TestBed.createComponent(MyComp);
212+
213+
fixture.detectChanges();
214+
expect(fixture.componentInstance.fb).toBeInstanceOf(FormBuilder);
215+
216+
const fc = fixture.componentInstance.fb.control('foo');
217+
{
218+
// Check the type of the value by assigning in each direction
219+
type ValueType = string|null;
220+
let t: ValueType = fc.value;
221+
let t1 = fc.value;
222+
t1 = null as unknown as ValueType;
223+
}
224+
fc.reset();
225+
expect(fc.value).toEqual(null);
226+
});
227+
228+
it('should be injectable as NonNullableFormBuilder', () => {
229+
@Component({
230+
standalone: true,
231+
template: '...',
232+
})
233+
class MyComp {
234+
constructor(public fb: NonNullableFormBuilder) {}
235+
}
236+
237+
TestBed.configureTestingModule({imports: [ReactiveFormsModule]});
238+
239+
const fixture = TestBed.createComponent(MyComp);
240+
fixture.detectChanges();
241+
expect(fixture.componentInstance.fb).toBeInstanceOf(FormBuilder);
242+
243+
const fc = fixture.componentInstance.fb.control('foo');
244+
{
245+
// Check the type of the value by assigning in each direction
246+
type ValueType = string;
247+
let t: ValueType = fc.value;
248+
let t1 = fc.value;
249+
t1 = null as unknown as ValueType;
250+
}
251+
fc.reset();
252+
expect(fc.value).toEqual('foo');
253+
});
254+
200255
describe('updateOn', () => {
201256
it('should default to on change', () => {
202257
const c = b.control('');

0 commit comments

Comments
 (0)