Как правильно установить фокус на поле формы в Angular Материал? ViewChild (с MatInput или ElementRef) не работает для меня - PullRequest
0 голосов
/ 06 марта 2020

Обзор:

Я использую мат-гармошку, которая содержит 3 элемента панели расширения матов. Каждая панель расширения имеет поле формы, и я хотел бы установить фокус на соответствующем поле формы всякий раз, когда открывается соответствующая панель. Он предназначен для использования с оптическим сканером штрих-кода. Рабочий процесс для конечного пользователя выглядит следующим образом:

  1. Нажмите на ссылку и перейдите на эту страницу приложения.
  2. Панель 1 аккордеона открыта, и поле Ticket Barcode автоматически получает начальный фокус.
  3. Сканирование первого штрих-кода; Сканер настроен на возврат CR в конце данных.
  4. CR запускает панель 2, и поле Штрих-код браслета должно получить фокус.
  5. Сканировать второй штрих-код.
  6. CR запускает панель 3, на которой отображаются данные, связанные с отсканированными штрих-кодами.
  7. Нажмите «Подтвердить», чтобы отправить отсканированные штрих-коды и сбросить форму.

Наши Требования 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 (), но, похоже, он не работает. Может кто-нибудь сказать, пожалуйста, правильный способ установить фокус на этом поле? Что я делаю не так?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...