Angularで、scriptタグを普通にHTMLに書いても、描画時にscriptタグの部分が消えてしまって処理が実行されません。
それでもscriptタグを埋め込みたい場合の方法を解説します。
Angular6でも多分大丈夫な方法です。
今回は、Stripeのタグを例に解説します。
stripe-angularっていうパッケージがあるけど、これは最近のバージョンだと使えない。
基本方針
・Stripe用のパッケージやモジュールには頼らない。良いのが無いので。
・AfterViewInitを用いて、再描画されるたびにDOM操作でStripeのタグを生成し、Javascriptを実行する。
・完成時はこんな感じ。ngForで商品の数だけStripeのボタンを生成する。
購入ボタンのところがStripeのタグです。
今作ってるのをキャプってきたけど、これより下に書くコードは、これよりもっと簡素なものです。
必要なモジュール
import { Component, OnInit, AfterViewInit, ViewChildren, QueryList } from '@angular/core';
コンポーネントのHTML側
<div #productsElement class="product" *ngFor="let product of this.products"> <h3>{{ product['product_name'] }}</h3> <p>{{ product['description'] }}</p> <p>¥{{product['price']}}-</p> <div> <form class="stripe-form" action="[POST先URL]" method="POST" id="stripe_{{product['product_id']}}" [attr.amount]="product['price']" // 商品によって変わる、動的な値はここで指定 [attr.name]="product['product_name']" // 商品によって変わる、動的な値はここで指定 [attr.product_id]="product['product_id']" // 商品によって変わる、動的な値はここで指定 enctype="application/x-www-form-urlencoded"> </form> </div> </div>
tsファイル側
import { Component, OnInit, AfterViewInit, ViewChildren, QueryList } from '@angular/core'; import { ProductService } from '../domain/product/product.service'; @Component({ selector: 'app-product-list', templateUrl: './product-list.component.html', styleUrls: ['./product-list.component.scss'] }) export class ProductListComponent implements OnInit, AfterViewInit { @ViewChildren('productsElement') // #productsElementを付与したngFor要素を取得 productsElement: QueryList<any>; private productElement: HTMLElement; public products = []; constructor(public productService: ProductService) { } ngOnInit() { // 商品一覧情報を取得、私の場合は商品情報を取得するサービスクラスを作って使ってます。 // サービスクラスの実装は割愛します。そこで躓いてるわけじゃないと思うので。 this.products = []; this.productService.getProducts().subscribe(response => { const responseJson = response.json(); // 私の実装では、APIサーバーから取得しているので、レスポンスコードを確認しています。 // 人によってこのあたりの実装は違うと思いますので、ここは参考にし過ぎないでください。 if (responseJson['statusCode'] === 200) { this.products = JSON.parse(responseJson['body']); console.log(this.products[0]['product_name']); console.log(this.products[0]); } }); } // 描画後の処理を定義 ngAfterViewInit() { // productsElementに変更があるたびに実行する処理を定義 this.productsElement.changes .subscribe(element => { console.log('element'); // ngForによって生成された、全ての要素について、ループで処理します。 for (const e of element.toArray()) { console.log(e); this.productElement = e.nativeElement; // formタグに付与した、商品情報の属性の値を取得して、商品ごとのstripeタグを生成します。 const stripeForm = this.productElement.querySelectorAll('.stripe-form').item(0); const productId = stripeForm.getAttribute('product_id'); const name = stripeForm.getAttribute('name'); const amount = stripeForm.getAttribute('amount'); // afterbeginは、要素内部の、最初の子要素の前に挿入するという意味。詳しく知りたければぐぐってください。 stripeForm.insertAdjacentElement('afterbegin', this.createStripeButton(productId, name, amount)); } }); } // Stripeのタグを生成 private createStripeButton(product_id: string, name: string, amount: string): HTMLScriptElement { const script = document.createElement('script'); // scriptタグを作ります // stripeタグに必要な属性をsetAttributeで付与する。 script.setAttribute('src', 'https://checkout.stripe.com/checkout.js'); script.setAttribute('class', 'stripe-button'); script.setAttribute('data-key', '[StripeのAPIキー]'); script.setAttribute('data-amount', amount); script.setAttribute('data-name', name); script.setAttribute('data-description', product_id); script.setAttribute('data-image', 'https://stripe.com/img/documentation/checkout/marketplace.png'); script.setAttribute('data-locale', 'ja'); script.setAttribute('data-currency', 'jpy'); script.setAttribute('data-label', '購入する'); script.setAttribute('data-panel-label', '購入する'); script.setAttribute('data-zip-code', 'true'); script.setAttribute('data-billing-address', 'true'); return script; } }
こんな感じ
重要なところ
・AfterViewInitを使って、再描画に対応している
・ViewChildrenを使って、changes.subscribeで、要素の内容変更時の処理を定義している
・HTMLScriptElement でScriptタグを生成している
・querySelectorAll で要素を検索している
・getAttributeやinsertAdjacentElementで要素を参照したりDOMの操作をしているところ
上記の概念が知識としてあれば、この記事は必要ないですね。