You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Angular 8自定义OTP输入组件:值重复与退格行为异常问题

自定义Angular 8 OTP输入组件问题排查

我了解已有用于OTP输入框的npm包,但为了更好的定制性与学习需求,从零构建了Angular 8的6位OTP输入组件。该组件通过*ngFor动态生成输入框,使用[value]绑定与事件监听维护状态,但遇到两个问题:

  • 值重复问题:在第一个输入框(索引0)输入数字时,第二个输入框(索引1)会出现相同数字;
  • 退格行为异常:点击第二个输入框并按下退格键时,删除的是第一个输入框的值而非当前框的值。

组件代码

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);
}
}
}

预期行为

  • 每个数字仅输入到选中的输入框,不影响其他框;
  • 按下退格键时先清空当前框,再将焦点移至上一个输入框。

已尝试的解决方法

  • 将[value]替换为[(ngModel)];
  • 检查意外的变更检测更新;
  • 添加控制台日志验证更新顺序;
  • 在onInput函数中手动处理状态更新。

问题

  1. 输入时为何值会在下一个输入框重复?
  2. 如何避免退格键先删除上一个框的值而非当前框?

问题分析与解决方案

1. 值重复问题原因及修复

原因:使用new Array(this.otpLength).fill('')初始化数组时,所有元素指向同一个空字符串引用。当修改otpArray[index]时,Angular的变更检测会错误地更新绑定了相同引用的其他输入框,导致值重复。

修复:初始化数组时确保每个元素都是独立的空字符串:

otpArray: string[] = Array.from({ length: this.otpLength }, () => '');

同时,在onInput方法中过滤非数字输入并阻止默认行为,避免重复处理:

onInput(event: Event, index: number): void {
  const inputElement = event.target as HTMLInputElement;
  // 只保留数字字符
  const value = inputElement.value.replace(/[^0-9]/g, '');
  
  event.preventDefault();
  this.otpArray[index] = value;

  if (value && index < this.otpLength - 1) {
    setTimeout(() => {
      this.otpInputs.toArray()[index + 1].nativeElement.focus();
    }, 0);
  }

  this.checkOtpCompletion();
}

2. 退格行为异常原因及修复

原因:keydown事件触发后,默认的退格行为会触发input事件,而onInput中对deleteContentBackward的判断会直接返回,导致焦点逻辑未执行;同时状态更新与焦点移动的时机不同步。

修复:在keydown中完整处理退格逻辑,并阻止默认行为避免input事件重复处理:

onKeyDown(event: KeyboardEvent, index: number): void {
  if (event.key === 'Backspace') {
    event.preventDefault();
    if (this.otpArray[index]) {
      this.otpArray[index] = '';
    } else if (index > 0) {
      const prevInput = this.otpInputs.toArray()[index - 1].nativeElement;
      prevInput.focus();
      // 若需要清空上一个输入框,可取消注释下面一行
      // this.otpArray[index - 1] = '';
    }
    this.checkOtpCompletion();
  }
}

同时移除onInput方法中对deleteContentBackward的判断逻辑。


内容的提问来源于stack exchange,提问作者Yoni Shkolsky

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 21:19:53