Automatically shift focus to the next input when reaching the maximum length in Angular

Looking for a smoother way to focus the next input element in Angular without manually specifying which one. Here's my current HTML setup...

<div class="mb-2 digit-insert d-flex align-items-center">
  <div class="confirmation-group d-flex">
    <div class="digit-wrapper">
      <input #digitOne type="text" (paste)="onDigitPaste($event)" maxlength="1"
        (keyup)="onDigitInput($event, null, digitTwo)" />
    </div>
    <div class="digit-wrapper">
      <input #digitTwo type="text" maxlength="1" (keyup)="onDigitInput($event, digitOne, digitThree)" />
    </div>
    <div class="digit-wrapper">
      <input #digitThree type="text" maxlength="1" (keyup)="onDigitInput($event, digitTwo, digitFour)" />
    </div>
  </div>
  <span class="confirmation-divider m-3">-</span>
  <div class="confirmation-group d-flex">
    <div class="digit-wrapper">
      <input #digitFour type="text" maxlength="1" (keyup)="onDigitInput($event, digitThree, digitFive)" />
    </div>
    <div class="digit-wrapper">
      <input #digitFive type="text" maxlength="1" (keyup)="onDigitInput($event, digitFour, digitSix)" />
    </div>
    <div class="digit-wrapper">
      <input #digitSix type="text" maxlength="1" (keyup)="onDigitInput($event, digitFive, null)" />
    </div>
  </div>
</div>

The key up event determines which input field to focus on after user input. Here's the relevant TypeScript code...

onDigitInput(event: any, previousElement: any, nextElement: any): void {
    if (event.code !== 'Backspace' && nextElement !== null) {
        nextElement.focus();
    }

    if (event.code === 'Backspace' && previousElement !== null) {
        previousElement.focus();
        previousElement.value = '';
    }
}

Curious if there's a better approach or directive to achieve this focus functionality?

Answer №1

Revamp your input element to this:

<input #digitSix type="text" maxlength="1" (keyup)="onDigitInput($event)" />

Then, update your function implementation as follows:

onDigitInput(event){

   let element;
   if (event.code !== 'Backspace')
        element = event.srcElement.nextElementSibling;

    if (event.code === 'Backspace')
        element = event.srcElement.previousElementSibling;

    if(element == null)
        return;
    else
        element.focus();
}

This modification results in a more streamlined code.

This solution is compatible with the following snippet:

<div class="mb-2 digit-insert d-flex align-items-center">
  <div class="confirmation-group d-flex">
      <input #digitOne type="text" (paste)="onDigitPaste($event)" maxlength="1"
        (keyup)="onDigitInput($event)" />
      <input #digitTwo type="text" maxlength="1" (keyup)="onDigitInput($event)" />
      <input #digitThree type="text" maxlength="1" (keyup)="onDigitInput($event)" />
  </div>
  <span class="confirmation-divider m-3">-</span>
  <div class="confirmation-group d-flex">
      <input #digitFour type="text" maxlength="1" (keyup)="onDigitInput($event)" />
      <input #digitFive type="text" maxlength="1" (keyup)="onDigitInput($event)" />
      <input #digitSix type="text" maxlength="1" (keyup)="onDigitInput($event)" />
  </div>
</div>

Answer №2

 ngOnInit(): void {
    this.verifyCode = this.formBuilder.group({
      code1: ['', Validators.required],
      code2: ['', Validators.required],
      code3: ['', Validators.required],
      code4: ['', Validators.required],
    });
    }
  nextStep(event, step: number): void {
    if (this.verifyCode.valid) {
      this.onSubmit()
    }
    const prevElement = document.getElementById('code' + (step - 1));
    const nextElement = document.getElementById('code' + (step + 1));
    console.log(event)
    if (event.code == 'Backspace' && event.target.value === '') {
      event.target.parentElement.parentElement.children[step - 2 > 0 ? step - 2 : 0].children[0].value = ''
      if (prevElement) {
        prevElement.focus()
        return
      }
    } else {
      if (nextElement) {
        nextElement.focus()
        return
      } else {

      }
    }


  }

  paste(event) {
    let clipboardData = event.clipboardData;
    let pastedText = clipboardData.getData('text');
    this.verifyCode.setValue({
      code1: pastedText.charAt(0),
      code2: pastedText.charAt(1),
      code3: pastedText.charAt(2),
      code4: pastedText.charAt(3)
    });
    this.onSubmit()
    debugger
  }

  focused(step) {
    if (step === 2) {
      if (this.verifyCode.controls.code1.value === '') {
        document.getElementById('code1').focus();
      }
    }
    if (step === 3) {
      if (this.verifyCode.controls.code1.value === '' || this.verifyCode.controls.code2.value === '') {
        document.getElementById('code2').focus();
      }
    }

    if (step === 4) {
      if (this.verifyCode.controls.code1.value === '' || this.verifyCode.controls.code2.value === '' || this.verifyCode.controls.code3.value === '') {
        document.getElementById('code3').focus();
      }
    }
  }
  
    onSubmit(): void {
    this.submitted = true;
    if (this.verifyCode.invalid) {
      return;
    }
    // ...
    }
