I have a reactive Angular 11 form which builds additional controls dynamically based on selection. These controls are all added within the same FormGroup inside the parent form and are required, but the parent form stays invalid even if I fill every field.
The template looks like this:
<form [formGroup]="emailForm" (ngSubmit)="onSubmit()">
<div class="template">
<label for="template">Template</label>
<select formControlName="template" id="template" required>
<option *ngFor="let template of templateNames" value="{{template}}">{{template}}</option>
</select>
</div>
<div class="language">
<label for="language">Language</label>
<select formControlName="language" id="language" required>
<option *ngFor="let language of templateLanguages" value="{{language}}">{{language}}</option>
</select>
</div>
<div class="placeholders" formGroupName="placeholders">
<p>Placeholders</p>
<div *ngFor="let placeholder of templatePlaceholders" class="placeholders__placeholder">
<label for="{{placeholder}}-field">{{placeholder}}</label>
<input id="{{placeholder}}-field" required type="text">
</div>
</div>
<div class="submit">
<input type="submit" value="Send" [disabled]="!emailForm.valid">
</div>
</form>
And this is the component (with most behaviour stripped to keep the example minimal and reproducible):
public emailForm!: FormGroup;
public templateNames = ["one", "two", "three"];
public templateLanguages = ["en", "pt", "de"];
public templatePlaceholders: string[] = [];
ngOnInit(): void {
this.emailForm = new FormGroup({
template: new FormControl(),
language: new FormControl(),
placeholders: new FormGroup({}),
}, Validators.required);
this.populatePlaceholders();
}
populatePlaceholders(): void {
const placeholders: string[] = [];
// Some API call...
// ...
const placeholderFields: Record<string, FormControl> = {};
placeholders.forEach((placeholder) => {
placeholderFields[placeholder] = new FormControl("", Validators.required);
this.templatePlaceholders.push(placeholder);
});
this.emailForm.setControl("placeholders", new FormGroup(placeholderFields));
}
The problem is that even if I fill every field, and even if there is no placeholder field at all, the submit button stays disabled. I've tried logging the status of emailForm.placoholders
on valueChanges
and I can see that it remains unchanged (the value doesn't show up in its controls if I input anything in the fields), and the valueChanges
event isn't even triggered if I input anything in the child fields, only when it's in one of the first-level controls.
I'm quite new to Angular so I must be doing something wrong, but what?
EDIT: a stackblitz of the code: https://stackblitz.com/edit/angular-ivy-mdzjn8?file=src/app/app.component.ts
I found the issue: I forgot to add the formControlName
attribute to the control in the template. I added formControlName="{{placeholder}}"
and the problem was solved.
I wish there was an explicit error for forgettable things like this one; it's really hard to debug.