本記事では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化されてないと思うので出力結果からの憶測です。 ↩
