Tengo un atributo llamado counter que quiero que aumente en 1 cada vez que hago clic en un botón en mi iframe.
Aquí está mi código: 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> Ahora, quiero entender por qué necesito la window?.postMessage('update ?', '*'); y @HostListener para que mi counter aumente, y específicamente, lo que realmente sucede detrás de escena.
postMessage es una API web que permite la comunicación entre ventanas. Entonces, si tiene una página web y un iframe usando postMessage , puede facilitar la comunicación entre esas dos ventanas.
Jugué un poco con tu proyecto. ¿Cuando comentas en la window?.postMessage('update ?', '*'); y al hacer clic en el botón, la propiedad del contador en AppComponent se incrementa, pero Angular no responde al cambio.
Supongo que ese incremento se está ejecutando fuera del sistema de detección de cambios de Angular. Por otro lado, cuando también haces un postMessage , la detección de cambios se activa correctamente, porque @HostListener('window:message', ['$event']) se encargará de eso.
Si actualiza su componente de la siguiente manera, mostrará el incremento de su propiedad de counter en AppComponent .
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 ?', '*'); }); } }); } } Llamar this.cd.detectChanges(); le pedirá a Angular explícitamente que ejecute la detección de cambios, por lo que mostrará la actualización del contador.
Respuesta extendida:
Intentaré explicar con más detalles lo que creo que está pasando debajo del capó.
Su aplicación tiene dos "contextos". Ventana principal, en la que se ejecuta la aplicación Angular y luego ventana iframe, que es simplemente HTML.
Según el artículo sobre la detección de cambios que ha mencionado en el comentario, en la ventana principal, Angular está parcheando la API del navegador, como addEventListener , setTimeout , etc. para poder detectar cambios, que deberían representarse en la vista.
Pero la ventana iframe no tiene su API parcheada.
Entonces, si hace clic en el botón dentro del iframe para incrementar el counter , la vista no se actualiza (aunque la propiedad AppComponent sí). El controlador de clics en su iframe no está parcheado para activar la detección de cambios angulares y actualizar la vista.
Para que Angular sepa que algo en la vista se actualizó desde "el exterior", debemos llamar a la detección de cambios explícitamente por this.cd.detectChanges(); .
Pero si hace un postMessage desde la ventana iframe, se activa un controlador de mensajes onPostMessage() en la ventana principal. El controlador de mensajes en la ventana principal está parcheado por Angular, por lo que cuando se recibe el evento del mensaje, se inicia la detección de cambios, se detecta el incremento del counter y se actualiza la vista.