I'm working with an angular app where I need to dynamically add/remove form controls (checkboxes) in a form record.
I created a minimal version of this behaviour on stackblitz, the important stuff is in the code below.
private formBuilder = inject(NonNullableFormBuilder);
form = this.formBuilder.record<boolean>({});
nestedForm = this.formBuilder.group({
field1: [``],
subRecord: this.formBuilder.record<boolean>({}),
});
// This works without problem
removeRandomFormControls() {
const record = this.form;
Object.keys(record.controls).forEach((key) => {
if (Math.random() > 0.5) {
return;
}
record.removeControl(key, { emitEvent: false });
});
}
// This is the exact same thing, but in a nested FormRecord. This gives an error.
removeRandomNestedFormControls() {
const subRecord = this.nestedForm.controls.subRecord;
Object.keys(subRecord.controls).forEach((key) => {
if (Math.random() > 0.5) {
return;
}
// The error occurs on the following line
subRecord.removeControl(key, { emitEvent: false });
});
}
The error that I get is
No overload matches this call.
Overload 1 of 2, '(this: FormGroup<{ [key: string]: AbstractControl<any, any>; }>, name: string, options?: { emitEvent?: boolean | undefined; } | undefined): void', gave the following error.
The 'this' context of type 'FormGroup<{ [key: string]: FormControl<boolean>; }>' is not assignable to method's 'this' of type 'FormGroup<{ [key: string]: AbstractControl<any, any>; }>'.
Type '{ [key: string]: AbstractControl<any, any>; }' is not assignable to type '{ [key: string]: FormControl<boolean>; }'.
'string' index signatures are incompatible.
Type 'AbstractControl<any, any>' is missing the following properties from type 'FormControl<boolean>': defaultValue, registerOnChange, registerOnDisabledChange
Overload 2 of 2, '(name: never, options?: { emitEvent?: boolean | undefined; } | undefined): void', gave the following error.
Argument of type 'string' is not assignable to parameter of type 'never'.
The interesting things I see are that I can use addControl()
without issues. Another interesting thing (perhaps what causes this error) is that formBuilder seems to change the type of the nested FormRecord to FormGroup. Meaning that subRecord
has the type FormRecord<FormControl<boolean>>
, whereas the whole nestedForm
has the type
FormGroup<{
field1: FormControl<string>;
subRecord: FormGroup<{ // <-- not FormRecord??
[key: string]: FormControl<boolean>;
}>;
}>
Is this a bug in Angular Forms, or am I missing something obvious? How is this supposed to be done?
There is an inference issue/bug with FormBuild.group
with doesn't return subRecord
as FormRecord
nestedForm = this.formBuilder.group({
field1: [``],
subRecord: this.formBuilder.record<boolean>({}),
});
It's inferred as
FormGroup<{
field1: FormControl<string>;
subRecord: FormGroup<{
[key: string]: FormControl<boolean>;
}>;
}>
The easiest fix is to specify the return type of subRecord
const subRecord: FormRecord<FormControl<boolean>> = this.nestedForm.controls.subRecord;