狛ログ

2020年12月30日水曜日

Angular・Componentのタグにclassを付与したい【HostBinding】


こんにちは、オフィス狛 技術部のpinoです。

早速ですが、皆さんはAngularをやっていて「コンポーネントのタグに直接classを付与したいんだけどなぁ....。」と思った経験はないでしょうか。

今回は、そんな時に使える @HostBinding についてご紹介したいと思います!

準備

まずは、コンポーネントを用意します。
app.component.html(親コンポーネント)
<header class="header">
  <section class="header__about">
    <h1 class="header__title">@HostBinding Sample</h1>
    <p class="header__lead">動作確認用のサンプルです</p>
  </section>
  <nav class="header-nav">
    <ul class="header-nav__list">
      <app-list-item *ngFor="let name of names" [name]="name"></app-list-item>
    </ul>
  </nav>
</header>

list-item.component.html
<li class="header-nav__item">
  <input type="checkbox" />{{ name }}
</li>
list-item.component.css
.header-nav__item {
  width: 120px;
  height: 64px;
  padding-left: 32px;
  line-height: 64px;
}
list-item.component.ts
import { Component, Input, OnInit } from '@angular/core';

@Component({
  selector: 'app-list-item',
  templateUrl: './list-item.component.html',
  styleUrls: ['./list-item.component.scss']
})
export class ListItemComponent implements OnInit {
  @Input() name: string;

  constructor() { }

  ngOnInit(): void {
  }
}
ビルド後のイメージ

色味がなく、シンプルで寂しい印象です。
ここから、実際にHostBindingを使ってclassを付与してみたいと思います。

HostBindingの使い方

使い方は

1. コンポーネントのタグにclassを付与する
2. 上記1に加えて、動的にclassの付け外しをする

と、主に2つあります。

まずは、1. コンポーネントのタグにclassを付与するについてです。
こちらは、大きく2通りの書き方ができます。
list-item.component.css
import { Component, HostBinding, Input, OnInit } from '@angular/core';  // HostBinding を追加

@Component({
  selector: 'app-list-item',
  templateUrl: './list-item.component.html',
  styleUrls: ['./list-item.component.scss']
})
export class ListItemComponent implements OnInit {
  @HostBinding('class') listItem = 'list-item';  // ①
  @HostBinding('class.first') first = true;  // ②

  @Input() name: string;

  constructor() { }

  ngOnInit(): void {
  }
}

ポイントは、@HostBinding()の、()の中です。
①は、「'class'」のみ指定して、なんのclassを付与するかは変数の値が使用されるパターンです。
値部分に定数を使えば、ロジック側にテンプレートの情報をベタ書きすることも避けられますね。
固定のclassであればこれで十分そうです。

②は、「'class.first'」と付与するclass名まで指定するパターンです。
この場合は、後に続いている値がtrueであれば付与、falseであれば付与しない、ということになります。
(※真偽値を固定で設定したい場合は、「@HostBinding('class.first') readonly first = true;」 といった具合に、readonlyをつけるなどの対策が考えられます。)

また、HostBindingの話とはすこし逸れますが、コンポーネントのタグに使用するclassは
グローバルである(style.cssに記述されている等)か、:hostメタデータを使用する必要があります。

今回は、自コンポーネントのCSSに背景色をつけるclassを用意したいと思います。
list-item.component.css
// 以下を追加
:host.list-item.first {
  background-color: #7ae5ec;
}

:host.list-item.second {
  background-color: #7aecc0;
}

:host.list-item.third {
  background-color: #7ab9ec;
}
ここまでのビルド後イメージ

すべてのコンポーネントにclassが付与されて、背景色がつきました。


次は、2. 上記1に加えて、動的にclassの付け外しをするについてです。
list-item.component.css
export class ListItemComponent implements OnInit {
  @HostBinding('class')
  @Input() name: string; // ①
  
  // 今回formは用意していないので、例としてだけ挙げます
  @HostBinding('class.valid') get valid() {
    return this.form.valid;
  } // ②

