Home > Enterprise >  How to simplify an angular reactive form validation boilerplate
How to simplify an angular reactive form validation boilerplate

Time:11-17

I have this sort of form:

 this.form = this.formBuilder.group(
      {
        name: new FormControl('', Validators.required),
        // more controls here
      }
    );

Html:

    <form   (ngSubmit)="onSubmit()" [formGroup]="form">
        <div
          
          [class.form-control--valid]="vm.name.valid && (vm.name.dirty || vm.name.touched)"
          [class.form-control--invalid]="vm.name.invalid && (vm.name.dirty || vm.name.touched)">
          <label for="form_name" >Your name</label>
          <div >
            <input type="text"  id="form_name" name="name" formControlName="name">
          </div>
          <div  *ngIf="vm.name.errors && (vm.name.dirty || vm.name.touched)">
            <p *ngIf="vm.name.errors.required">First name is required</p>
          </div>
        </div>
        <!-- more controls here-->
        <button  type="submit" [disabled]='!form.valid'>Submit</button>
      </form>

vm is equal to this.form.controls;

It seems to me that the html contains to much code related to form validation.

I wonder if it is possible to reduce the amount of markup code to avoid writing all that vm.name.invalid && (vm.name.dirty || vm.name.touched.

Also I believe things like that could be simplified, it is a lot of boilerplate to:

[class.form-control--valid]="vm.name.valid && (vm.name.dirty || vm.name.touched)"
[class.form-control--invalid]="vm.name.invalid && (vm.name.dirty || vm.name.touched)"

CodePudding user response:

Angular yet add different class if valid or touched. See the docs

Based on it, I suggest these are the class you can add to your divs (*)

As always we can improve a "tag" sometimes it's util use a directive

@Directive({
  selector: '[errorClass]'
})
export class ErrorClassDirective {
  @ContentChild(NgControl) control:NgControl;
  @HostBinding('class') get class()
  {
    if (!this.control)
      return null

    let className=this.control.invalid?'ng-invalid ':'ng-valid '
    className =this.control.touched?'ng-touched ': 'ng-untouched'
    className =this.control.dirty?'ng-dirty ': 'ng-pristine'
    return className;
  }
}

Then we can use some like

<form [formGroup]="form">
  <div errorClass>
    <label>Control</label>
    <input formControlName="control" />
  </div>
</form>

Now we can play with .css

e.g.

/*Some class to style the inputs and labels*/
label {
  margin-right: 0.25rem;
}
input {
  background-color: #fff;
  background-clip: padding-box;
  border: 1px solid #ced4da;
  border-radius: 0.25rem;
  padding: 0.25rem;
}

input:focus {
  color: #212529;
  background-color: #fff;
  border-color: #86b7fe;
  outline: 0;
  box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
}

/*Here style the "div"*/
.ng-invalid.ng-touched:not(input),
.ng-invalid.ng-dirty:not(input) {
  color: red;
}

/*Style the input invalid*/
input.ng-invalid.ng-touched,
input.ng-invalid.ng-dirty {
  border-color: red;
  outline: none;
}
input.ng-invalid.ng-touched:focus,
input.ng-invalid.ng-dirty:focus {
  border-color: red;
  box-shadow: 0 0 0 0.25rem rgba(255, 0, 0, 0.25);
}

See stackblitz

(*) with the new selector has you needn't any directive, We can simply use a class in the way

div:has(input.ng-invalid.ng-touched)
{
  color:red
}

Unfortunaly this selector is not ready for some navigator (Firefox need User must explicitly enable this feature)

  • Related