在Angular 8中,我该如何使用@ViewChild的新静态选项?

332

我该如何配置新的 Angular 8 视图子组件?

@ViewChild('searchText', {read: ElementRef, static: false})
public searchTextInput: ElementRef;

对比

@ViewChild('searchText', {read: ElementRef, static: true})
public searchTextInput: ElementRef;

哪个更好?何时应该使用 static:true vs static:false

9个回答

389

在大多数情况下,您将希望使用 {static: false}。这样设置将确保找到依赖于绑定解析的查询匹配项(如结构性指令*ngIf等)。

何时使用static: false的示例:

@Component({
  template: `
    <div *ngIf="showMe" #viewMe>Am I here?</div>
    <button (click)="showMe = !showMe"></button>
  ` 
})
export class ExampleComponent {
  @ViewChild('viewMe', { static: false })
  viewMe?: ElementRef<HTMLElement>; 

  showMe = false;
}

static: false将成为Angular 9中的默认后备行为。在此处此处了解更多。

{ static: true }选项是用于支持动态创建嵌入式视图。当您动态创建视图并希望访问TemplateRef时,您将无法在ngAfterViewInit中这样做,因为这会导致ExpressionHasChangedAfterChecked错误。将静态标志设置为true将在ngOnInit中创建视图。

然而:

在大多数其他情况下,最佳实践是使用{static: false}

但要注意,{ static: false }选项将成为Angular 9中的默认设置。这意味着不再需要设置静态标志,除非您想使用static: true选项。

您可以使用Angular CLI的ng update命令自动升级当前的代码库。

有关此内容的迁移指南和更多信息,请单击此处此处

#静态查询和动态查询有什么区别? @ViewChild()和@ContentChild()查询的静态选项确定查询结果何时可用。

对于静态查询(static: true),查询解析一旦视图创建完成但在变更检测运行之前进行。然而,结果永远不会更新以反映视图的更改,例如ngIf和ngFor块的更改。

对于动态查询(static: false),查询解析在@ViewChild()和@ContentChild()的ngAfterViewInit()或ngAfterContentInit()之后进行。结果将根据视图的更改更新,例如ngIf和ngFor块的更改。


使用static: true的一个不错的用例是,如果您正在使用fromEvent绑定到模板中定义的元素。请考虑以下模板:

<div [ngStyle]="thumbStyle$ | async" #thumb></div>

如果您不想或不能使用Angular事件绑定,那么您可以在此元素上处理事件,而无需使用订阅或初始化钩子:

@Component({})
export class ThumbComponent {
  @ViewChild('thumb', { static: true })
  thumb?: ElementRef<HTMLElement>;

  readonly thumbStyle$ = defer(() => fromEvent(this.thumb, 'pointerdown').pipe(
    switchMap((startEvent) => fromEvent(document, 'pointermove', { passive: true })
    // transform to proper positioning
  ));
}

使用defer很重要。这将确保该observable只有在订阅时才被解析。这会在ngAfterViewInit被触发之前发生,当async管道订阅它时发生。由于我们使用了static:true,所以this.thumb已经填充好了。


请更新 Angular 文档的链接(发布后已更改)https://angular.io/api/core/ViewChild#description - Sachin Gupta
2
我无法访问childView实例,它一直显示未定义。 - Nesan Mano
@AlexMarinov 我已更新答案,以便更清楚地说明 Angular 9 中将发生的事情。有关此内容的链接在迁移指南中。 - Poul Kruijt
如果我将组件嵌套在 *ngIf 中,并且该块仅在稍后设置为 true,那么我如何获取 ViewChild - Minh Nghĩa
2
@MinhNghĩa 如果您将整个组件嵌套在组件模板之外,您可以使用 { static: true },但如果在 ngOnInit 中没有直接需要访问 ViewChild,则应仅使用 { static: false } - Poul Kruijt
显示剩余3条评论

189

以下是一些经验法则:

  • 当您想在ngOnInit中访问ViewChild时,需要设置{ static: true }

