12

I want to move focus from an input field to another when a user has entered the maxLength amount of characters into the first input field. So in my example below, when a user has entered 2 characters into the day input, the focus would move to the month input.

This is my code so far:

<input formControlName="day" maxlength="2" placeholder="DD" type="text" (keyup)="keytab($event)" />
<input formControlName="month" maxlength="2" placeholder="MM" type="text" (keyup)="keytab($event)" />
<input formControlName="year" maxlength="4" placeholder="YYYY" type="text" />

And in my TS file:

 keytab(event){
    let nextInput = event.srcElement.nextElementSibling; // get the sibling element

    var target = event.target || event.srcElement;
    var id = target.id
    console.log(id.maxlength); // prints undefined

    if(nextInput == null)  // check the maxLength from here
        return;
    else
        nextInput.focus();   // focus if not null
}

I know the code in my TS file is wrong, but I was trying to find a way of getting the maxLength property and then shifting the focus. Right now the focus will move as soon as there is a keyup in the input field.

Can anyone tell me how I can access the inputs maxLength property from the keytab function? Thanks.

I'm using Angular 4.

Edit - I'm trying to get the maxLength value and then compare to the input value length. If the input value is more, then move focus to the input field.

Jose the hose
  • 1,805
  • 9
  • 33
  • 56

5 Answers5

8

here is a generic (Directive) solution to move to next similar control type when reaches the maximum length

1- Create the Directive

import { Directive, HostListener } from '@angular/core';

@Directive({
  selector: 'input[moveNextByMaxLength], textarea[moveNextByMaxLength]',
})
export class MoveNextByMaxLengthDirective {
  @HostListener('keyup', ['$event']) onKeyDown(keyboardEvent: KeyboardEvent) {
    const target = keyboardEvent.target as
      | HTMLInputElement
      | HTMLTextAreaElement
      | null;

    if (!target || target.maxLength !== target.value.length) return;

    keyboardEvent.preventDefault();

    const { type } = target;
    let { nextElementSibling } = target;

    while (nextElementSibling) {
      if (
        (nextElementSibling as HTMLInputElement | HTMLTextAreaElement).type ===
        type
      ) {
        (nextElementSibling as HTMLInputElement | HTMLTextAreaElement).focus();
        return;
      }

      nextElementSibling = nextElementSibling.nextElementSibling;
    }
  }
}

2- Declare the Directive in the module

@NgModule({
  imports: [ BrowserModule ],
  declarations: [
    AppComponent,
    MoveNextByMaxLengthDirective 
  ],
  bootstrap: [ AppComponent ]
})

3- Use the Directive in the component

<input formControlName="day" maxlength="2" moveNextByMaxLength placeholder="DD" type="text" (keyup)="keytab($event)" />
<input formControlName="month" maxlength="2" moveNextByMaxLength placeholder="MM" type="text" (keyup)="keytab($event)" />
<input formControlName="year" maxlength="4" placeholder="YYYY" type="text" />
dev_054
  • 3,448
  • 8
  • 29
  • 56
Mina Matta
  • 1,024
  • 14
  • 22
  • its working fine , but problem is here that if you want to remove data by backspace .. its not going to previous input .... i am using this solution for 4 OTP input boxes – Shashwat Gupta Jul 13 '20 at 17:33
  • It is not working using with div what are the changes required.
    – pushp May 23 '23 at 14:00
4

Use a different approach. Angular does not select elements and read attributes from the existing DOM, as jQuery does, because Angular generates the DOM from data. So it's difficult, if possible at all, to read the input's maxlength attribute, and anyway it would be clumsy an "non-Angulary".

Instead, use a different approach and pass the maxLength in the keyup function :

<input type="text" (keyup)="keytab($event, 2)" />
<input type="text" (keyup)="keytab($event, 4)" />


keytab(event, maxLength){
   console.log(maxlength); // 2 or 4
   // .......
}
Jeremy Thille
  • 26,047
  • 12
  • 43
  • 63
  • Thanks for your answer. I'm now passing the extra param to the keytab function, and from there I get the input value length and compare them - if (event.target.value.length < maxLength) ... – Jose the hose Nov 29 '17 at 10:30
  • @Josethehose can you comment the full logic please, thanks – Md Alamin Sep 01 '18 at 09:52
