如何使用 Angular 2 正确检测计算样式 属性 的变化?

How to properly detect a change in computed style property with Angular 2?

我创建了一个 Angular 2 应用程序,其中包含一个组件,我试图将其 HTML 元素相对于另一个组件的元素进行定位。为了计算准确的坐标,我需要知道浏览器呈现的我自己的元素的宽度和高度。

我正在使用 window.getComputedStyle(this.elementRef.nativeElement) 来获取这些属性。我注意到,随着页面的呈现,属性不断变化,直到页面呈现完成。为了了解任何更改并调整定位,我检查了我组件的 ngAfterViewChecked 方法中的值。

但是,似乎 ngAfterViewChecked 而不是 当渲染导致新的计算样式属性时被调用,正如我发现的我的钩子不再被调用,即使计算的样式属性仍在变化。我假设 Angular 框架不是为了检测这种变化而设计的。

我的第一次尝试是实现 ngDoCheck 挂钩,但似乎这个挂钩在一段时间后和计算样式 属性 具有最终值之前都没有被调用。我假设我还没有完全理解这个钩子究竟应该在什么时候被调用。

我终于发现,如果我在 ngAfterViewChecked 中实现一个 setTimeout 函数,这会导致稍后再次调用同一个钩子,即使我只传递一个虚拟函数,如:

setTimeout(() => {}, 500);

但坦率地说,我不明白为什么会这样。 有人可以向我解释一下 setTimeoutngAfterViewChecked 钩子之间的联系吗?

虽然这恰好是一个有点肮脏的解决方法:检测和处理计算样式属性更改的正确 'Angular' 方法是什么?


代码摘录:

export class MyComponent implements OnInit, AfterViewChecked
{
  private cssWidth: number;
  private cssHeight: number;

  constructor(private elementRef: ElementRef, private renderer: Renderer2)
  {
  }

  ngAfterViewChecked()
  {
    console.log("ngAfterViewChecked");
    this.updateView();
  }

  public updateView()
  {
    const sizeX = parseFloat(window.getComputedStyle(this.elementRef.nativeElement).width) || 0;
    const sizeY = parseFloat(window.getComputedStyle(this.elementRef.nativeElement.height) || 0;

    if (sizeX === this.cssWidth && sizeY === this.cssHeight) {
      // no change
      return;
    }

    console.log("Change detected!");

    // TODO Does not work without this dummy timeout function (else no more changes detected) - why so??
    setTimeout(() => {}, 500);

    [.. doing the positioning here ..]
 }

在原版中 JavaScript 你可以使用:

MutationObserver

并在进行 DOM 更改时触发您自己的函数。

MutationObserver provides developers with a way to react to changes in a DOM. It is designed as a replacement for Mutation Events defined in the DOM3 Events specification.

查看顶部图表 here 以查看生命周期图表。如果您在 doCheck 中的挂钩不起作用,则 AfterViewChecked 也将不起作用,因为 doCheck 会启动 AfterViewChecked。

每次异步进程完成时,都会发生另一轮 doCheck。那会调用 AfterViewChecked,然后调用您的 updateView 函数。 SetTimout() 是一个异步进程。我怀疑上面的代码会原样无限循环。

我相信您想要的是让您在此处显示的内容的父组件使用 ngAfterViewChecked() 并调用 updateView()。探讨了该主题 here

使两个组件协同工作的 Angular 方法是让父组件可以在子组件之间传达更改,因为父组件是可以看到子组件中发生的事件的上下文。

我个人使用的解决方案是 EventEmitter,在未显示的组件中,在 ngOnInit() 函数中。它会通知父级它已初始化。然后,在父组件中,有一个函数调用发生在发出的事件上,告诉子组件你在这里更新视图。

当渲染导致新的计算样式属性时,为什么不调用 ngAfterViewChecked?

Angular 框架不会检测 DOM 元素的计算样式的变化,至少当它们是由浏览器的渲染引擎引起时不会。因此,在这种情况下不会调用 ngAfterViewChecked 挂钩。

为什么在使用setTimeout时有效?

正如 Michael Palmer 的回答中所解释的那样,setTimeout 导致 Angular 意识到 可能 导致更改的异步进程。因此,ngAfterViewChecked 钩子在执行超时函数后被调用。 Angular 不会尝试检查 setTimeout 函数中到底做了什么以及这是否实际影响了视图。显然,这将很难实现,可能不值得付出努力。相反,Angular 调用 ngAfterViewChecked 挂钩 以防万一 在影响视图的异步进程中发生某些事情。

监听计算样式更改的 Angular 方法是什么?

除了使用 setTimeout 似乎没有其他方法来观察 DOM 元素的计算样式的变化。