本記事ではAngularElementsを使ってAMP htmlをロードするという試みをします。
AMPは、ページを高速に表示してSEOがよくなるやつですね。1
Angular Elementsについて
ブートストラップ以下のツリーを通さず直接DOMにAngularComponentを追加できるようになるです。
ユースケースは1年前の記事でごちゃごちゃ書きましたが、相変わらず2つパラレルに走っています。
Angularアプリ内でElementsを使う方法
公式ドキュメントのサンプルが現在このユースケースです。Angularアプリ内で動的コンポーネントをどうやって実現するかという方向です。サンプルでは、AngularComponentでそのままやる方法と、customElementを用いる方法の2つが併記されています。
単発のcustomElementsとしてビルドして配布し、他のアプリケーションから使用する方法
この記事が非常にクオリティ高いです。
customElementのブートストラップや他のelementとの外部インターフェースについて理解が深まります。結論としてはivy待ちですね。
「Angular Elementsことはじめ – Custom Elementsを実装する方法」
また、公式ドキュメントですが、配布用のユースケースを載せてよというイシューが上がっているため、そのうち出てきそうです。
AMP htmlをAngularElementsでロードする
- AngularElementsを使います。
- サーバからAMP htmlを読み込み、elementsのiframeにセットします。
実装
公式ドキュメントのサンプルをベースに進めていきます。
AMP表示用のcomponentを用意する
いつものようにcomponentを作ります。iframeタグを用意してsrcdoc
属性にXHRで取得したAMPhtmlソースをセットしようとしています。このcomponentは後にcusutomElement化します。そうすると@Input
がHTMLElementのproperty属性になります。
@Component({
selector: 'app-amp-display',
template: `
<iframe [srcdoc]="safeMsg"></iframe>
<button (click)="closed.next()">✖</button>
`
})
export class AmpDisplayComponent {
safeMsg: SafeHtml;
// customElement化した時にproperty属性になります。
@Input()
set message(message: string) {
this.safeMsg = this.sanitizer.bypassSecurityTrustHtml(message);
}
@Output()
closed = new EventEmitter();
constructor(private sanitizer: DomSanitizer) {}
}
customElement化する
ここは公式ドキュメントのサンプルそのままです。ソースコメントにいたっては完全にコピペです。
AppComponentのコンストラクタ内で先ほどのcomponentをNgElementという変換器にかけた上でwindow.customElements
に登録します。これでもう標準のカスタム要素になりました。DOM APIから<app-amp-display>
で操作可能です。逆にAngular内で扱おうとすると、例えばtemplateに<app-amp-display>
をセットしても様々な困難がやってきます。
@Component({
selector: 'app-root',
template: `
<button (click)="ampDisplay.showAsElement()">Show as element</button>
`
})
export class AppComponent {
constructor(injector: Injector, public ampDisplay: AmpDisplayService) {
// Convert `PopupComponent` to a custom element.
const AmpDisplayElement = createCustomElement(AmpDisplayComponent, {
injector
});
// Register the custom element with the browser.
customElements.define('amp-display-element', AmpDisplayElement);
}
}
AMPを読み込む
serviceを起こして、responseType: 'text'
でサーバからAMPソースを読み込むようにします。AMPソースは、URLを叩けば普通に表示できる<html>
タグ以下のフルhtmlドキュメントです。
@Injectable()
export class AmpDisplayService {
private ampUrl = 'http://localhost:8080/amp';
constructor(
private http: HttpClient
) {}
private getContent() {
return this.http.get(this.ampUrl, { responseType: 'text' });
}
}
Angularアプリ内からcustomElementを呼び出す
serviceにcustomElementをDOMに付与するメソッドを追加します。すでにwindowには登録済みなので、document.createElement()
で作成できるようになっています。@Input() message
がHTMLElementのproperty属性として変換されているため、取得したAMPソースを渡してやります。
@Injectable()
export class AmpDisplayService {
private ampUrl = 'http://localhost:8080/amp';
constructor(
private injector: Injector,
private http: HttpClient
) {}
// This uses the new custom-element method to add the popup to the DOM.
showAsElement() {
// Create element
const ampDisplayEl: NgElement &
WithProperties<AmpDisplayComponent> = document.createElement(
'amp-display-element'
) as any;
// Listen to the close event
ampDisplayEl.addEventListener('closed', () =>
document.body.removeChild(ampDisplayEl)
);
this.getContent().subscribe(text => {
// Set the message
ampDisplayEl.message = text;
// Add to the DOM
document.body.appendChild(ampDisplayEl);
});
}
}
APMコンテンツを用意する
以下のAMPチュートリアルを参考にHTMLドキュメントを用意しました。expressサーバから供給します。
結果
ボタンを押すと、AMPソースをサーバーから取得して、トーストに表示しています。グレーの背景がAMPソースです。
リポジトリ
今回のコードはこちらにアップしています。
ほんとうにやりたかったこと
正直、iframeは逃げました。ほんとうにやりたかったことは、iframeの代わりにshadowDOMを使ってAMPを表示する2という話がありました。そこまで来るとAngularElementsを使ってcustomElementにする意味もあるというもので。しかし、shadowRootに<html>
タグ以下をそのまま追加すればいいってものでもなく、AMP projectで用意されているwindow.AMP.attachShadowDoc()
という謎モジュールの解明で時間切れとなりました。reactのサンプル実装があるので、ご興味ある方はトライしてみてください。
デモサイト3を見るとおおよそやってることはわかるかと思います。今回やろうとしていた特定領域に出力先をコントールするのではなくドキュメント全体に適用するような手法で、実はそれならAngularでもwindow.AMP.attachShadowDoc()
を使ってできました。機会を見てまた記事にするかもしれません。
明日は@Czernyさんです。
-
https://choumx.github.io/amp-pwa AMPhtmlから
<style>
と<body>
タグだけを取り出してshadowRootに適用している感じです。<meta>
や<script>
はホストのreactドキュメントに適用しているような動きに見えます。shadow-v0.js
というのがOSS化されてないと思うので出力結果からの憶測です。 ↩