3

Another simple answer for Angular 9+

<input #input1 (keyup)="(input1.value.length == 2) ? input2.focus() : ''" type="text" maxlength="2">
<input #input2 (keyup)="(input2.value.length == 2) ? input3.focus() : ''" type="text" maxlength="2">
<input #input3 (keyup)="(input2.value.length == 2) ? submit.focus() : ''"  type="text" maxlength="2">

<button #submit type="submit">Submit</button>
2

Just an idea but if you're using reactive forms you could do something like this:

import { FormGroup, FormBuilder, Validators } from "@angular/forms";
import { Component, OnInit, ViewChild, ElementRef } from "@angular/core";
import { filter } "rxjs/operators";

@Component({
  selector: "app-form",
  template: `
    <form [formGroup]="form">
        <input formControlName="day" placeholder="DD" type="text" #day />
        <input formControlName="month" placeholder="MM" type="text" #month />
        <input formControlName="year" placeholder="YYYY" type="text" #year />
    </form>
`
})
export class FormComponent implements OnInit {
  form: FormGroup;

  @ViewChild("day") dayElement: ElementRef;

  @ViewChild("month") monthElement: ElementRef;

  @ViewChild("year") yearElement: ElementRef;

  constructor(private fb: FormBuilder) {}

  ngOnInit() {
    const dayMaxLength = 2;
    const monthMaxLength = 2;
    const yearMaxLength = 4;

    this.form = this.fb.group({
      day: ["", [Validators.required, Validators.maxLength(dayMaxLength)]],
      month: ["", [Validators.required, Validators.maxLength(monthMaxLength)]],
      year: ["", [Validators.required, Validators.maxLength(yearMaxLength)]]
    });

    this.form.get("day").valueChanges
      .pipe(filter((value: string) => value.length === dayMaxLength))
      .subscribe(() => this.monthElement.nativeElement.focus());

    this.form.get("month").valueChanges
      .pipe(filter((value: string) => value.length === monthMaxLength))
      .subscribe(() => this.yearElement.nativeElement.focus());
}

Basically subscribe to the value changes of both day and month form controls, filter each stream so that it only continues when the value is equal to the max length, then set the focus to the next element. Probably worth noting these will need to be unsubscribed from too.

developer033
  • 24,267
  • 8
  • 82
  • 108
Harry
  • 353
  • 2
  • 9
  • This looks interesting. I'll try it later and let you know. Many thanks! – Jose the hose Nov 29 '17 at 11:13
  • 1
    I have try this solution. `this.form.controls['day'].valueChanges` works instead of `this.form.get("day").valueChanges` – Emon Mar 29 '19 at 11:03
  • This is definitly the most elegant way of doing things in angular. Like @EmonZan said tough, your example does not work with get. A the best way would probably be `this.form.controls.day.valueChanges`. – Wirde May 12 '20 at 15:00
1

Pure js approach

let allInputs = document.getElementsByTagName('input');
let index = 0;
for(i=0;i<allInputs.length;i++) {
allInputs[i].onkeydown =trackInputandChangeFocus;
}

function trackInputandChangeFocus() {
let allInputsArr = Array.from(allInputs); 
let presentInput = allInputsArr.indexOf(this)
if(this.value.length == parseInt(this.getAttribute('maxlength'))) {
      let next;
      if(presentInput != 2) next = allInputsArr[presentInput+1]
      else next = allInputsArr[0]
      next.focus();
    }
}
<input formControlName="day" maxlength="2" placeholder="DD" type="text" (keyup)="keytab($event)" />
<input formControlName="month" maxlength="2" placeholder="MM" type="text" (keyup)="keytab($event)" />
<input formControlName="year" maxlength="4" placeholder="YYYY" type="text" />
Rajkumar Somasundaram
  • 1,225
  • 2
  • 10
  • 20