As you say, Angular does not "automatically validate" an input, if you don't use setValue or change the input. So the simpler solution is to force this validation when you change the checkbox.
<input type="check" formControlName="emailRequired" (input)="form.controls.emailRecipients.updateValueAndValidity()">
Update Based on recommendation from @MoxxiManagarm, It would be better if we can define our formGroup and without needing to take into account when, we need subscribe to valueChanges or use (input).
We can define a "validator" like
export function checkControl(controlName:string) { return (x:AbstractControl) => { const control=x?.parent?.get(controlName); if (control) control.updateValueAndValidity() return null; } }
This allows us to write,
form = new FormGroup({ emailRequired: new FormControl(false,checkControl('emailRecipients')), emailRecipients: new FormControl('',requireIf('emailRequired')) });
Then we simply,
<form [formGroup]="form"> <input type="checkbox" formControlName="emailRequired"> <input formControlName="emailRecipients"> {{form.get('emailRecipients')?.errors|json}} </form>
NOTE: the validator "requiredIf" like
export function requireIf(controlName:string) { return (x:AbstractControl) => { if (x?.parent?.get(controlName)?.value && !x.value) { return { required: true } }; return null; } }
See that, when we change the "checkbox", the formControl form.controls.emailRecipiens are updateAndValidity.
Well, this "validators" makes the typical repeat password validators problem more "robust".
export function equalsTo(controlName:string) { return (x:AbstractControl) => { const control=x?.parent?.get(controlName); if (control && control.value && control.value!=x.value) { return { notMatch: true } }; return null; } }
The form
form2=new FormGroup({ password:new FormControl('',checkControl('repeatPassword')), repeatPassword:new FormControl('',equalsTo('password')) })
A stackblitz
Update 2 Improving the checkControl to allow more that one control
export function checkControls(...controlNames:string[]) { return (x: AbstractControl) => { controlNames.forEach((name:string)=>{ const control = x?.parent?.get(name); if (control) control.updateValueAndValidity(); }) return null; }; }
This allows us to use something like,
emailRequired: new FormControl(false, checkControls('emailRecipient1','emailRecipient2')),