I've been creating reuseable subforms in Angular 8, and using the FormGroupDirective
to get a reference of the parent FormGroup
for the child is quite easy to accomplish, but I'm having a hard time figure out how to dynamically set the validators on the child form from the parent.
Using ngAfterViewInit
I can see the form controls of the child forms have been added to the parent, but I can't seem to update the default child form validators directly from the parent.
// PARENT COMPONENT
public ngAfterViewInit() {
// Form controls have been applied to the form group
console.log('ngAfterViewInit', this.form.value);
// BUT the validators of profile form aren't being set?
this.form.get('profile').get('description').setValidators([Validators.required]);
}
I created a StackBlitz of my example code. All the fields except the profile description is a required field and has a default value. So without any changes being made to the validators the form will pass validations on submission and output VALID in the console. In ngAfterViewInit
I set the description fields validator to be required, which should prevent the form from being submitted and output INVALID in the console, but it still submits and outputs VALID.
Is it possible to dynamically set the validators of a subform without passing them in via @Input
bindings?
Apparently, you just have to use updateValueAndValidity
on the form control that was changed.
this.form.get('profile').get('description').updateValueAndValidity();
So it looks like this in the end:
public ngAfterViewInit() {
// Form controls will now exist
console.log('ngAfterViewInit', this.form.value);
// BUT the validators of the profile form should not allow submission if set to required, and they do?
this.form.get('profile').get('description').setValidators([Validators.required]);
this.form.get('profile').get('description').updateValueAndValidity();
}
When you do this you'll get an error in the console that says:
ERROR
Error: ExpressionChangedAfterItHasBeenCheckedError:
Expression has changed after it was checked.
Previous value: 'ng-valid: true'. Current value: 'ng-valid: false'.
Which can be overcome by using ChangeDetectorRef
and invoking this.changeDetector.detectChanges()
, or by updating the parent component's change detection strategy to be ChagneDetectionStrategy.OnPush
.
constructor(private changeDetector: ChangeDetectorRef) { }
// Removed for brevity
public ngAfterViewInit() {
const descControl = this.form.get('profile').get('description');
descControl.setValidators([Validators.required]);
descControl.updateValueAndValidity();
this.changeDetector.detectChanges();
}
@Component({
selector: 'app-page',
templateUrl: './page.component.html',
styleUrls: ['./page.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class PageComponent implements OnInit, AfterContentInit, AfterViewInit {
// Removed for brevity
public ngAfterViewInit() {
const descControl = this.form.get('profile').get('description');
descControl.setValidators([Validators.required]);
descControl.updateValueAndValidity();
}
}