【问题标题】:How to properly detect a change in computed style property with Angular 2?如何使用 Angular 2 正确检测计算样式属性的变化?
【发布时间】:2017-09-25 07:43:09
【问题描述】:

我创建了一个 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 css angular dom getcomputedstyle


    【解决方案1】:

    见顶图here查看生命周期图。如果你在 doCheck 中的钩子不起作用,那么 AfterViewChecked 也不会起作用,因为 doCheck 会启动 AfterViewChecked。

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

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

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

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

    【讨论】:

    • 谢谢,我现在明白任何异步进程都会调用ngDoCheckngAfterViewChecked,其中setTimeout是一种可能性,无论setTimeout调用什么函数(即使没有我的情况)。顺便说一句,这不是一个无限循环,因为当不再检测到更改时,我不再发出setTimeout但是,您的其余答案似乎是基于误解,因为我不想在父组件和子组件之间进行通信。我正在尝试仅获取有关我自己组件的计算样式更改的通知。
    • 您是否要根据另一个组件更改此处显示的组件的位置/大小?
    • 是的,没错,但是另一个组件没有改变,因为它从一开始就给出了一个不可变的属性值,所以这不是我正在尝试的问题解决。我需要检测的是我的 own 组件的计算样式的变化。即使我使用常量而不是其他组件的属性值,问题也会一样。这就是为什么我没有在示例代码中包含该部分。
    • 我觉得我解释得不好,我很抱歉。您希望显示的组件对另一个组件做出反应。您有办法检查它需要调整大小的值,但正试图找到一种方法让它知道它需要调整大小。为此,您需要了解显示的组件及其响应的组件的状态。解决方案是拥有一个可以在两者之间发送信息的组件。这是一个父母和两个孩子的情况。在这种情况下,父组件会在其他组件初始化时告诉显示的组件 updateView()。
    • 我讨厌这么坚持,但恐怕我认为我们仍然不是在谈论同一个问题。正如您所假设的,我 不是 '试图找到一种方法让它知道它需要调整大小'。我知道如何让我的组件知道它需要如何调整大小,但我需要一种方法让 Angular 和/或 DOM me 知道这个计算的样式已经改变,或者 至少 给 me 机会自己找出来(通过调用我的钩子)。正如您在示例代码中看到的那样,我可以检查更改,但除非我使用 setTimeout 技巧,否则不会调用我的函数。
    【解决方案2】:

    在原生 JavaScript 中你可以使用:

    MutationObserver

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

    MutationObserver 为开发人员提供了一种对 一个 DOM。它被设计为替代定义的突变事件 DOM3 事件规范。

    【讨论】:

    • 对不起,我知道这不是您问题的完整答案,但我希望它至少可以部分有用:)
    • 谢谢,但我已经尝试过observe(this.elementRef.nativeElement, { attributes: true }),它给了我多个回调,其中 oldValue=null、newValue=undefined 仅此而已。
    • 我实际上怀疑是否会检测到 DOM 更改。我假设 DOM 没有改变,但只有 getComputedStyle 函数会计算出不同的结果。 ?!
    • 虽然这是一个有趣的选项,但经过一些研究,我得出的结论是 MutationObserver 目前不支持观察计算的样式属性。另见问题Can I use a MutationObserver to listen for changes on computed styles?
    • 是的,它不监听计算的样式更改,作为 CSS OM 的一部分。有什么办法可以得到计算的样式变化?
    【解决方案3】:

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

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

    为什么使用 setTimeout 会起作用?

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

    Angular 监听计算样式变化的方法是什么?

    除了使用setTimeout 来观察DOM 元素的计算样式的变化,似乎别无他法。

    【讨论】:

      猜你喜欢
      • 2017-08-15
      • 1970-01-01
      • 2019-06-09
      • 2017-02-15
      • 2017-05-04
      • 2017-02-09
      • 1970-01-01
      • 1970-01-01
      • 2019-11-12
      相关资源
      最近更新 更多