I know there is an existing npm package for OTP input fields, as mentioned in this Stack Overflow post, but I need to build this component from scratch for better customization and learning purposes.
I am working on an Angular 8 OTP input component, where users enter a 6-digit code. The OTP input fields are dynamically generated using *ngFor, and I am using [value] binding and event listeners to update the component state. However, I am encountering two issues:
- Value Duplication:
- When I type a number in the first input (index 0), the same number appears in the second input (index 1).
- Backspace Behavior:
- When I click on the second input and press backspace, the value in the first input is deleted instead of the second one.
My Component Code HTML (otp-input.component.html):
<div class="otp-container">
<input
*ngFor="let digit of otpArray; let i = index"
type="text"
class="otp-input"
maxlength="1"
[value]="otpArray[i]"
(input)="onInput($event, i)"
(keydown)="onKeyDown($event, i)"
#otpInput
/>
</div>
TypeScript (otp-input.component.ts):
import { Component, EventEmitter, Output, ViewChildren, ElementRef, QueryList } from
'@angular/core';
@Component({
selector: 'app-otp-input',
templateUrl: './otp-input.component.html',
styleUrls: ['./otp-input.component.css']
})
export class OtpInputComponent {
otpLength = 6;
otpArray: string[] = new Array(this.otpLength).fill('');
@Output() otpCompleted = new EventEmitter<string>();
@ViewChildren('otpInput') otpInputs!: QueryList<ElementRef>;
onInput(event: Event, index: number): void {
const inputElement = event.target as HTMLInputElement;
const value = inputElement.value;
// Ensure the correct value is assigned to the correct index
this.otpArray[index] = value;
console.log(`User entered value "${this.otpArray[index]}" at index ${index}`);
const inputEvent = event as InputEvent;
if (inputEvent.inputType === 'deleteContentBackward') {
console.log('User pressed delete on input ' + index);
return;
}
if (value && index < this.otpLength - 1) {
this.otpInputs.toArray()[index + 1].nativeElement.focus();
}
this.checkOtpCompletion();
}
onKeyDown(event: KeyboardEvent, index: number): void {
if (event.key === 'Backspace') {
if (this.otpArray[index]) {
this.otpArray[index] = ''; // Clear current input
} else if (index > 0) {
console.log('Backspace pressed, moving to previous index:', index);
this.otpInputs.toArray()[index - 1].nativeElement.focus();
}
}
}
checkOtpCompletion(): void {
const otpValue: string = this.otpArray.join('');
if (otpValue.length === this.otpLength) {
this.otpCompleted.emit(otpValue);
}
} } Expected Behavior:
- Each digit should only be entered in the selected input field, without affecting others.
- Pressing backspace should clear the current field first and then move focus to the previous input.
What I Have Tried:
- Replaced [value] with [(ngModel)
- hecked for unexpected change detection updates.
- I added console logs and verified the updates were occurring in the expected order.
- Manually handling state updates in onInput function
Questions
- Why does the value get duplicated in the next input field when typing?
- How can I prevent backspace from deleting the previous field's value before the current one?