🚀
Angularコンポーネントをiframe内に表示する
かなりエッジケースだと思いますが、最近必要になったので一応メモしておきます。
検討
基本的にiframeにコンテンツを埋め込む場合、srcにリソースのURIを指定するかHTMLのテキストを直接srcdocに突っ込むかの二択になります。
しかし今回はアプリケーション内からiframeへコンテンツを突っ込みたかったためsrcは少し手間ですし、srcdocの方はangularのコンポーネントからスタイルを含めたHTMLを取り出そうと色々と試行錯誤してみましたが上手く取り出せなかったため断念しました。
もしかしたら他に上手いやり方があるのかもしれませんが・・・。
(直接タグにインラインスタイルで指定したり、インラインスタイルテンプレートやリンクタグテンプレートを試してみましたが上手くいきませんでした)
解決策
やっていることとしては、特に難しくありません。
コンポーネントのインスタンスを作成してその要素をallow-scripts指定したiframe配下のBody要素にappendしているだけです。
iframe-view.component.html
<iframe #iframe-element sandbox="allow-scripts"></iframe>
iframe-view.component.ts
import { IframeContentComponent } './iframe-content.component'
@Component({
standalone: true,
selector: 'iframe-view-component',
templateUrl: './iframe-view-content.component.html',
imports: [CommonModule]
})
export class IframeViewComponent implements AfterViewInit {
private readonly viewContainerRef = inject(ViewContainerRef);
private readonly _contentComponent: ComponentRef<IframeContentComponent>;
@ViewChild('iframe-element', {static:false})
_iframeRef: ElementRef<HTMLIFrameElement>;
private document: HTMLIFrameElement['contentWindow']['document'];
ngAfterViewInit(){
this.document = this._iframeRef?.nativeElement.contentWindow.document;
this._contentComponent = this.viewContainerRef.createComponent(IframeContentComponent);
this._contentComponent.instance.setData('test');
this._contentComponent.changeDetectorRef.detectChanges();
this.document.body.appendChild(this._contentComponent.location.nativeElement);
}
}
iframe-content.component.ts
@Component({
standalone: true,
template: `<div>{{ data }}</div>`,
styles: ['div { color: green;}']
encapsulation: ViewEncapsulation.ShadowDom,
imports: [CommonModule],
})
export class IframeContentComponent {
private data;
setData(data:string){
this.data = data;
}
}
当初appendした要素に対してスタイルが適用されずかなり混乱しましたが、これはデフォルトでencapsulationの設定がEmulatedになっているためです。ShadowDomを指定すると上手くいきました。
ここに関しては調べると同じようなことをしている人がいたため参考にしました。
どうもiframeがAngularの世界の外側であることでEmulateするための処理が上手く行かないためではないかという気がしています。
Discussion