<div class="d-inline-block" style="width: 200px;">
                <div class="row ltr">
                  <div class="col-3 px-2">
                    <input type="text" class="form-control text-center ltr p-0" id="code1" [ngClass]="{'border-danger': submitted && f.code1.errors}" (keyup)="nextStep($event,1)" (focus)="focused(1)" formControlName="code1" maxlength="1"
                           (paste)="paste($event)" autofocus>
                  </div>
                  <div class="col-3 px-2">
                    <input type="text" class="form-control text-center ltr p-0" id="code2" [ngClass]="{'border-danger': submitted && f.code2.errors}" (keyup)="nextStep($event,2)" (focus)="focused(2)" formControlName="code2" maxlength="1">
                  </div>
                  <div class="col-3 px-2">
                    <input type="text" class="form-control text-center ltr p-0" id="code3" [ngClass]="{'border-danger': submitted && f.code3.errors}" (keyup)="nextStep($event,3)" (focus)="focused(3)" formControlName="code3" maxlength="1">
                  </div>
                  <div class="col-3 px-2">
                    <input type="text" class="form-control text-center ltr p-0" id="code4" [ngClass]="{'border-danger': submitted && f.code4.errors}" (keyup)="nextStep($event,4)" (focus)="focused(4)" formControlName="code4" maxlength="1">
                  </div>
                </div>

Answer №3

In this example, I have implemented a host listener to capture keyboard events and used a custom directive to manage focus.

  1. focus.directive.ts

The directive has an Input variable that is an EventEmitter accepting a string parameter. This parameter will store the element id to focus on.

import { Directive, EventEmitter, Input, OnInit, Renderer2 } from '@angular/core';

@Directive({
 selector: '[appFocus]'
})
export class FocusDirective implements OnInit {
@Input('appFocus') eventEmitter: EventEmitter<string>;

  constructor(private renderer: Renderer2) { }

