首页 > 解决方案 > 处理 ViewChildren 的最佳 Angular 7 挂钩是什么

问题描述

我有一个 Angular 7 应用程序,我试图在 ngAfterViewChecked() 中处理文本输入。

文本输入是 mat-tree 中的一个节点。它的可见性取决于 ngIf 条件。如果不满足该条件,我将显示一个跨度。本质上,如果用户双击树中的一个节点(一个 span 元素),它就会变成一个文本输入,以便用户可以编辑文本:

<mat-tree [dataSource]="nestedDataSource" [treeControl]="nestedTreeControl">
  <mat-tree-node *matTreeNodeDef="let node">
    <li>
      <span *ngIf="!node.isInput" (dblClick)="nodeDoubleClicked(node)">{{ node.name }}</span>
      <input *ngIf="node.isInput" #nodeNameInput type="text" [(ngModel)]="node.name" (blur)="doneEditting(node)" (keypress)="keyPressed($event, node)" />
    </li>
  </mat-tree-node>
  <mat-nested-tree-node *matTreeNodeDef="let node; when: hasNestedChild">
    <button mat-icon-button matTreeNodeToggle>
      <mat-icon>
        {{ nestedTreeControl.isExpanded(node) ? 'expand_more' : 'chevron_right' }}
      </mat-icon>
    </button>
    <span *ngIf="!node.isInput" (dblClick)="nodeDoubleClicked(node)">{{ node.name }}</span>
    <input *ngIf="node.isInput" #nodeNameInput type="text" [(ngModel)]="node.name" (blur)="doneEditting(node)" (keypress)="keyPressed($event, node)" />
    <ul [class.collapsed]="!nestedTreeControl.isExpanded(node)">
      <ng-container matTreeNodeOutlet></ng-container>
    </ul>
  </mat-nested-tree-node>
</mat-tree>

当用户双击一个节点时,我不仅希望它变成输入文本,我希望它获得焦点并选择里面的文本。为了做到这一点,我必须获取本机元素并在其上调用 .focus() 和 .select() 。为了获得本机元素,我必须使用 ViewChildren(如您在上面的代码片段中所见,输入标记为 #nodeNameInput)。最后,我需要连接到 ngAfterViewChecked() 以确保 ViewChildren 的 QueryList 已准备好。

这是组件的代码:

@ViewChildren('nodeNameInput') nodeNameInputs: QueryList<ElementRef>;

...

ngAfterViewChecked() {
  if (this.nodeNameInputs && this.nodeNameInputs.length) {
    this.nodeNameInputs.first.nativeElement.focus();
    this.nodeNameInputs.first.nativeElement.select();
  }
}

我已确保一次只编辑一个节点,因此首先使用它是安全的,而不是通过 nodeNameInputs 搜索来找到要聚焦并选择文本的节点。

这似乎可行,但有一个问题。似乎每次击键都会调用 ngAfterViewChecked() 。这意味着当用户编辑节点的文本时,每次击键都会重新选择它。这导致用户输入的文本在每次击键时都会被覆盖。

我有一个解决这个问题的方法:

ngAfterViewChecked() {
  if (this.nodeNameInputs && this.nodeNameInputs.length) {
    this.nodeNameInputs.first.nativeElement.focus();
    if (!this.keyStroked) {
      this.nodeNameInputs.first.nativeElement.select();
    }
  }
}

...其中 keyStroked 在 keyPressed 处理程序中设置并在模糊处理程序中设置为 false。

但我想知道是否有另一个钩子可以可靠地用于聚焦输入并选择其文本而不响应​​击键。我选择 ngAfterViewChecked 是因为测试表明它是唯一一个每次都始终准备好 nodeNameInputs 的钩子(即 this.nodeNameInputs.length 始终为 1)。但也许我错过了某些钩子。

我的解决方法似乎是一个 hack。你将如何解决这个问题?

标签: javascriptangularhook

解决方案


创建一个焦点指令并将其放在您想要关注的输入上,您不必担心生命周期事件。

import { Directive, ElementRef } from '@angular/core';

@Directive({
  selector: '[focus]'
})
export class FocusDirective {

  constructor(elm: ElementRef) {
    elm.nativeElement.focus();
  }
}

并使用它

<input focus>

https://stackblitz.com/edit/angular-qnjw1s?file=src%2Fapp%2Fapp.component.html


推荐阅读