Bhagwat Tupe
Bhagwat Tupe

Reputation: 1943

Angular : When submitting Template Driven form how can I set focus on first invalid input

I have checked in stackoverflow but I only found the way to do. I found a solution with querySelector but is there any better approach for setting focus? When I submit the form then I want to set focus on the first invalid input using Template Driven form.

How can I set focus on first invalid input using Template Driven forms?

Upvotes: 2

Views: 824

Answers (1)

Reinier Garcia
Reinier Garcia

Reputation: 1680

SOLUTION # 1:

I just coded this and it's working at least with inputs (with type text and number) and also with selects. It's using template driven as you requested.

Here it goes:

This is the minimum that you will need in your component (.ts file):

// Built-in Packages:
import { Component, ElementRef, ViewChild } from '@angular/core';
import { NgForm } from "@angular/forms";

@Component({
  selector: 'app-shopping-edit',
  templateUrl: './shopping-edit.component.html',
  styleUrls: ['./shopping-edit.component.css']
})
export class ShoppingEditComponent implements OnInit {
  @ViewChild('f', {static: false}) ingredientForm: NgForm;

  constructor(private elementRef: ElementRef) {
  }

  focusFirstInvalidControl(): void {
    const formControls = this.ingredientForm.controls;
    const formControlsKeys = Object.keys(formControls)

    for (let i = 0; i < formControlsKeys.length; i++) {
      const isInvalid = formControls[formControlsKeys[i]].invalid;
      if (isInvalid) {
        this.elementRef.nativeElement.querySelector(`#${formControlsKeys[i]}`).focus();
        break;
      }
    }
  }
}

That NgForm is connected as usual to your form on your template.

Conditions:

in order to make this work, each input inside the form in your template, needs to have a matching id, with the name and the local reference bindded to ngModel. All of them using the same name/denomination.

Example: Using a text input for the "Name".

      <div class="form-group">
        <label for="name">Name</label>
        <input
          type="text"
          id="name"
          name="name"
          class="form-control"
          placeholder="Type the name here"
          autofocus
          required
          ngModel
          #name="ngModel"
        >
      </div>
      <span *ngIf="(name?.invalid && name.touched); else nameOk" class="help-block">Please enter a valid name</span>
      <ng-template #nameOk><span class="help-block">&nbsp;</span></ng-template>
    </div>

Another example: Using a numeric input for the "Amount":

      <div class="form-group">
        <label for="amount">Amount</label>
        <input
          type="number"
          id="amount"
          name="amount"
          min="1"
          class="form-control"
          required
          [ngModel]="1"
          #amount="ngModel"
        >

      </div>
      <span *ngIf="(amount?.invalid && amount.touched); else amountOk" class="help-block">Please enter a valid amount</span>
      <ng-template #amountOk><span class="help-block">&nbsp;</span></ng-template>

Now you only need to trigger the method "focusFirstInvalidControl" on the submit event or by any other way that you prefer.

SOLUTION # 2:

This is even simpler:

  constructor(private elementRef: ElementRef) {
  }

      focusFirstInvalidControlPlus(): void {
        const firstElementWithErrors: HTMLElement = this.elementRef.nativeElement.querySelector(`form .ng-invalid`);
        if (firstElementWithErrors) {
          firstElementWithErrors.focus();
        }
      }

Just trigger the method "focusFirstInvalidControlPlus" within your .ts file on the submit event. It works when using template driven and also with Reactive approach.

Upvotes: 1

Related Questions