  ngOnInit() {
   this.eventEmitter.subscribe(elementId => {
    try {
     this.renderer.selectRootElement(elementId).focus();
    } catch (ex) {
     // Handle cases where the element doesn't exist or disappears
    }
   });
  }
}
  1. inputs.component.ts

    import { Component, HostListener, EventEmitter } from '@angular/core';
    
    @Component({
     selector: 'app-inputs',
     templateUrl: './inputs.component.html'
    })
    
    export class InputsComponent {
      inputFocusEmitter = new EventEmitter<string>();
    
      @HostListener('window:keydown', ['$event'])
      HandlKeyEvents(event) {
       const key = event.key.toLocaleLowerCase();
       const inputIds = ['digitOne', 'digitTwo', 'digitThree'];
       let elementId = '';
    
       switch (key) {
        case 'backspace':
         elementId = event.target.id;
         const prevInputIndex = inputIds.indexOf(elementId) - 1;
         if (prevInputIndex >= 0) {
          this.inputFocusEmitter.emit(`#${inputIds[prevInputIndex]}`);
         }
         break;
    
       default:
        elementId = event.target.id;
        const index = inputIds.indexOf(elementId);
        const nextInputIndex = index + 1;
        if (nextInputIndex > 0 && nextInputIndex < inputIds.length) {
         this.inputFocusEmitter.emit(`#${inputIds[nextInputIndex]}`);
        }
        break;
      }
    }
    
  2. inputs.component.html

In the HTML file, the focus directive is connected to each input element for managing focus effectively.

 <div class="mb-2 digit-insert d-flex align-items-center">
      <div class="confirmation-group d-flex">
          <div class="digit-wrapper">
              <input #digitOne type="text" maxlength="1"  [appFocus]="inputFocusEmitter"/>
          </div>
          <div class="digit-wrapper">
              <input #digitTwo type="text" maxlength="1"  [appFocus]="inputFocusEmitter"/>
          </div>
          <div class="digit-wrapper">
              <input #digitThree type="text" maxlength="1" [appFocus]="inputFocusEmitter"/>
          </div>
      </div>
 </div> 

I hope this explanation clarifies how focus handling is achieved using Angular directives and components.

Answer №4

Incorporating jQuery has simplified the process and reduced the chances of errors for me.

move-focus-to-next.directive.ts

import { Directive, HostListener } from '@angular/core';
import * as $ from 'jquery';

@Directive({
    selector: '[moveFocusToNext]'
})
export class MoveFocusToNext {
    @HostListener('input', ['$event']) onInputChange(event) {
        if (event.target && event.target.maxLength === event.target.value.length) {
            $(":input")[$(":input").index(document.activeElement) + 1].focus();
        }
    }
}

my-component.html

<input [(ngModel)]="myModal" moveFocusToNext maxlength="10">

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

An insightful guide on effectively binding form controls in Angular using reactive forms, exploring the nuances of formControlName and ngModel

Here is the code snippet: list.component.html <form nz-form [formGroup]="taskFormGroup" (submit)="saveFormData()"> <div nz-row *ngFor="let remark of checklist> <div nz-col nzXXl="12" *ngFor="let task of remark.tasks" styl ...

Encountering a Typings Install Error When Setting Up angular2-localstorage in WebStorm

I am currently using Windows and WebStorm as my development environment. I attempted to install the angular2-localstorage package by running the command npm install angular2-localstorage, but encountered an error. After realizing that the angular2-localst ...

Can someone explain how to showcase a collection attribute in Angular?

I am working with a collection of objects called "ELEMENT_DATA". Each object in the collection has an attribute named "Activite", which is also a collection. My goal is to display this attribute in a specific way. Below is my app.component.ts: export cla ...

"Hmm, the React context's state doesn't seem to be changing

I have been working on a next.js app and I encountered an issue related to using react context to export a state. Despite my efforts, the state doesn't seem to update and it remains stuck at the initial value defined by the createContext hook, which i ...

TypeScript was looking for 'never' but found an intersection instead

Can someone help me understand why a conflicting type intersection did not produce a type of never? What am I overlooking? type A = {value: string} type B = {value: number} type D = A & B type E<T> = T extends never ? 'never' : ' ...

Angular 9's localization feature is not functioning properly when using the configuration flag --configuration=de

When I utilize $localize from "@angular/localize/" with "ng serve --configuration=de", the result that I obtain is shown in the following image: Everything functions perfectly when executing the project with just "ng serve". Within app.component.ts: pub ...

Unusual actions observed in Ionic 3 app using webview 3 plugin

I am currently facing a significant problem with Webview 3. One of our developers upgraded it to webview 3 without utilizing the Ionic native webview plugin, and surprisingly, it is functioning well on our Ionic 3 application. As per the documentation avai ...

Setting up Emotion js in a React TypeScript project using Vite 4

Currently, I am in the process of transitioning from Webpack to Vite for my React Typescript application. I have been attempting to integrate Emotion js into the project. "@vitejs/plugin-react": "^4.0.1", "vite": "^4.3.9 ...

Encountering an error when invoking a web API controller from a service in Angular 2

I am currently following an Angular quick start tutorial that focuses on the Hero tutorial provided on the Angular2 website. The tutorial runs smoothly for me as it binds static array data and allows for CRUD operations. However, my goal now is to understa ...

Angular is having trouble locating the module for my custom library

Trying to implement SSR in my angular application, but encountering an error when running npm run build:ssr. I've created my own library named @asfc/shared, which is bundled in the dist folder. ERROR in projects/asfc-web/src/environments/environment. ...

Clicking on an icon to initiate rotation (Material UI)

Is there a way to toggle the rotation of an icon (IconButton) based on the visibility of a Collapse component? I want it to point down when the Collapse is hidden and up when it's shown. const [expanded, setExpanded] = useState<boolean>(false); ...

When I try to pass a formControl to a child component in Angular, it throws a "no value

What could be causing the error message "no value accessor for form control with unspecified name" to appear? I am working with the edit-component: Here is the code in HTML: <mat-form-field> <input [formControl]="formControl"> </mat-f ...

"What are the necessary components to include in UserDTO and what is the reasoning behind their

Presenting the User entity: package com.yogesh.juvenilebackend.Model; import jakarta.annotation.Generated; import jakarta.persistence.*; import lombok.*; @Entity @Getter @Setter @NoArgsConstructor @AllArgsConstructor @RequiredArgsConstructor public class ...

How can you extract the text content within component tags that is neither a component nor an HTML tag?

For illustration purposes, consider the following example: var ItemComponent = ng.core.Component({ selector: "item", inputs: ["title"], template: "<li>{{title}} | <ng-content></ng-content></li>", }).Class({ construc ...

Enhancing data binding in Angular 2.0 with string interpolation

In my script, I am working with a string that goes like this: {name} is my name. Greeting {sender} Is there a module available in Angular 2.0 that allows me to use something similar to the string.format() function in C#? I understand that it can be achie ...

Injecting constructor for Angular standalone component and service

I am encountering an issue with injecting a service through the constructor of a standalone component. The service is declared as Injectable with the provider set to root: @Injectable({ providedIn: 'root' }) export class ProductsService {...} ...

Using Angular to send a text to an injection service

i have developed a generic CRUD service that utilizes HttpClient through Dependency Injection (DI). However, I need to include another value in the constructor of this service. How can I achieve this? the issue arises when attempting to define this additi ...

Convert all key types into arrays of that key type using a TypeScript utility type

My interface (type) is currently defined as: interface User { name: string, id: string, age: number, town: string } I have a function now that will search for Users based on specific fields. I prefer not to manually declare an additi ...

What is the best way to invoke a function with multiple parameters in TypeScript?

I have a function that manipulates a specified query string, along with another version that always uses window.location.search. Here is the code snippet: class MyClass { public changeQuery(query: string; exclude: boolean = true; ...values: string[]): st ...

Conditional Skipping of Lines in Node Line Reader: A Step-by-Step Guide

I am currently in the process of developing a project that involves using a line reader to input credit card numbers into a validator and identifier. If I input 10 numbers from four different credit card companies, I want to filter out the numbers from thr ...