How can I prevent NgModel from setting invalid form control values?

4k views Asked by At

How can I prevent NgModel from setting invalid template-driven form control values? I verified that NgModel will set invalid values when two-way bound to a model instance. I am aware that I can create a copy of the model instance, but I may have scenarios where a save/revert approach is not appropriate.

https://stackblitz.com/edit/angular-gcu9mz

@Component({
  selector: 'my-app',
  template: `
    Enter an invalid value (less than 14 characters).
    <br><br>
    <label for="input">Input:</label>
    <input #input="ngModel" type="text" minlength="14" [(ngModel)]="value" 
      placeholder="Enter an invalid value">
    Valid: {{input.valid}}
    <br>
    Model value: {{value}}
`
})
export class AppComponent  {
  value = 'Invalid value';
}

I found many related questions for AngularJS where ngModelOptions is said to support an allowInvalid configuration that changes the default behavior. However, that does not seem to be supported in Angular's NgModel.

Don't record invalid values with ng-model

How to prevent model to be invalid?

I am not interested in the discussion on whether accepting, displaying, or setting invalid values is good practice nor whether the model should be the "source of truth" as my requirements always depend on the application and use case.

3

There are 3 answers

0
Trevor Karjanis On BEST ANSWER

Expanding on alexortizl's answer, I chose to utilize getters and setters but not to duplicate the validation logic. I used the FormControl's validity state to prevent setting invalid values on the model.

https://stackblitz.com/edit/angular-5xkael

@Component({
  selector: 'my-app',
  template: `
    <label for="input">Input:</label>
    <input #input="ngModel" type="text" minlength="14" [(ngModel)]="value">
`
})
export class AppComponent  {
  @ViewChild('input', { static: true }) input: NgControl;

  // Allow an initial invalid value.
  private _value = 'Invalid value';
  get value() { return this._value; }

  set value(value: string) { if (this.input.valid) this._value = value; }
}
0
alexortizl On

You could use a getter to wrap your validation logic. Something like this:

@Component({
 selector: 'my-app',
 template: `
   Enter an invalid value (less than 14 characters).
   <br><br>
   <label for="input">Input:</label>
   <input #input="ngModel" type="text" minlength="14" [(ngModel)]="modelValue" 
   placeholder="Enter an invalid value">
   Valid: {{input.valid}}
   <br>
   Model value: {{modelValue}}`
})

export class AppComponent  {
 get modelValue() {
   let value;

   // validation logic to ensure value is valid

   return value; // valid value
 }
}

set modelValue(value) {
  //set value logic
}
1
Eliseo On

from the docs

To add validation to a template-driven form, you add the same validation attributes as you would with native HTML form validation. Angular uses directives to match these attributes with validator functions in the framework.

In the link, about minlength say

The number of characters (code points) must not be less than the value of the attribute, if non-empty.

You can use

<input required minlength="14 ....>