为啥只有当我从 iframe 调用 postMessage() 方法时我的 Angular 属性才会更新

Posted

技术标签:

【中文标题】为啥只有当我从 iframe 调用 postMessage() 方法时我的 Angular 属性才会更新【英文标题】:Why does my Angular attribute is updated only when I call a postMessage() method from my iframe为什么只有当我从 iframe 调用 postMessage() 方法时我的 Angular 属性才会更新 【发布时间】:2022-01-17 02:48:54 【问题描述】:

我有一个名为 counter 的属性,我希望每次单击 iframe 中的按钮时都将其增加 1。

这是我的代码:https://stackblitz.com/edit/angular-fznrnf?file=src/app/app.component.ts

app.component.ts

export class AppComponent implements AfterViewInit 
  counter = 0;

  @HostListener('window:message', ['$event']) onPostMessage() 
    console.log('Message received');
  

  ngAfterViewInit(): void 
    let iframe = document.getElementById('iframe') as htmlIFrameElement;

    iframe?.addEventListener('load', () => 
      let iframeWindow = iframe.contentWindow;

      if (iframeWindow) 
        const doc = iframeWindow?.document;

        doc.getElementById('iframeButton')?.addEventListener('click', () => 
          this.counter++;

          window?.postMessage('update ?', '*');
        );
      
    );
  

app.component.html

<iframe
  id="iframe"
  srcdoc="<button id='iframeButton'>Increase my counter !</button>"
></iframe>

<p>Counter =  counter </p>

现在,我想了解为什么我需要 window?.postMessage('update ?', '*');@HostListener 来增加我的 counter,具体来说,真正在幕后发生了什么。

【问题讨论】:

【参考方案1】:

postMessage 是一个 Web API,它允许窗口之间的通信。因此,如果您有一个网页和一个使用 postMessage 的 iframe,您可以促进这两个窗口之间的通信。

我玩过你的项目。当您注释掉 window?.postMessage('update ?', '*'); 并单击按钮时,AppComponent 上的计数器属性会增加,但 Angular 没有响应更改。

我的猜测是增量是在 Angular 的变更检测系统之外运行的。另一方面,当您还执行postMessage 时,更改检测会正确触发,因为@HostListener('window:message', ['$event']) 会处理此问题。

如果您按如下方式更新组件,它将在AppComponent 中显示counter 属性的增量。

export class AppComponent implements AfterViewInit 
  counter = 0;

  constructor(public cd: ChangeDetectorRef) 

  @HostListener('window:message', ['$event']) onPostMessage() 
    console.log('Message received');
  

  ngAfterViewInit(): void 
    let iframe = document.getElementById('iframe') as HTMLIFrameElement;

    iframe?.addEventListener('load', () => 
      let iframeWindow = iframe.contentWindow;

      if (iframeWindow) 
        const doc = iframeWindow?.document;

        doc.getElementById('iframeButton')?.addEventListener('click', () => 
          this.counter++;
          this.cd.detectChanges();
          // window?.postMessage('update ?', '*');
        );
      
    );
  

调用 this.cd.detectChanges(); 将明确要求 Angular 运行更改检测,因此将显示计数器更新。


扩展答案:

将尝试更详细地解释我认为幕后发生的事情。

您的应用程序有两个“上下文”。主窗口,Angular 应用程序在其中运行,然后是 iframe 窗口,它只是纯 HTML。

Per article about change detection你在评论中提到,在主窗口Angular正在修补浏览器API,例如addEventListenersetTimeout等,以便能够检测到应该在视图中呈现的变化.

但是 iframe 窗口没有修补它的 API。

因此,如果您单击 iframe 中的按钮以增加 counter,则视图不会更新(尽管 AppComponent 属性会更新)。您的 iframe 中的单击处理程序未修补以触发 Angular 更改检测和更新视图。

为了让 Angular 知道,视图中的某些内容是从“外部”更新的,我们需要通过 this.cd.detectChanges(); 显式调用更改检测。

但是,如果您从 iframe 窗口执行 postMessage,则会触发主窗口中的消息处理程序 onPostMessage()。主窗口中的消息处理程序由 Angular 修补,因此当收到消息事件时,更改检测被启动,检测到 counter 增量并更新视图。

【讨论】:

这里的关键字似乎是“角度变化检测系统”,而 ChangeDetectorRef 对此有所帮助,所以谢谢!我发现这篇 Angular 博文 blog.angular-university.io/… 解释了其中的一部分。所以,虽然我仍然不知道到底发生了什么,但我明白了一点^^ 您附加的链接很好地解释了更改检测。因此,我将尝试扩展我的答案,让我知道它是否能让更多人了解正在发生的事情。 不幸的是,我发现了一个 postMessage() 有效的情况,但 detectChanges() 没有...但它可能是另一个问题。 答案更新了,是不是更清楚了,现在是怎么回事? 是的,它更清楚了,谢谢,我想我在阅读文章时理解了同样的事情。我现在遇到的事情有点复杂(stackblitz 的例子很简单,我认为它完全代表了我想要的,但似乎它没有)。我的问题是关于 Mozilla 的 PDF.js 查看器解决方案的滚动事件,它使用 iframe,我想从 Angular 组件自定义。我将尝试将我的确切问题放在 stackblitz 上,但我不确定我是否可以,因为 PDF.js 查看器包含一些大文件。然后我可能会问另一个问题^^'

以上是关于为啥只有当我从 iframe 调用 postMessage() 方法时我的 Angular 属性才会更新的主要内容,如果未能解决你的问题,请参考以下文章

当我从另一个视图控制器调用时,为啥我的表视图为零?

当我从单独的视图控制器中删除核心数据实体时,为啥会调用 UITableView 方法?

iframe跨域通信方案

Vue2:当我从响应中分配属性时,为啥会收到无限的 $http.get 请求?

iframe与父页面之间通讯跨域问题

为啥重新加载 tableView 不能快速工作?