Ionic form validate from code, remove ng-untouched

i’ve the following form:

<form [formGroup]="personForm">
  <ion-list>
    <ion-item>
      <ion-label color="primary">Name</ion-label>
      <ion-input type="text" placeholder="Name" [(ngModel)]="username" formControlName="username"></ion-input>
    </ion-item>
  </ion-list>
</form>

in my constructor i register it:

    this.personForm = formBuilder.group({
      username:              ['', Validators.required]
    });

on the “next” button i want to verify the form. this works fine, and via “this.personForm.valid” i can verify the form. if the user touchs into the username field, and presses the next button, the field is also marked as red (from $text-input-ios-show-invalid-highlight) and invalid -> perfect.

But if the user does not touch the field and presses the next button, the form is invalid and the input is NOT marked red. When i manually remove the “ng-untouched” css-class from this input element, it is marked as red.

I tried to set the Control as “touched” in various forms:

    for (var i in this.personForm.controls) {
      this.personForm.controls[i].markAsTouched();
      this.personForm.controls[i].updateValueAndValidity({ onlySelf: false, emitEvent: true});
    }

    this.personForm.markAsTouched();
    this.personForm.updateValueAndValidity({ onlySelf: false, emitEvent: true});

with and without parameters for updateValueAndValidity etc.
The control has the right status after this (touched: true), but ionic will not automatticly remove the “ng-untouched” class from the control, even if the control is successfully marked as touched.

How can this be done?

Okai, i found out that calling the “markAsTouched()” method on the control actually does remove the ng-untouched and add the ng-touched class on the input, but not on the ion-item. is this a bug or a feature?
something like this will get it work…

  next()
  {
    for (var controlName in this.personForm.controls) {
      let ele = document.querySelector('[formControlName="' + controlName + '"]');
      this.personForm.controls[controlName].markAsTouched();

      if(ele) {
        this.setControlCss(ele.parentElement.parentElement.parentElement, this.personForm.controls[controlName]);
      }
    }
  }

  setControlCss(element: any, control: AbstractControl) 
  {
    this.renderer.setElementClass(element, 'ng-untouched', control.untouched);
    this.renderer.setElementClass(element, 'ng-touched', control.touched);
    this.renderer.setElementClass(element, 'ng-pristine', control.pristine);
    this.renderer.setElementClass(element, 'ng-dirty', control.dirty);
    this.renderer.setElementClass(element, 'ng-valid', control.valid);
    this.renderer.setElementClass(element, 'ng-invalid', !control.valid);
  }

note, for some reasons ionic is not highlighting select-input items which are invalid with a red border, only text-inputs. again, is this a bug or a feature?

see also: https://github.com/ionic-team/ionic/issues/6040#issuecomment-315037781

2 Likes

i’ve put this into a feature request and an bug report on github:


Hi, there is a lot of error in your code.

For example on your template if you are using Reactive Forms you dont need the ngModel directive.
Another thing, here is the way you should create a form:

    this.itemForm = new FormGroup({
        'name': new FormControl('', [Validators.required, 
                                     Validators.maxLength(20), 
                                     Validators.pattern('[A-Z][a-zA-Z]*')]),      
        'quantity': new FormControl('', [Validators.required, QuantityValidator.isValid])
    });

And with this input:

      <ion-item>
        <ion-label>{{ 'Modal.ItemModal.name-label' | translate }}</ion-label>
        <ion-input formControlName="name" placeholder="{{item?.title}}" type="text"></ion-input>
      </ion-item>

I got my input with red color if its not filled and i can show error messages like this:

      <div *ngIf="!itemForm.get('name').valid && itemForm.get('name').touched">
        <p class="error-text" *ngIf="itemForm.get('name').errors['pattern']">
          {{ 'Validations.modal-name-pattern' | translate }}
        </p>
        <p class="error-text" *ngIf="itemForm.get('name').errors['required']">
          {{ 'Validations.modal-name-required' | translate }}
        </p>
      </div>

This is all working hope this help you :wink:

thanks for your response!
it doesnt matter if i create the formgroup with the FormControl constructor or, like in my example, with an array for the parameters.
The ngModel is needed in my case because i bind the input directly to my model which i use later, but thats not relevant for this case.
I know that i can additionally show some error textes like in your example, and this will work fine for sure, because you are checking the touched property of the control, not of the ion-item. The red border from ionic will only show up, if the ion-item has the ng-touched class (check the css selector of this).

How do you trigger the validation in your case, if the user does not touch the input but the “next” button or similar?

EDIT: this is the relevant css for the invalid-border on an ion-item:

.item-ios.item-input.ng-invalid.ng-touched:not(.input-has-focus):not(.item-input-has-focus) .item-inner {
  border-bottom-color: #f53d3d;
}

note the ng-touched class

Hi, did you find the solution? I have the same issue. In my scenario, the first time I touch and then touch outside, the ‘ng-untouched’ still there. and the second time touch, it change to ‘ng-touched’.

2 Likes

Hey @xiejiabao, I have the exact same issue! Did you manage to resolve it?

1 Like

Esto funciono para mi.

Recorro todos los controles y los que no sean validos, les asigno un valor vacio, y los marco como dirty.

for (var controlName in this.fGeneral.controls) {
let ele = document.querySelector(’[formControlName="’ + controlName + ‘"]’);
if(this.fGeneral.controls[controlName].valid) {

  }else {
    this.fGeneral.controls[controlName].setValue('');
    this.fGeneral.controls[controlName].markAsTouched();
    this.fGeneral.controls[controlName].markAsDirty();
  }
  
}
2 Likes

Here’s how I solved the issue:

export class YourPage {
  constructor(
    public elementRef: ElementRef
  ) {

  }

  submit() {
    //remove the 'ng-untouched' class and add the 'ng-touched' attribute
    (this.elementRef.nativeElement.querySelectorAll('ion-item') as HTMLElement[]).forEach((x) => {
      x.classList.add('ng-touched');
      x.classList.remove('ng-untouched');
    });
  }
}
<form (submit)="submit()">
  <!--your inputs here-->
</form>
2 Likes

here is my solution:

    for(let i in this.form.controls){
      this.form.controls[i].setValue(this.form.controls[i].value);
      this.form.controls[i].markAsTouched();
    }

hope it helps someone :vulcan_salute:

4 Likes

HA, it’s funny because it works beautifully! Thanks for the tip.