Move input focus to next input when maxLength is reached - Angular 4 / Typescript
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" />
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
// .......
}
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.