Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular reactive forms - validate on blur but update model while typing

I have a situation using reactive forms and an input text.

I need to:

  1. as the user types, suggest a list based on what was typed (I'm using ngx bootstrap typeahead);
  2. validate the input field only when the user lose focus of the input.

In order to execute the input validation only when the user leaves the input field I added "updateOn: 'blur'" to the validation options. The problem is that this affects not only when the validation is executed but also when the model will be updated. With 'blur' the model gets update only when the user leaves the input text and as a consequence the suggestion list is not working as user types as it should because the model is not being updated.

I added an example in stackblitz.

I would like to know if there is a way to execute the validation only on blur but the model to be updated as the user types.

Thanks.

like image 586
tiagowanke Avatar asked Apr 01 '20 12:04

tiagowanke


2 Answers

I'm not aware of any built-in way of doing this, but here's one approach, which will use a custom value accessor.

custom-input.component.ts

@Component({
  selector: 'app-custom-input',
  template: `
    <p>
      custom input: 
      <input
        #i
        (input)="onChangeFn($event.target.value)" 
        (blur)="onTouchedFn()"
        type="text"
        [formControl]="input"
      >
    </p>

    <p>
    inner input value: {{ i.value }}
    </p>
  `,
  providers: [ { provide: NG_VALUE_ACCESSOR, useExisting: CustomInputComponent, multi: true, } ]
})
export class CustomInputComponent implements OnInit, ControlValueAccessor {
  input: FormControl;
  onTouchedFn: Function;
  onChangeFn: Function;

  constructor(private fb: FormBuilder) { }

  ngOnInit() {
    this.input = this.fb.control('');
  }

  registerOnChange (fn) {
    this.onChangeFn = fn;
  }

  registerOnTouched (fn) {
    this.onTouchedFn = fn;
  }

  writeValue (v) { this.input.setValue(v); }
}

parent.component.ts

  specialInput = new FormControl('', { updateOn: 'blur', validators: Validators.required });

parent.component.html

<app-custom-input [formControl]="specialInput"></app-custom-input>

<p>
  Special input validation status: {{ specialInput.valid || 'false' }}
</p>

You can think of your form controls as a tree structure:

FG - FormGroup; FC - FormControl

    FG
 /  |  \
FC  FC  FC

In your case, when FC has changed its value(e.g due to user input), all its descendants(in this case FG, but in a complex example there could be more) will have to be updated as well, regardless of their updateOn value.

By following this approach, this tree would now look like this(assuming the last FC is built this way):

    FG
 /  |  \_________
FC  FC | FC      |
       |   \     |
       |    FC <-- the `input` elem. in the custom component
       |_________|

As a result, the value will update onChange in the inner FC, and onBlur in the outer FC.

StackBlitz.

Also, if you'd like to read more about forms, I'd recommend checking out this article.

like image 172
Andrei Gătej Avatar answered Oct 20 '22 09:10

Andrei Gătej


I used a solution that I don't think is the best one but it solves my current issue.

When creating the form I'm not adding the validator anymore and I added a function that is executed on blur that add the validator to the FormControl, validate and remove the validator;

I can imagine that this solution is not the ideal one because I may have problems if I want to trigger the validation in a moment other than on blur but for my current scenario it works.

So this will be the form creation of the form:

const myInput = new FormControl('', validators: Validators.required })

This is the html

<input type="text" (blur)="validate()" formControlName="myInput">

And this is the validate function

  validate(): void {
    this.myInput.setAsyncValidators(this.myAsyncValidator());
    this.myInput.updateValueAndValidity();
    this.myInput.clearAsyncValidators();
  }
like image 23
tiagowanke Avatar answered Oct 20 '22 08:10

tiagowanke