【问题标题】:Why is @ViewChild still undefined even though the *ngIf is set to true为什么即使 *ngIf 设置为 true,@ViewChild 仍然未定义
【发布时间】:2020-11-25 01:39:15
【问题描述】:

我遇到了@ViewChild 中的{static: false} 属性修复的以下问题。 这个 stackoverflow Q/A 帮助了 How should I use the new static option for @ViewChild in Angular 8?

我想更好地理解这个场景以及static 如何改变结果,以及变化检测如何影响这个场景。我在 Angular 文档中阅读了一些关于更改检测的内容,发现非常缺乏。

我想出了一个堆栈闪电战来说明我不理解的东西。 Stackblitz angular example

当点击toggle 按钮两次时,我在命令行上得到以下信息:

> undefined undefined
> undefined undefined
> undefined ElementRef {nativeElement: div}
> undefined ElementRef {nativeElement: div}

但我希望:

> undefined undefined
> undefined ElementRef {nativeElement: div}
> ElementRef {nativeElement: div} ElementRef {nativeElement: div}
> ElementRef {nativeElement: div} ElementRef {nativeElement: div}

这是代码的逻辑——(参见 stackblitz 中的完整代码)

@Component({
  selector: "my-app",
  templateUrl: "./app.component.html",
  styleUrls: ["./app.component.css"]
})
export class AppComponent {
  @ViewChild("contentPlaceholder", { static: true })
  trueViewChild: ElementRef;

  @ViewChild("contentPlaceholder", { static: false })
  falseViewChild: ElementRef;

  display = false;

  constructor() {}

  show() {
    console.log(this.trueViewChild, this.falseViewChild);
    this.display = true;
    console.log(this.trueViewChild, this.falseViewChild);
  } 
}

我的问题是:

  1. 为什么this.falseViewChild 的第二行值显示为未定义。设置 this.display = false 后不应该运行更改检测,因此它不应该是未定义的吗?
  2. 为什么this.trueViewChild 保持未定义。我希望它在 *ngIf 变为 true 后找到元素?

【问题讨论】:

    标签: angular angular-ng-if viewchild


    【解决方案1】:

    Angular 变化检测在 zone.js 库的帮助下工作。 更新 ViewChild/Content 查询发生在更改检测周期

    zone.js 库修补异步 API(addEventListener、setTimeout()、Promises...),并准确知道执行了哪个任务以及何时完成。

    例如,它可以监听点击事件并在此任务已完成时发出通知(没有待处理的任务,意味着该区域变得稳定)。

    Angular 订阅这些通知以便对从根组件开始的所有三个组件执行更改检测

    // your code 
    (click)="someHandler()" 
    
    someHandler() {              
     ....
    }
    
    // angular core
    checkStable() {
      if (there is no any task being executed and there is no any async pending request) {
        PERFORM CHANGE DETECTION
      } 
    }
    

    代码中关于的顺序如下:

     click
      ||
      \/
    someHandler()
      ||
      \/
    checkStable()
      ||
      \/
    PERFORM CHANGE DETECTION
    

    那么,让我们回答你的问题:

    1. 为什么 this.falseViewChild 的第二行值显示为未定义。设置 this.display = false 后不应该运行更改检测,因此它不应该是未定义的吗?

    更改display 属性时没有反应

    show() {
     console.log(this.trueViewChild, this.falseViewChild);
     this.display = true;  <--- Angular doesn't do here anything, it only listens to zone state changes
     console.log(this.trueViewChild, this.falseViewChild); // nothing should be updated here 
                                                           // because there wasn't any cd cycle yet
    } 
    

    这就是您在第一次点击时获得以下输出的原因:

    > undefined undefined
    > undefined undefined   <---- nothing has updated
    
     ......
     update happens here
    

    稍后会更新,但除非您再次单击,否则您不会看到此内容,因为您稍后不会记录这些值。

    1. Why does this.trueViewChild stay undefined. I would expect it to find the element after the *ngIf becomes true?

    因为Angular documentation有这个规则:

    使用静态查询(静态:true),查询会在视图中解析 已创建,但在更改检测运行之前。结果, 不过,永远不会更新以反映您的视图的更改,例如 对 ngIf 和 ngFor 块的更改。

    这意味着如果它最初是 false(例如,它在 *ngIf 或 ng-template 内部),那么它将始终是 false

    【讨论】:

    • 感谢您的深入回答。问题 #2 现在有意义了。您的分叉 stackblitz 在第一次点击后什么都没有显示,而在第二次点击后只有一些东西 [object Object]。我只是想检查一下这是否是您的预期效果?
    • 是的,你说得对,我看到了不同的行为。它也应该在第二次点击后更新视图。
    • 第一个问题还是有点不清楚。那么this.display = true; 之后的console.log(...) 是未定义的,因为 Angular 尚未从 zone.js 收到订阅的值,还是因为 zone.js 不考虑这些变化?
    • 此代码在 Angular 区域内执行,并且区域仅在此处理程序执行后才知道正在执行某些内容 Angular 检查区域的状态,并且只有在没有待处理任务时才会发出 onMicrotaskEmpty 事件。已订阅此事件以运行更改检测周期
    • 下面是 Angular 检查区域状态 github.com/angular/angular/blob/…
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-11-14
    • 2020-01-22
    • 1970-01-01
    • 1970-01-01
    • 2021-03-29
    • 1970-01-01
    • 2017-09-15
    相关资源
    最近更新 更多