Обзор:
Я использую мат-гармошку, которая содержит 3 элемента панели расширения матов. Каждая панель расширения имеет поле формы, и я хотел бы установить фокус на соответствующем поле формы всякий раз, когда открывается соответствующая панель. Он предназначен для использования с оптическим сканером штрих-кода. Рабочий процесс для конечного пользователя выглядит следующим образом:
- Нажмите на ссылку и перейдите на эту страницу приложения.
- Панель 1 аккордеона открыта, и поле Ticket Barcode автоматически получает начальный фокус.
- Сканирование первого штрих-кода; Сканер настроен на возврат CR в конце данных.
- CR запускает панель 2, и поле Штрих-код браслета должно получить фокус.
- Сканировать второй штрих-код.
- CR запускает панель 3, на которой отображаются данные, связанные с отсканированными штрих-кодами.
- Нажмите «Подтвердить», чтобы отправить отсканированные штрих-коды и сбросить форму.
Наши Требования UX диктуют, что шаги 2-6 не должны требовать от пользователя использования клавиатуры или мыши - только сканер штрих-кода. Это означает, что нам нужно программно обрабатывать фокус на этих полях. Есть и другие элементы этого интерфейса, но я убрал нерелевантные логи c из кода ниже.
ng --version
Angular CLI: 8.3.23
Node: 12.14.1
OS: win32 x64
Angular: 8.2.14
... animations, common, compiler, compiler-cli, core, forms
... language-service, platform-browser, platform-browser-dynamic
... router
Package Version
-----------------------------------------------------------
@angular-devkit/architect 0.803.23
@angular-devkit/build-angular 0.803.23
@angular-devkit/build-optimizer 0.803.23
@angular-devkit/build-webpack 0.803.23
@angular-devkit/core 8.3.23
@angular-devkit/schematics 8.3.23
@angular/cdk 8.2.3
@angular/cli 8.3.23
@angular/flex-layout 8.0.0-beta.27
@angular/material 8.2.3
@ngtools/webpack 8.3.23
@schematics/angular 8.3.23
@schematics/update 0.803.23
rxjs 6.4.0
typescript 3.5.3
webpack 4.39.2
scan.component. html
<mat-accordion class="redemption-accordion">
<!-- panel 1 -->
<mat-expansion-panel [expanded]="step===1" (opened)="setStep(1)" hideToggle>
<mat-expansion-panel-header>
<mat-panel-title>
1. Scan Ticket
</mat-panel-title>
</mat-expansion-panel-header>
<mat-grid-list cols="1" rowHeight="42px" gutterSize="3px">
<mat-grid-tile>
<mat-form-field appearance="legacy">
<mat-label>Ticket Barcode</mat-label>
<input #ticketBarcodeField matInput name="ticketBarcodeField" [(ngModel)]="ticketBarcode" (keyup.enter)="onTicketEnter()" required>
</mat-form-field>
</mat-grid-tile>
</mat-grid-list>
<mat-action-row>
<button mat-raised-button color="primary" (click)="nextStep()">Next <mat-icon>arrow_forward</mat-icon></button>
</mat-action-row>
</mat-expansion-panel>
<!-- panel 2 -->
<mat-expansion-panel [expanded]="step===2" (opened)="setStep(2)" hideToggle>
<mat-expansion-panel-header>
<mat-panel-title>
2. Scan Wristband
</mat-panel-title>
</mat-expansion-panel-header>
<mat-grid-list cols="1" rowHeight="42px" gutterSize="3px">
<mat-grid-tile>
<mat-form-field appearance="legacy">
<mat-label>Wristband Barcode</mat-label>
<input #wristbandBarcodeField matInput name="wristbandBarcodeField" [(ngModel)]="wristbandBarcode" (keyup.enter)="onWristbandEnter()" required>
</mat-form-field>
</mat-grid-tile>
</mat-grid-list>
<mat-action-row>
<button mat-raised-button color="primary" (click)="prevStep()"><mat-icon>arrow_back</mat-icon> Back</button>
<button mat-raised-button color="primary" (click)="nextStep()">Next <mat-icon>arrow_forward</mat-icon></button>
</mat-action-row>
</mat-expansion-panel>
<!-- Panel 3 -->
<mat-expansion-panel [expanded]="step===3" (opened)="setStep(3)" hideToggle>
<mat-expansion-panel-header>
<mat-panel-title>
3. Confirm
</mat-panel-title>
</mat-expansion-panel-header>
<mat-action-row>
<button mat-raised-button color="primary" (click)="prevStep()"><mat-icon>arrow_back</mat-icon> Back</button>
<button mat-raised-button color="warn" (click)="resetForm()"><mat-icon>clear</mat-icon> Cancel</button>
<button mat-raised-button class="greenButton" [disabled]="!canSubmit" (click)="submitTransaction()">Confirm <mat-icon>arrow_forward</mat-icon></button>
</mat-action-row>
</mat-expansion-panel>
</mat-accordion>
scan.component.ts
import { Component, ViewChild, AfterViewInit, ElementRef } from '@angular/core';
import { MatInput } from '@angular/material/input';
import { NgxSpinnerService } from "ngx-spinner";
@Component({
selector: 'app-scan',
templateUrl: './scan.component.html',
styleUrls: ['./scan.component.scss']
})
export class WristbandSwapComponent implements AfterViewInit {
private canSubmit: boolean;
private step: number;
private ticketBarcode: string;
private wristbandBarcode: string;
@ViewChild('ticketBarcodeField', {read:MatInput, static:false}) public ticketBarcodeField: MatInput;
@ViewChild('wristbandBarcodeField', {read:ElementRef, static:false}) private wristbandBarcodeField: ElementRef;
constructor(
private spinner:NgxSpinnerService
) {
this.canSubmit = false;
this.step = 0;
this.ticketBarcode = '';
this.wristbandBarcode = '';
}
ngAfterViewInit() {
console.log("Running ngAfterViewInit...");
// components should be defined at this point
console.log("this.ticketBarcodeField:", this.ticketBarcodeField);
console.log("this.wristbandBarcodeField:", this.wristbandBarcodeField);
// make sure panel 1 is expanded
this.setStep(1);
}
// triggered when a CR is detected in the ticketBarcode field
onTicketEnter() {
this.spinner.show('primary');
// Logic goes here to check the scanned ticket barcode
this.spinner.hide('primary');
this.nextStep();
}
// triggered when a CR is detected in the wristbandBarcode field
onWristbandEnter() {
this.spinner.show('primary');
// Logic goes here to check the scanned wristband barcode
this.spinner.hide('primary');
this.nextStep();
}
// triggered when an accordion panel is explicitly opened
setStep(index: number) {
console.log("Setting step to " + index);
this.step = index;
this.doFocus();
}
// triggered by "next" arrows on the accordion
nextStep() {
console.log("Called nextStep...");
this.step++;
this.doFocus();
}
// triggered by "back" arrows on the accordion
prevStep() {
console.log("Called prevStep...");
this.step--;
this.doFocus();
}
// triggered by "Cancel" button
resetForm() {
this.ticketBarcode = '';
this.wristbandBarcode = '';
this.ticket = null;
this.setStep(1);
}
// When an accordion panel is open, make sure its field has focus
doFocus() {
if (this.step === 1) {
console.log("Setting focus on ticketBarcodeField");
// MatInput provides a focus method
this.ticketBarcodeField.focus();
} else if (this.step === 2) {
console.log("Setting focus on wristbandBarcodeField");
// the nativeElement attribute of ElementRef provides the focus method
this.wristbandBarcodeField.nativeElement.focus();
}
}
// triggered when the Confirm button is clicked
submitTransaction() {
console.log("called submitTransaction");
// logic goes here to handle the scanned barcode values
}
}
вывод консоли
Вот что я вижу в своей консоли при загрузке страницы.
Running ngAfterViewInit...
this.ticketBarcodeField: MatInput {_defaultErrorStateMatcher: ErrorStateMatcher, _parentForm: null, _parentFormGroup: null, ngControl: NgModel, errorState: false, …}
this.wristbandBarcodeField: ElementRef {nativeElement: input#mat-input-4.mat-input-element.mat-form-field-autofill-control.cdk-text-field-autofill-monitor…}
Setting step to 1
Setting focus on ticketBarcodeField
И когда я открываю вторую панель расширения:
Setting step to 2
Setting focus on wristbandBarcodeField
Но ни одно поле формы никогда не получает фокус. Мои поиски в Google привели меня к мысли, что MatInput является предпочтительным методом, наряду с this.myField.focus (), но, похоже, он не работает. Может кто-нибудь сказать, пожалуйста, правильный способ установить фокус на этом поле? Что я делаю не так?