  @HostBinding('class.list-item') isClick = false; // ③-A
  ...
  onClick(): void {
    this.isClick = !this.isClick;
  } // ③-B

他にもいろいろな方法があると思うのですが、簡単に思いつくものを挙げてみました。

①は、Input()で受け取った値を使用します。
組み合わせて使うだけで、特別なことをしてる感じがします...!

②について、今回Formは用意していないので記述の例になるのですが、状態など値そのものを監視したい時に使える方法です。

getterってあまり使う機会がなかったのですが、今回試してみたところ使い勝手がいいですね!


また③は、②のようにイベント発生時ではなく、識別子に設定した変数の値を書き換えることによってclassの付け外しをしています。

ここまでで、上記の①と③をプログラムに落とし込んだビルド後のイメージが以下のようになります。

それぞれで別々のclassの付与と、classの付け替えができました!

補足

ここまでclassを付与する使い方を紹介してきましたが、なんとHostBindingはclass以外のバインディングにも使えます。
例えば、「@HostBinding('style.display') flex = 'flex';」としてあげればstyle="display: flex;"がバインドされます。
便利!

今回は公式以外に以下サイトも参考にしたのですが、「HostBinding('value') myValue;は[value]="myValue"と全く同じです。」という一文が特にわかりやすかったです。
@HostBindingと@HostListener:彼らは何をし、何のためにいますか?


以上、HostBindingデコレータについて紹介しました。
参考になればうれしいです。

Spring Fest 2020に参加しました。


オフィス狛 技術部のJoeです。

Spring Fest 2020が開催されましたので、昨年に引き続き参加しました。
公式ページ

今年はオンライン開催ということで、昨年のような雰囲気ではありませんでしたが、
質問をチャットで受け付けてセッション後に回答いただけるなど、良かった点もありました。

また、今回は「Advanced Track」と「Beginner track」の2トラックで分かれて配信されていましたので、初めて参加される方も、セッションを選択し易かったかと思います。
(私はどれも聞いてみたく、結構直前まで迷ってしまいました)

今回も、特に気になったセッションの感想を、ブログに残したいと思います。


今こそ知りたいSpringエコシステム

槙 俊明さん(VMWare)
セッション資料

Springの歴史から始まり、主なSpring Projectを下記の3つの分類に分けて紹介されていました。
・できることを増やす
・バリエーションを増やす
・使いやすくするプロジェクト

改めて、様々なプロジェクトがあることを認識させられたことと、
最新情報は、書籍ではなく、公式ドキュメントと
プロジェクト開発者のGihub、Twitterで集めることが勉強になりました。


今こそ知りたいSpring DI × AOP

多田 真敏さん(株式会社カサレアル)
セッション資料

Springの基礎である、コンテナ、DI、スコープ、プロキシ、AOPについて丁寧に解説されていました。
初心者向けとありましたが、私の勉強不足もあり、なかなか説明に追いつけないところがありましたので、是非、もう一度復習したい内容でした。


CloudNativeな決済サービスの開発と2年間の歩み

鈴木 順也さん(SBペイメントサービス)
セッション資料

大規模な決済サービスのシステムを、2年間運用された実績をもとに、
アプリケーション構成とアプリの役割から、
ライブラリの移行と発生した問題、開発体制、CI / CD、テスト、Observabilityといった内容を
実績で紹介されていました。

1つのサービスの歩みという内容がとても面白く、勉強になりました。
また、2年経過してシステム数やアプリ数が増加しているのでに、
プラットフォーム運用者はほぼ変わらないという点にはとても驚きました。


Spring 5.3 & Spring Boot 2.4

槙 俊明さん(VMWare)
セッション資料

こちらはSpring 5.3 と Spring Boot 2.4 の新機能の紹介でした。
常に新しいもの、いいものを取り込んで進化しているという印象を受けました。

特に気になったのは、「プロパティ読み込み方法の改善」で、YAMLの定義方法が紹介されていました。
過去にSpring Boot のバージョンアップを行った際に、YAMLの定義方法の変更で、
結構ハマってしまうことがあったので、早めのバージョンアップが必要だなと思いました。

またこちらのセッション後にあった、Springについて色々な観点からの質問コーナー(?)は個人的に面白かったです。



今回はオンラインでの開催で、目的のセッションをゆっくり視聴できました。

今回のセッション動画は、今は非公開になっていますが、
来年に公開される予定とのことですので、今回視聴出来なかったセッションも確認してみたいと思います。


,

node.jsでS3上の画像ファイルをPDFファイルに変換する方法。


オフィス狛 技術部のJoeです。

担当したプロジェクトで、S3上の画像ファイルをPDFファイルに変換する要件がありましたので、
変換方法を備忘も兼ねてご紹介しようと思います。

今回使用したライブライは2つです。 ※()は使用したバージョンです
・pdf-lib(1.11.1):PDFファイルを編集するためのnpmパッケージ
・sharp(0.26.3):画像ファイルを編集するためのnpmパッケージ


1.S3上の画像ファイル読み込む

S3から画像ファイルを読み込んで、bufferデータを取得します。
ここで問題になったのは、読み込んだ画像ファイルが意図せずに回転してしまったことです。
読み込んだ画像ファイルの回転情報を保持するために、sharpのライブラリを使用しました。

const aws = require('aws-sdk');
const sharp = require('sharp');

// S3にアクセスするための情報
const s3Client = new aws.S3({
  accessKeyId: [S3アクセスキー],
  secretAccessKey: [S3シークレットアクセスキー],
  region: [S3のリージョン],
});

const params = {
  Bucket: [S3バケット名],
  Key: [S3ファイルパス/ファイル名],
};

// S3の画像ファイルを取得
dataInS3 = await s3Client.getObject(params).promise();

// 画像ファイルのbufferデータを取得(rotateの引数なしで、読み込んだ画像の回転情報を保持する)
bufferData = await sharp(dataInS3.Body).rotate().toBuffer();

2.pdf-libで画像をPDFファイルに埋め込む

読み込んだ画像ファイルをPDFファイルに埋め込みます。
この処理では、読み込んだ画像ファイル容量が大きい場合、メモリ使用率にご注意ください。

const { PDFDocument } = require('pdf-lib');

// 空のPDFを作成
const pdfDoc = await PDFDocument.create();

// 画像のbufferデータをPDFに埋め込む
image = await pdfDoc.embedJpg(bufferData);  // jpgファイルの場合はこちら
image = await pdfDoc.embedPng(bufferData);  // pngファイルの場合はこちら

// 画像の倍率を元サイズのままとして幅と高さの情報を取得
const dims = image.scale(1);

// 画像ファイルと同じサイズのPDFページを追加して、画像を設定する
const page = pdfDoc.addPage([dims.width, dims.height]);
page.drawImage(image, {
  width: dims.width,
  height: dims.height,
});
const pdfBytes = await pdfDoc.save();


3.PDFファイルをS3に保存

最後に変換したPDFファイルをS3に保存にします。

// PDFファイルのbufferデータを取得
const pdfBuffer = Buffer.from(pdfBytes);

// S3に保存するためのパラメータを設定
const putParams = {
  Bucket: [S3バケット名],
  Key: [S3ファイルパス/PDFファイル名],
  Body: pdfBuffer,
  ContentType: 'application/pdf',
};

// S3に保存
await s3Client.putObject(putParams).promise();


以上で、変換したPDFファイルがS3に保存されました。
もし、画像からPDFへの変換が必要になった際に、参考になれば幸いです。


Catalinaにアップデートした後、pod installできないときの対処法。


オフィス狛 技術部のmmm(むー)です。

既にBig Surがリリースされているので、少し今更感はありますがMac OSをCatalinaにアップデートした後、pod installが実行できなくなったのでその対処方法を記載します。
※ちなみにCatalinaのデフォルトシェルはzshですが、急いでいたのでひとまずbashで各作業は行っています。

実行環境
Xcodeのバージョンは諸事情で11系です...
$ sw_vers
ProductName:	Mac OS X
ProductVersion:	10.15.7
BuildVersion:	19H15

$ xcodebuild -version
Xcode 11.1
Build version 11A1027

1. エラーの内容確認

1-1. pod installのエラーメッセージの確認
エラー内容から、CocoaPodsはrubyの2.3を使用しようとしているようですが、フォルダがないようです。
$ pod install

-bash: /usr/local/bin/pod: /System/Library/Frameworks/Ruby.framework/Versions/2.3/usr/bin/ruby: bad interpreter: No such file or directory

1-2. rubyのバージョン確認
Catalinaではデフォルトでrubyの2.6が入っているようです。
$ ruby --version

ruby 2.6.3p62 (2019-04-16 revision 67580) [universal.x86_64-darwin19]

2. 対処方法

調査した結果、CocoaPodsを入れ直すと書かれていたので入れ直してみます。
gemを使用する方法と、brewを使用する方法があるようですが、今回はgemを使用します。

2-1. Xcodeのコマンドラインツール
その前にXcodeのコマンドラインツールが必要になるのでインストールします。
すでに入っている方は、下記のように表示されます。
$ xcode-select --install

xcode-select: error: command line tools are already installed, use "Software Update" to install updates

うまくインストールできない方は、公式から直接ダウンロードできるようです。
参考:https://developer.apple.com/download/more/

2-2. Xcodeとの紐付け
Xcodeにて、使用するコマンドラインツールの紐付けを行います。
既にこの作業が完了している方は、読み飛ばしてください。
(私はここが完了していなかったのですが、調べた数々のサイトにはこの手順の記載がなかったので時間が溶けました...)
Xcode > Preferences > Locations > Command Line Tools > XCode X.X.X(X.X.Xはバージョン)

2-3. CocoaPodsのインストール
$ sudo gem install cocoapods -n /usr/local/bin

pod insatll を行えたら、確認完了です。
以上となります、参考になりましたら幸いです。

参考にしたページ
This terminal won't let me run or install cocoa pods because I am on MacOS Catalina Beta? (stack overflow)
Brew based CocoaPods CLI fails in MacOS 10.15 Catalina due incorrect fallback to Ruby 2.3 #8955 (GitHubのCocoaPodsレポジトリのissue)

Angularでgetパラメータに配列を設定する方法。

オフィス狛 技術部のHammarです。

前回6月くらいにAngularについて投稿したのですが、そこからお久しぶりのAngularについての投稿になります。
またご存知の方には基本的なお話しかもしれませんが、「angularでgetパラメータに配列をつかってリクエストする方法」についてご紹介しようと思います。
実はすこし前にKoma(Twitterアカウントの中の人&CEO)より「HttpParams を動的に追加したい。」という投稿で、配列形式でクエリパラメータを追加するという投稿があったのですが、これに少し似ている内容で、今回は1つのパラメータに配列を設定する方法になります。

まずは最初こんな感じでいけるのかなーと思って下記作ってみました。
※HogeInfoRequestModelにitemArrayという配列項目があるとします。
  getHogeInfo(req: HogeInfoRequestModel): Observable<HogeInfoModel> {
    const options = {
      params: new HttpParams()
        .set('items[]', req.itemArray)
    };
 
    return this.http.get('xxxxx/yyyyy/hoge/info', options).pipe(tap(console.log));
  }

上記で上手くいきそうな感じはするんですが、もちろん上手くいきません。。。

ここで「HttpParams を動的に追加したい。」の方法で、改めて配列追加という流れでやってももちろん大丈夫ですが、すでに配列になっている項目をリクエストしたい場合、下記のような記述方法でもいけました。

 getHogeInfo(req: HogeInfoRequestModel): Observable<HogeInfoModel> {
    const options = {
      params: new HttpParams()
        .set('items', JSON.stringify(req.itemArray))
    };
 
    return this.http.get('xxxxx/yyyyy/hoge/info', options).pipe(tap(console.log));
  }

JSON.stringifyを使ってここでJSON文字列に変換しちゃおうという方法です。

【注意!】結果的に、「配列を送っている」というのは正しくなく、ただの文字列を送っているにすぎないので、当然受け手側(WebAPI側)が、その文字列を配列に変換する必要があります。

配列項目をリクエストするという時には、用途に合わせて良き書き方で書いていただければと思います。