Angular Elements でWeb Components化した Dialog をHugo に埋め込む
はじめに
Hugo のような静的なコンテンツを扱うフレームワークを利用している際に部分的に動的なガジェットが欲しくなることがあります。
Hugo が読み込める形で javascript のみでなんとかする方法はガジェットの規模によっては少ししんどく、外部ライブラリーを探して読み込む方法は、完全に要件を満たしていなかったり、依存関係やライセンスの問題など本質的ではない問題に当たってしまうことが多い印象です。
やはり慣れたフレームワークでシュッと作ってしまいたくなるわけです。
なのでAngular Elements で作ったアプリケーションを Hugo に埋め込む方法をまとめます。
今回は画像をクリックすると Modal としてクリックされた画像が浮かぶ簡単なアプリケーションを作成します。
実物が動いているページはこちら
また、Angularのソースコードは記事の最後にGithubのリンクを載せておきます。
Angular Elements
Angular Elements とはなんでしょう。
Angular のドキュメントには以下のようにあります。
Angular Elements は、 Custom Elements (Web Componentsとも呼ばれます)としてパッケージ化される Angular コンポーネントです。Custom Elements は、フレームワークに依存しない形で新たな HTML 要素を定義するウェブ標準技術です。
難しいですね。
いい感じに埋め込めるガジェットが Angular で作れるようになったんだなぁくらいでいい気がします。
Angular CLI で 環境構築
現時点で Angular8 で環境構築を行います。
フットプリントの縮小が見込まれるため ivy を enable にしています。
$ ng new img-modal --enable-ivy --routing --style=scss $ cd img-modal $ ng add @angular/material ? Choose a prebuilt theme name, or "custom" for a custom theme: Custom ? Set up HammerJS for gesture recognition? Yes ? Set up browser animations for Angular Material? Yes $ ng add @angular/elements $ ng serve
localhost:4200
にアクセスすると以下のような初期画面が表示されます。
最近とてもかっこよくなりました。
Angular Elements の作成
まずはコンポーネントを作成します。
私の構成だと作ったコンポーネント名が Web Components として利用する HTML タグの一部として利用することになります。
$ ng g component img-modal
app.component の設定
app.component.html
は router-outlet のみ残します。
<router-outlet></router-outlet>
app.component.ts
は以下のように編集します。
注意点としては selector
を app-root
から先ほど作成したimg-modal Component の selector
である app-img-modal
を設定します。
あとは createCustomElement として先ほど作成したimg-modal Component を登録します。
import { Component, Injector } from '@angular/core'; import { createCustomElement } from '@angular/elements'; import { ImgModalComponent } from './img-modal/img-modal.component'; @Component({ selector: 'app-img-modal', templateUrl: './app.component.html', styleUrls: ['./app.component.scss'] }) export class AppComponent { constructor( private injector: Injector, ) { const AppImgModalElement = createCustomElement( ImgModalComponent, { injector: this.injector } ); customElements.define('app-img-modal', AppImgModalElement); } }
app.module.ts
は以下のように設定します。
entryComponents
として ImgModalComponent を登録しています。
import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { AppRoutingModule } from './app-routing.module'; // material import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { MatDialogModule } from '@angular/material/dialog'; // component import { AppComponent } from './app.component'; import { ImgModalComponent } from './img-modal/img-modal.component'; @NgModule({ declarations: [ AppComponent, ImgModalComponent ], imports: [ BrowserModule, AppRoutingModule, BrowserAnimationsModule, MatDialogModule ], providers: [], bootstrap: [AppComponent], entryComponents: [ ImgModalComponent ] }) export class AppModule { }
img-modal.component の設定
img-modal.component.ts
を設定します。
今回は Hugo に埋め込んだ際の HTML から src
として画像の URL と alt
として alt をもらう想定です。
DialogComponent は Angular Material のDialog の使い方そのものですが @Inject(MAT_DIALOG_DATA) public data: ImageData
としてデータの受け渡しをしてます。
import { Component, Inject, Input } from '@angular/core'; import { MatDialog, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; @Component({ selector: 'app-img-modal', templateUrl: './img-modal.component.html', styleUrls: ['./img-modal.component.scss'] }) export class ImgModalComponent { @Input() src: string; @Input() alt?: string; constructor( public dialog: MatDialog ) { } openDialog(): void { const dialogRef = this.dialog.open(DialogComponent, { width: '90%', data: {src: this.src, alt: this.alt} }); } } @Component({ selector: 'app-dialog', templateUrl: 'dialog.html', }) export class DialogComponent { constructor( public dialogRef: MatDialogRef<DialogComponent>, @Inject(MAT_DIALOG_DATA) public data: ImageData, ) {} }
img-modal.component.html
は以下の通りです。
ただの img タグです。クリックすると Dialog が開きます。
<img [src]="src" [alt]="alt" (click)="openDialog()">
dialog.html
として Dialog として浮かんでくる HTML を書きます。
今回は雑にもらったデータをそのまま表示します。
<img [src]="data.src" [alt]="data.alt" height="100%" width="100%">
Angular Elements の表示
さて、このままでは ng serve
の結果は何も表示されていません。
それは index.html
として <app-root></app-root>
を表示しているためです。
ng serve
でも Angular Elements をデバッグできるように index.html
を以下のように編集します。
<!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <title>ImgModal</title> <base href="/"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="icon" type="image/x-icon" href="favicon.ico"> <link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet"> </head> <body> <app-img-modal src="https://placehold.it/900x600" alt="This is test image"></app-img-modal> </body> </html>
これで以下のように表示されたはずです。
Angular Elements の build
Angular は単一の jsファイルとして build されません。
また、Angular8 から es5 と es2015 で別ファイルになっています。
なので以下のように package.json
のスクリプトで es5 と es2015 それぞれ単一の jsファイルにまとめています。
"scripts": { "build:elements": "ng build --prod --output-hashing=none && cat dist/img-modal/{runtime-es5,polyfills-es5,scripts,main-es5}.js > dist/img-modal/app-img-modal-es5.js && cat dist/img-modal/{runtime-es2015,polyfills-es2015,scripts,main-es2015}.js > dist/img-modal/app-img-modal-es2015.js", },
上記 scripts を実行します。
$ npm run build:elements
すると dist/img-modal/
配下に es5 と es2015 それぞれのファイルが build されています。
enable ivy してこの結果です。
もう少し小さくなってくれると嬉しいですね。
$ ls -lh dist/img-modal/ total 4208 -rw-r--r-- 1 nao staff 433K 10 17 08:08 app-img-modal-es2015.js -rw-r--r-- 1 nao staff 569K 10 17 08:08 app-img-modal-es5.js -rw-r--r-- 1 nao staff 61K 10 17 08:08 styles.css
Hugoに埋め込み
build した jsファイルと css ファイルを Hugo に埋め込みます。
今回はただファイルを読み込むのみなので static フォルダ配下に cssフォルダを作成し styles.css
をコピーします。また、jsフォルダを作成し app-img-modal-es2015.js
と app-img-modal-es5.js
両方をコピーして置いておきます。
partials/head.html
から読み込みます。
<script type="module" src="{{ .Site.BaseURL }}/js/app-img-modal-es2015.js"></script> <script src="{{ .Site.BaseURL }}/js/app-img-modal-es5.js" nomodule defer></script> <link rel="stylesheet" href="{{ .Site.BaseURL }}/css/styles.css">
あとは template から以下のように利用します。
<app-img-modal src="https://source.unsplash.com/random/512x512">
まとめ
Angular Elements でWeb Components化した Dialog をHugo に埋め込む方法をまとめました。
簡単なガジェットが Angular でかけるのはありがたいですね。色々触っていこうと思います。