  • 只有当您的模板中有结构性指令(即*ngIf)时,才能在ngAfterViewInit中访问{ static: false }


6
注意:在Angular 9中,静态标志默认为false,因此“任何{static:false}标志都可以安全地删除”。文档:https://angular.io/guide/static-query-migration。 - Stevethemacguy

24

静态属性告知 Angular 我们的子组件的可用性。

例如:如果将 static 设置为true,我们告诉 Angular 我们的子组件从一开始就在页面上可用(这意味着它不依赖于 *ngIf、页面绑定、API 调用等),因此 Angular 在最早的生命周期钩子(ngOnInit)中寻找它,以后就不再查找了。

如果我们将 static 设置为false,则告诉 Angular 我们的子组件依赖于某些条件指令,因此 Angular 尝试在每个变更检测周期后寻找我们的子组件,如果可用,我们可以在 ngAfterViewInit() 生命周期钩子中访问它。


这是完全相反的方式:{static: true} Angular 只检查一次(在组件的早期阶段 - 这就是为什么你可以在 ngOnInit 中获取它),而且该元素不能处于 *ngIf 等之下。 - Eliseo
这对我来说是最清晰的解释。谢谢你。 - Lani

23

来自Angular 文档

static - 是否在变更检测运行之前解析查询结果(即仅返回静态结果)。如果未提供此选项,则编译器将回退到其默认行为,即使用查询结果确定查询解析的时间。 如果任何查询结果位于嵌套视图内(例如 *ngIf),则查询将在变更检测运行后解析。否则,它将在变更检测运行之前解析。

如果子元素不依赖于任何条件,则使用static: true 可能是一个更好的选择。如果元素的可见性发生更改,则static: false 可能会产生更好的结果。

PS:由于这是一个新功能,我们可能需要针对性能进行基准测试。

编辑

正如 @Massimiliano Sartoretto 所提到的,GitHub 提交 可能会给您带来更多启示。


3
我会添加这个功能背后的官方动机 https://github.com/angular/angular/pull/28810 - Massimiliano Sartoretto

3
在ng8中,你可以在父组件中手动设置何时访问子组件。当你将static设置为true时,它意味着父组件仅在onInit钩子中获取组件的定义: 例如:
 // You got a childComponent which has a ngIf/for tag
ngOnInit(){
  console.log(this.childComponent);
}

ngAfterViewInit(){
  console.log(this.childComponent);
}

如果 `static` 属性为 false,那么你只能在 `ngAfterViewInit()` 中获取定义,而在 `ngOnInit()` 中则会得到 `undefined`。

3

升级到Angular 8后,在ngOnInit中一个ViewChild为空时来到这里。

静态查询在ngOnInit之前填充,而动态查询(static:false)在之后填充。换句话说,如果你设置了static:false,现在在ngOnInit中一个viewchild为空,你应该考虑改为static:true或将代码移到ngAfterViewInit中。

请参见https://github.com/angular/angular/blob/master/packages/core/src/view/view.ts#L332-L336

其他答案是正确的,并解释了为什么会这样:依赖于结构指令的查询,例如ngIf内部的ViewChild引用,应该在该指令的条件已经解决后运行,即在变更检测之后。然而,对于非嵌套引用,可以安全地使用static:true并在ngOnInit之前解决查询。我认为这个特定情况值得一提,因为空异常很可能是你遇到这个特殊情况的第一种方式,就像我一样。


2

查看子元素

...可以用于模板元素引用。

...用于特定组件之外的引用。

使用装饰器风格语法。 @ViewChild(选择器) 引用 : ElementRef || QueryList。

特定组件或元素的引用。

AfterViewInIt()中使用它。

我们可以在Oninit()中使用它。

但这是特定于使用ngAfterViewInit()

最后,{static : false}应该放置在@ViewChild( Useme , { static : false})中...用于模板变量引用。

模板文件中的变量如下所示。 #Useme


2

查看子组件 @angular 5+ token 两个参数 ('局部引用名称',静态: false|true)

@ViewChild('nameInput', { static: false }) nameInputRef: ElementRef;

若想了解如何区分真正和错误的检查,请看这里

static - 是否在变更检测运行之前解析查询结果(即仅返回静态结果)。如果未提供此选项,编译器将回退到其默认行为,即使用查询结果确定查询分辨率的时机。如果任何查询结果位于嵌套视图中(例如 *ngIf),则查询将在变更检测运行后解析。否则,在变更检测运行之前解析。


0
如果我们声明 static: true,它将在开始时仅渲染一次。否则,如果我们声明 static: false,这意味着我们以动态方式进行渲染,因此每当发生更改时,它都会再次渲染。大多数情况下,声明 static: false 是安全的。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接