新米エンジニアの失敗再発防止メモ

自分そしてこの世界の皆が、同じ失敗をしないためのメモ

Twitterやってます!@rakuton_t
欲しいものリストのブタメンを送ってくれた方、ありがとうございます!

Angular7でscriptタグを使う方法。Stripeのタグを例に解説

Angularで、scriptタグを普通にHTMLに書いても、描画時にscriptタグの部分が消えてしまって処理が実行されません。
それでもscriptタグを埋め込みたい場合の方法を解説します。

Angular6でも多分大丈夫な方法です。
今回は、Stripeのタグを例に解説します。
stripe-angularっていうパッケージがあるけど、これは最近のバージョンだと使えない。

基本方針

・Stripe用のパッケージやモジュールには頼らない。良いのが無いので。
・AfterViewInitを用いて、再描画されるたびにDOM操作でStripeのタグを生成し、Javascriptを実行する。
・完成時はこんな感じ。ngForで商品の数だけStripeのボタンを生成する。
Angularで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の操作をしているところ
上記の概念が知識としてあれば、この記事は必要ないですね。

私の記事が役に立ったら、どうぞ何か買ってください!→ Amazon欲しいものリスト