loading...
Cover image for Don't use Form Controls Two-Way Binding

Don't use Form Controls Two-Way Binding

jwp profile image John Peters Updated on ・2 min read

New Discovery
Don't use Form Controls, Form Groups, Form Builders, Reactive Forms at all.

Use ngModel instead, read this 3 part series.

Consider this HTML

The Angular FormControl pattern:

<form [formGroup]="formGroup">
   <input
      formControlName="fcSSN"
   />
</form>
Enter fullscreen mode Exit fullscreen mode

In Angular, we use form controls to provide validation. We like its simple regex based validation scheme and automatic application of warning colors when values are invalid.

We use the form control value changed event and the valid flag to automatically show and hide the Save button.

All powerful reasons for using the FormControl.

The Triad of Values

There are three values to consider:

-The HTMLInputElement value shown and changed by the user.

-The FormControl Value which tracks user input.

-The immutable Model Value, to be set 1 time at start and changed only by FormControl event handler.

The guidance from Angular is to avoid direct DOM value manipulation, this leaves us with two options.

The Model and the Form Control Relationship

// this is the validation pattern
ssnPattern = 
 /(?=.{11}$)\d{1,3}-?\d{1,2}-?\d{1,4}|###-###-###/;
controls = {
  // this is the formControl for the SSN number.
  fcSSN: new FormControl(
   // this.ssn is the model
   this.ssn, 
   // use the regex above for validation
   [Validators.pattern(this.ssnPattern)]),
};
formGroup: FormGroup = 
  new FormGroup(this.controls);
Enter fullscreen mode Exit fullscreen mode

From the code above, we can see that the FormGroup encapsulates the FormControl which points to the Model. We also can see the regex which is used to validate the value.

If we set the initial value of the FormContol in ngAfterInit like this:

@Input() defaultValue = "###-###-####";
ngAfterViewInit() {
 this.controls.fcSSN.setValue(this.defaultValue);
}
Enter fullscreen mode Exit fullscreen mode

We will see this in the View.

SSN Hash Tags

As soon as we type in a value of 1 the ripple effect looks like this from the form control changed event handler.

FormControlValue: "1"
FormControlValueChangeEventValue: "1"
HTMLInputElementValue: "1"
ModelValue: undefined
valid: false

Enter fullscreen mode Exit fullscreen mode

In the output shown above, we can see that the form control and HTMLInputElement values are tracking the changes; but the Model is not following along! This means that there is no two-way binding as the model is not updated.

How to Synch Form Control Changes to the Model

This new way of doing things is a result of the laws of Immutability; which imply the only time to mutate is right before the valid Save.

There should be only one place where the model is updated and that's in the change event handler as shown below.

this.controls.fcSSN.valueChanges.subscribe((value) => {
   // if valid
   if (this.controls.fcSSN.valid) {
      // update model
      this.ssn = value;
   }
Enter fullscreen mode Exit fullscreen mode

We no longer mutate or use the Model for mutation as the workflow happens. Rather we wait until the new value is deemed valid!

All mutation is done in the form control itself. The Typescript logic and HTML only focus on the form controls and not the model!

Discussion

pic
Editor guide