15
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

AngularAdvent Calendar 2018

Day 23

AngularElementsでAMP htmlを動的にロードする

Last updated at Posted at 2018-12-23

本記事では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属性になります。

amp-display.component.ts
@Component({
  selector: 'app-amp-display',
  template: `
    <iframe [srcdoc]="safeMsg"></iframe>
    <button (click)="closed.next()">&#x2716;</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>をセットしても様々な困難がやってきます。

app.component.ts
@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ソースです。

トリム.mov.gif

リポジトリ

今回のコードはこちらにアップしています。

ほんとうにやりたかったこと

正直、iframeは逃げました。ほんとうにやりたかったことは、iframeの代わりにshadowDOMを使ってAMPを表示する2という話がありました。そこまで来るとAngularElementsを使ってcustomElementにする意味もあるというもので。しかし、shadowRootに<html>タグ以下をそのまま追加すればいいってものでもなく、AMP projectで用意されているwindow.AMP.attachShadowDoc()という謎モジュールの解明で時間切れとなりました。reactのサンプル実装があるので、ご興味ある方はトライしてみてください。

「AMP を埋め込んでデータソースとして使用する」

デモサイト3を見るとおおよそやってることはわかるかと思います。今回やろうとしていた特定領域に出力先をコントールするのではなくドキュメント全体に適用するような手法で、実はそれならAngularでもwindow.AMP.attachShadowDoc()を使ってできました。機会を見てまた記事にするかもしれません。

明日は@Czernyさんです。

  1. https://www.ampproject.org/ja/learn/overview/

  2. https://html5experts.jp/shumpei-shiraishi/24795/

  3. https://choumx.github.io/amp-pwa AMPhtmlから<style><body>タグだけを取り出してshadowRootに適用している感じです。<meta><script>はホストのreactドキュメントに適用しているような動きに見えます。shadow-v0.jsというのがOSS化されてないと思うので出力結果からの憶測です。

15
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?