当 Angular 组件可能被 ngIf 删除时,如何从打字稿中引用它?

How do I reference an Angular component from typescript when it may be removed by ngIf?

我有一个使用 Angular Material 中的 MatInput 的模板。我正在尝试使用 @ViewChild 从代码访问此组件,因此我可以通过编程方式更改 focused 状态,但是当视图初始化时此 MatInput 不存在 - 它的存在由 *ngIf 指令。基本上,当视图加载时,有一个带有一些文本的 p 元素。如果用户单击该元素,它将被替换为 input,其中起始值是原始元素的文本。当它失去焦点时,它会被保存并恢复为 p 元素。问题是,当他们第一次单击文本进行更改时,创建的 input 没有焦点 - 他们必须再次单击它才能开始编辑。我希望他们能够单击一次并开始输入。

这是我的相关代码。

模板:

<mat-form-field *ngIf="selected; else staticText" class="full-width">
  <input matInput type="text" [(ngModel)]="text" (blur)="save()">
</mat-form-field>
<ng-template #staticText>
  <p class="selectable" (click)="select()">{{ text }}</p>
</ng-template>

打字稿:

import { Component, Input, Output, EventEmitter, ViewChild } from '@angular/core';
import { MatInput } from '@angular/material';
import { AfterViewInit } from '@angular/core/src/metadata/lifecycle_hooks';

@Component({
  selector: 'app-click-to-input',
  templateUrl: './click-to-input.component.html',
  styleUrls: ['./click-to-input.component.scss']
})
export class ClickToInputComponent implements AfterViewInit {
  @Input() text: string;
  @Output() saved = new EventEmitter<string>();
  @ViewChild(MatInput) input: MatInput;

  selected = false;

  ngAfterViewInit(): void {
    console.log(this.input); // shows undefined - no elements match the ViewChild selector at this point
  }

  save(): void {
    this.saved.emit(this.text);
    this.selected = false;
  }

  select(): void {
    this.selected = true;  // ngIf should now add the input to the template
    this.input.focus();    // but input is still undefined
  }
}

来自文档:

You can use ViewChild to get the first element or the directive matching the selector from the view DOM. If the view DOM changes, and a new child matches the selector, the property will be updated.

*ngIf 是否工作太慢,我试图在 属性 更新之前过早地访问 this.input?如果是这样,我如何才能等到 *ngIf 完成替换 DOM 然后访问 MatInput?或者是否有其他方法可以完全解决我只是没有看到的聚焦问题?

您确定选择器匹配相同的输入吗?

另一种方法是像这样将输入声明为模板变量

<input matInput #myInput type="text" [(ngModel)]="text" (blur)="save()">

并在您的组件中访问它作为

@ViewChild('#myInput') input: MatInput

或者可以使用 setTimeout()

<input matInput id="myInput" type="text" [(ngModel)]="text" (blur)="save()">

setTimeout(() => {
  document.getElementById('myInput').focus();
});

您可以使用 @ViewChildren 装饰器获取 QueryList 并订阅 changes observable 以在 DOM 实际更新时获取更新。

import { Component, Input, Output, EventEmitter, ViewChildren } from '@angular/core';
import { MatInput } from '@angular/material';
import { AfterViewInit } from '@angular/core/src/metadata/lifecycle_hooks';

@Component({
  selector: 'app-click-to-input',
  templateUrl: './click-to-input.component.html',
  styleUrls: ['./click-to-input.component.scss']
})
export class ClickToInputComponent implements AfterViewInit {
  @Input() text: string;
  @Output() saved = new EventEmitter<string>();
  @ViewChildren(MatInput) input: QueryList<MatInput>;

  selected = false;

  ngAfterViewInit(): void {
    // Will get called everytime the input gets added/removed.
    this.input.changes.subscribe((list: any) => {
        if (!this.selected || list.first == null)
            return;

        console.log(list.first);
        list.first.focus();
    });
  }

  save(): void {
    this.saved.emit(this.text);
    this.selected = false;
  }

  select(): void {
    this.selected = true;  // ngIf should now add the input to the template
  }
}

我在 this stackblitz. After setting this.selected = true, Angular has to perform change detection to display the mat-form-field element, and that would normally happen after the current execution cycle. One way to get immediate access to the input element is to trigger change detection in your code, for example with ChangeDetector.detectChanges (see 中复制了您的案例用于其他技术):

import { Component, ChangeDetectorRef, ViewChild } from '@angular/core';
import { MatInput } from '@angular/material';

@Component({
  ...
})
export class FormFieldPrefixSuffixExample {

  @ViewChild(MatInput) input: MatInput;

  text = "Hello world!"
  selected = false;

  constructor(private changeDetector: ChangeDetectorRef) {
  }

  select(): void {
    this.selected = true;
    this.changeDetector.detectChanges();
    this.input.focus();
  }  
}

另一种解决方法 是通过使其异步来延迟对 this.input.focus() 的调用:

  select(): void {
    this.selected = true;
    setTimeout(() => {
      this.input.focus();
    }, 0);
  }  

ViewChild 更新 - 由于 ViewChild 发生变化

@ViewChildren(MatInput) input: QueryList<MatInput>;

已经不够了。现在,您必须使用:

@ViewChildren(MatInput, {static: false}) input: QueryList<MatInput>;

查看 https://angular.io/api/core/ViewChild 了解更多信息。