html - Angular OTP 输入:多个字段中值重复问题
我知道有一个现成的用于 OTP 输入字段的 npm 包,如这个 Stack Overflow 帖子所提到的,但我需要从头构建这个组件,以便更好地进行自定义和学习。
我正在开发一个 Angular 8 OTP 输入组件,用户在其中输入 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
问题可能在于,当未指定 trackBy 时,*ngFor 会在变化检测周期中销毁元素并重新创建它们,这可能是这种奇怪行为的原因。另一种理论是,name 和 id 帮助确定更新哪个元素,如果未指定,可能会遇到一些奇怪的错误,例如输入被错误更新。
ngFor :
<div class="otp-container">
<input
*ngFor="let digit of otpArray; let i = index; trackBy:trackByIndex"
type="text"
class="otp-input"
maxlength="1"
[id]="'otp-' + i"
[name]="'otp-' + i"
[value]="otpArray[i]"
(input)="onInput($event, i)"
(keydown)="onKeyDown($event, i)"
#otpInput
/>
</div>
TS :
trackByIndex = (index: number, obj: object): string => { return index; };
@for:
<div class="otp-container">
@for(digit of otpArray; let i = $index;track i) {
<input
type="text"
class="otp-input"
maxlength="1"
[id]="'otp-' + i"
[name]="'otp-' + i"
[value]="otpArray[i]"
(input)="onInput($event, i)"
(keydown)="onKeyDown($event, i)"
#otpInput
/>
}
</div>
完整代码:
import { Component } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
import {
EventEmitter,
Output,
ViewChildren,
ElementRef,
QueryList,
} from '@angular/core';
@Component({
selector: 'app-otp-input',
template: `
<div class="otp-container">
@for(digit of otpArray; let i = $index;track i) {
<input
type="text"
class="otp-input"
maxlength="1"
[id]="'otp-' + i"
[name]="'otp-' + i"
[value]="otpArray[i]"
(input)="onInput($event, i)"
(keydown)="onKeyDown($event, i)"
#otpInput
/>
}
</div>
`,
})
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();
}
checkOtpCompletion(): void {
const otpValue: string = this.otpArray.join('');
if (otpValue.length === this.otpLength) {
this.otpCompleted.emit(otpValue);
}
}
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();
}
}
}
}
@Component({
selector: 'app-root',
template: `
<app-otp-input/>
`,
imports: [OtpInputComponent],
})
export class App {
name = 'Angular';
}
bootstrapApplication(App);
