狛ログ

2018年11月15日木曜日

AngularのFormでのselect要素の使い方(selected指定の方法)

11月 15, 2018

オフィス狛 技術部のKoma(Twitterアカウントの中の人&CEO)です。

何だか分かり難い題名ですいません。良い題名が思いつきませんでした。

さて、やりたい事から説明しようと思います。

Angularで、Formに入力した値をPOSTする時、ちょっとやり方迷うのがselect要素です。
プルダウンってやつですね。

こんな感じのFormは良くあるかと思います。
初期表示時に先頭を選択状態にしておくのは簡単ですが、
データの修正を行う機能などは、「APIで取得した値を選択状態に」って事は良くあります。

例えば、このようなJSONがAPIから返却されるとします。
{"list": [
        {"api-data-status": 1},
        {"api-data-status": 2},
        {"api-data-status": 3}
 ]}

まずはプルダウンに表示する値(value)と名称(name)を定義します。
【constants.ts】
export namespace testDataStatus {
  export const STATUS_1 = { value: 1, name: 'ステータス1' };
  export const STATUS_2 = { value: 2, name: 'ステータス2' };
  export const STATUS_3 = { value: 3, name: 'ステータス3' };
}

続いて、コンポーネント側でプルダウン用のリスト定義します。
先程の定義した定数を使用しています。
【test-data-status.component.ts】
  testDataStatusOption: Array<{ status: number; name: string }> = [
    {
      status: Constants.testDataStatus.STATUS_1.value,
      name: Constants.testDataStatus.STATUS_1.name
    },
    {
      status: Constants.testDataStatus.STATUS_2.value,
      name: Constants.testDataStatus.STATUS_2.name
    },
    {
      status: Constants.testDataStatus.STATUS_3.value,
      name: Constants.testDataStatus.STATUS_3.name
    }
  ];

そして、最後はView(html)側ですが・・・下記のように記載すると、うまく行きません
【test-data-status.component.html】(NGな例)
<ng-container *ngFor="let item of statusList.list">
    <select class="form-control" formControlName="testDataStatus">
        <option *ngFor="let hoge of testDataStatusOption"
              [ngValue]="hoge.status"
              [selected]="hoge.status === item.api-data-status">
              {{report.name}}
        </option>
    </select>
</ng-container>
「statusList.list」がAPIで取得したデータです。
それを最初の「ngFor」で繰り返してデータ数分のプルダウンを作り、二番目の「ngFor」からプルダウンの値(value)と名称(name)を作り、APIの値と比較して「selected」に値を設定する・・・一見うまく行きそうなんですけどね。これではダメです。

という訳で、うまく行く書き方は以下の通りです。
【test-data-status.component.html】(OKな例)
<ng-container *ngFor="let item of statusList.list">
  <select class="form-control form-control-sm" [(ngModel)]="item.api-data-status" formControlName="testDataStatus">
    <option *ngFor="let hoge of testDataStatusOption"
        [ngValue]="hoge.status">
        {{hoge.name}}
    </option>
  </select>
</ng-container>
まあ、良く考えると当たり前なのですが、
「[(ngModel)]」に値を設定する事で、APIから取得した値で選択状態とする事が出来ます。

view(html)表示だけだと、そこまで難しくないのですが、FormControlを使うと少しややこしくなるので、自分の備忘録の為に記事にしました。

それでは、良いAngularライフを!


2018年11月2日金曜日

C#でプロキシ経由でJSONデータを外部APIにわたす。

11月 02, 2018

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

今回も業務でちょっとハマったことについて、解決方法を書いていこうと思います。
今回はC#でプロキシ経由でAPIを呼び出す方法です。
いつもは社内のネットワーク内でAPIを呼び出していて、あるタイミングで社外のAPIを
呼び出すとなったときに、プロキシ設定しているため通常の呼び出し方ではリクエストが届かないという感じでちょっとハマりました。

まず下記のようなJSONを、あるAPIに送るとします。
{
    "id" : 1,
    "name" : "テスト"
}

プロキシ設定を考えずに書くと、HttpClientを使って下記のような感じでPOSTします。
var json = "{ \"id\" : 1, \"name\" : \"テスト\"  }";

using (var client = new HttpClient())
{
    string apiUrl = "http://hostname/testapi";
    HttpContent content = new StringContent(json, Encoding.UTF8, "application/json");
    HttpResponseMessage response = await client.PostAsync(apiUrl, content).Result;
}
※上記のJSON部分は一旦わかりやすいように書いていますが、実際にはJson.NETを使ってシリアライズしてやり取りします。

で、上記の状態でAPIをコールしてもプロキシに引っかかり、API側にリクエストが届きません。
ですので、上記に利用するプロキシ設定情報を付与してあげると外にでれます。
そのやり方が下記になります。
var json = "{ \"id\" : 1, \"name\" : \"テスト\" }";
var httpClientHandler = new HttpClientHandler
{
    Proxy = new WebProxy("http://localhost:8888", false),
    UseProxy = true
};
using (var client = new HttpClient(httpClientHandler))
{
    string apiUrl = "http://hostname/testapi";
    HttpContent content = new StringContent(json, Encoding.UTF8, "application/json");
    HttpResponseMessage response = await client.PostAsync(apiUrl, content).Result;
}
上記でわかるようにHttpClientHandlerクラスのProxyプロパティを使ってあげます。
これでこのプロキシ設定情報でリクエストされ、API側に届くようになります。

ちなみにこのやり方は認証ない場合で、認証が必要な場合は下記のようにHttpClientHandlerクラスに認証情報のCredentialsプロパティを追加するだけです。
var httpClientHandler = new HttpClientHandler
{
    Proxy = new WebProxy("http://localhost:8888", false),
    Credentials = new NetworkCredential(@"username", @"password");
    UseProxy = true
};

HttpClientHandlerクラスにはその他いろんなプロパティがあるので、HttpClientを使ったリクエストを行う際にはこちらも参考にしてみてください。
https://msdn.microsoft.com/ja-jp/library/system.net.http.httpclienthandler(v=vs.110).aspx

2018年10月4日木曜日

CSSでテキストの1文字目を大きく表示する。

10月 04, 2018

こんにちは、オフィス狛 デザイン部のSatoです。

先日、ドロップキャップをウェブサイトで表現する必要があったのですが調べるまで正しいやり方がわからなかったのでメモしたいと思います。
(テキストの1文字目のみを大きく表示する表現のことをドロップキャップって言うんですね。実は知らなかったです。)

最初大きくしたい1文字目の部分を<span>で囲んでclassで…と思ったのですが、実は「:first-letter」という擬似要素で簡単に実現できます。
いちいち<span>で囲まなくてもいいんです。
この擬似要素はブロックレベル要素の最初の文字にのみスタイルが適用されるので、ドロップキャップを実現できるのですね。


「p:first-letter」にfloat設定しp要素でfloat解除するといい感じになりました。


2018年10月1日月曜日

ESLint のエラー「Use object destructuring prefer-destructuring」に対応する。

10月 01, 2018

オフィス狛 技術部のKoma(Twitterアカウントの中の人&CEO)です。

Node.js + ESLint で開発を行っていると、良く出てくるエラーが、
「Use object destructuring prefer-destructuring」です。

直訳すると、「オブジェクトの非構造化の使用」なのですが、初見だと、何をどう修正すれば良いのか分からないんですよね・・・

例えば、以下のようなプログラムがあったとします。(あくまで例なので、意味の無いプログラムになっています)
  const response = await hogeService.getUser();
  if (response.id) {
    const userId = response.id;
  } else if (response.name) {
    const userName = response.name;
  }

「hogeService.getUser()」は、DBやAPIからデータを取得するイメージです。
「hogeService.getUser()」の戻り値の中には、「{id: 1234, name: "test"}」のような構造化されたデータが入っている事が前提で、そのデータと構成を「response」に設定している、という感じです。

これでESLintのチェックに掛けると、見事に
error Use object destructuring prefer-destructuring
が出ます。

「オブジェクトの非構造化の使用」を促されているので、問題なのは構造化されている「response」だと分かります。
(正確に言うと、構造化された後の使い方ですが)

と言う事で、
  const { id, name } = await hogeService.getUser();
  if (id) {
  } else if (name) {
  }

と直す事で、ESLintのエラーが出なくなります。
「const { id, name }」とする事で、構造化から解放されていますね。

初見だと分かり難いですが、
お決まりのパターンの修正なので、慣れるのが一番ですね。


2018年9月26日水曜日

Moment.jsでDeprecation warning(value provided is not in a recognized RFC2822 or ISO format)が発生した場合の対応方法(Angular,Pipe)

9月 26, 2018

オフィス狛 技術部のKoma(Twitterアカウントの中の人&CEO)です。

前回、AngularのPipeを使ってhtml(View)の表示を共通化する。と言う記事を書きましたが、実は実装をしている時に一つハマったことがあります。

最終的な実装の前に記載していたプログラムは以下の通りです。
import { Pipe, PipeTransform } from '@angular/core';
import * as moment from 'moment';

@Pipe({
  name: 'changeDateFormat'
})
export class ChangeDateFormatPipe implements PipeTransform {

  transform(value: string, args?: any): any {
    const postDate = moment(value).format('YYYYMMDD');
    const today = moment().format('YYYYMMDD');
    const yesterday = moment().subtract(1, 'days').format('YYYYMMDD');

    let displayDate: string = value;

    if (postDate === today) {
      displayDate = '今日';
    } else if (postDate === yesterday) {
      displayDate = '昨日';
    }

    return displayDate;
  }

}

上記で実行すると・・・・
Deprecation warning: value provided is not in a recognized RFC2822 or ISO format. moment construction falls back to js Date(), which is not reliable across all browsers and versions. Non RFC2822/ISO date formats are discouraged and will be removed in an upcoming major release. Please refer to http://momentjs.com/guides/#/warnings/js-date/ for more info.
Arguments: 
[0] _isAMomentObject: true, _isUTC: false, _useUTC: false, _l: undefined, _i: 2018/09/01, _f: undefined, _strict: undefined, _locale: [object Object]
Error

とエラーになってしまいます。
momentに引き渡す日付は、ISO形式ではなくていけないようです。 ただ、そもそもの値が文字列なので、文字列操作でISO形式にフォーマットするのが面倒・・・ と言う事で、前回の実装では、
    const postDate = new Date(value);
    const postDateISO = moment(postDate.toISOString()).format('YYYYMMDD');
上記のように一度日付に戻してから、「toISOString()」を使うようにしました。
元々、トラブルの多い「new Date()」を使いたく無いので、momentにしたのですが・・・

と言う訳で、最終的な実装は、前回も記載した通り、
import { Pipe, PipeTransform } from '@angular/core';
import * as moment from 'moment';

@Pipe({
  name: 'changeDateFormat'
})
export class ChangeDateFormatPipe implements PipeTransform {

  transform(value: string, args?: any): any {
    const postDate = new Date(value);
    const postDateISO = moment(postDate.toISOString()).format('YYYYMMDD');
    const today = moment().format('YYYYMMDD');
    const yesterday = moment().subtract(1, 'days').format('YYYYMMDD');

    let displayDate: string = value;

    if (postDateISO === today) {
      displayDate = '今日';
    } else if (postDateISO === yesterday) {
      displayDate = '昨日';
    }

    return displayDate;
  }

}

となります。
上記実装を参考にされる方は、タイムゾーンに注意して下さいね。


2018年9月25日火曜日

AngularのPipeを使ってhtml(View)の表示を共通化する。

9月 25, 2018

オフィス狛 技術部のKoma(Twitterアカウントの中の人&CEO)です。

Angularのhtml(View)で変数を展開する場合、中括弧を二つ(double curly braces)で変数を囲みます。
<div>{{ targetDate }}</div>

さて、例えば、この「targetDate」変数に「2018/09/01」のような日付が設定されていて、
その日付が本日であれば『今日』、昨日だったら『昨日』と表示し、それ以外の時だけ、日付をそのまま表示するとします。
そんな時、どんな実現方法があるでしょうか?

「*ngIf」を使う?そもそもTypeScript側で判定しておく?色々とありますが、「*ngIf」を使うと煩雑になって、他に同じような表示箇所があったら、その度に記載する必要がありますし、APIから取得した値などは、html(View)側でリストのまま繰り返し処理を行う、
<ng-container *ngFor="let item of list">
  <div class="hoge">{{item.targetDate}}</div>
</ng-container>
という方法を使う事が多いので、TypeScript側で編集はしないんですよね。

まあ、『そんなのAPI側の責務でしょ』と言われたらそれまでですが、
表示内容に特化しているなら、フロントエンドでやるのも悪手では無いと思います。

さて、話が逸れてしまいましたが、こんな時に役に立つのが、AngularのPipe機能です。

簡単に言うと、htmlに表示する時に同時に変換処理も行い、表示内容を変えてしまう、と言う機能です。

早速使ってみましょう。
angular-cliを利用しているならば、
ng g pipe [path/name]
と言うコマンドでテンプレートを作成出来ます。

今回は、[path/name]の部分を pipe/change-date-format に変えます。
すると、以下のようなファイルが生成されます。
import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
  name: 'changeDateFormat'
})
export class ChangeDateFormatPipe implements PipeTransform {

  transform(value: string, args?: any): any {
    return null;
  }

}

この状態で、実装を行なっていきます。
今回、日付の編集・判定には、「moment」を使用します。
momentのインストールと使用方法は省きますが、実装結果は以下のようになります。
import { Pipe, PipeTransform } from '@angular/core';
import * as moment from 'moment';

@Pipe({
  name: 'changeDateFormat'
})
export class ChangeDateFormatPipe implements PipeTransform {

  transform(value: string, args?: any): any {
    const postDate = new Date(value);
    const postDateISO = moment(postDate.toISOString()).format('YYYYMMDD');
    const today = moment().format('YYYYMMDD');
    const yesterday = moment().subtract(1, 'days').format('YYYYMMDD');

    let displayDate: string = value;

    if (postDateISO === today) {
      displayDate = '今日';
    } else if (postDateISO === yesterday) {
      displayDate = '昨日';
    }

    return displayDate;
  }

}

処理は単純で、引数に送られてきた値を日付に変換、形式の統一、そして判定を行い、その日付が本日であれば『今日』、昨日だったら『昨日』、それ以外は日付をそのまま return しています。

Pipeの使い方は、
<div>{{ targetDate | changeDateFormat }}</div>
となります。@Pipe の name をvertical lineで変数に繋げて(Pipe)記載するだけです。簡単ですね。

大規模なプロジェクトになればなるほどPipeを上手く使うと、開発効率が上がります。
それでは、より良いAngularライフを!


2018年9月21日金曜日

Amazon Linux 2 にnginxをインストールする。(Amazon Linux Extrasを使う)

9月 21, 2018

オフィス狛 技術部のKoma(Twitterアカウントの中の人&CEO)です。
今回は完全に自分用の備忘録です。
(毎回同じ問題にぶちあたり、同じ情報を調べるので・・・・)

さて、早速ですが、AWS EC2 のAmazon Linux 2 に、nginxをyumでインストールしようとすると・・・
$ sudo yum install -y nginx
読み込んだプラグイン:extras_suggestions, langpacks, priorities, update-motd
パッケージ nginx は利用できません。
エラー: 何もしません

nginx is available in Amazon Linux Extra topic "nginx1.12"

To use, run
# sudo amazon-linux-extras install nginx1.12


と、エラーになってしまいます。
・・・まあ、メッセージに解決策があるのですが、
念の為、説明しておくと、Amazon Linux 2には、
Amazon Linux Extras という仕組みがあって、良く使われるソフトウェアの最新バージョンについて、関連パッケージを含めて簡単にインストールすることが出来ます。
AWS自体が提供している仕組みだけあって、OSの安定性を保ったインストール・利用が可能になります。

実際にどんなものを提供しているのか見てみましょう。「amazon-linux-extras」というコマンドを実行してみます。
$ amazon-linux-extras
  0  ansible2                 available  [ =2.4.2 ]
  1  emacs                    available  [ =25.3 ]
  2  memcached1.5             available  [ =1.5.1 ]
  3  nginx1.12                available  [ =1.12.2 ]
  4  postgresql9.6            available  [ =9.6.6  =9.6.8 ]
  5  postgresql10             available  [ =10 ]
  6  python3                  available  [ =3.6.2 ]
  7  redis4.0                 available  [ =4.0.5  =4.0.10 ]
  8  R3.4                     available  [ =3.4.3 ]
  9  rust1                    available  \
        [ =1.22.1  =1.26.0  =1.26.1  =1.27.2 ]
 10  vim                      available  [ =8.0 ]
 11  golang1.9                available  [ =1.9.2 ]
 12  ruby2.4                  available  [ =2.4.2  =2.4.4 ]
 13  nano                     available  [ =2.9.1 ]
 14  php7.2                   available  [ =7.2.0  =7.2.4  =7.2.5 ]
 15  lamp-mariadb10.2-php7.2  available  \
        [ =10.2.10_7.2.0  =10.2.10_7.2.4  =10.2.10_7.2.5 ]
 16  libreoffice              available  [ =5.0.6.2_15 ]
 17  gimp                     available  [ =2.8.22 ]
 18  docker=latest            enabled    [ =17.12.1  =18.03.1 ]
 19  mate-desktop1.x          available  [ =1.19.0  =1.20.0 ]
 20  GraphicsMagick1.3        available  [ =1.3.29 ]
 21  tomcat8.5                available  [ =8.5.31 ]
DBやWebコンテナ、言語、フレームワークなど、色々ありますね。

さて、nginx1.12 が利用可能(available)になっているので、インストールしてみます。
sudo amazon-linux-extras install nginx1.12

これでインストール完了です。もう一度「amazon-linux-extras」というコマンドを実行してみます。
 $ amazon-linux-extras
  0  ansible2                 available  [ =2.4.2 ]
  1  emacs                    available  [ =25.3 ]
  2  memcached1.5             available  [ =1.5.1 ]
  3  nginx1.12=latest         enabled    [ =1.12.2 ]
  4  postgresql9.6            available  [ =9.6.6  =9.6.8 ]
  5  postgresql10             available  [ =10 ]
  6  python3                  available  [ =3.6.2 ]
  7  redis4.0                 available  [ =4.0.5  =4.0.10 ]
  8  R3.4                     available  [ =3.4.3 ]
  9  rust1                    available  \
        [ =1.22.1  =1.26.0  =1.26.1  =1.27.2 ]
 10  vim                      available  [ =8.0 ]
 11  golang1.9                available  [ =1.9.2 ]
 12  ruby2.4                  available  [ =2.4.2  =2.4.4 ]
 13  nano                     available  [ =2.9.1 ]
 14  php7.2                   available  [ =7.2.0  =7.2.4  =7.2.5 ]
 15  lamp-mariadb10.2-php7.2  available  \
        [ =10.2.10_7.2.0  =10.2.10_7.2.4  =10.2.10_7.2.5 ]
 16  libreoffice              available  [ =5.0.6.2_15 ]
 17  gimp                     available  [ =2.8.22 ]
 18  docker=latest            enabled    [ =17.12.1  =18.03.1 ]
 19  mate-desktop1.x          available  [ =1.19.0  =1.20.0 ]
 20  GraphicsMagick1.3        available  [ =1.3.29 ]
 21  tomcat8.5                available  [ =8.5.31 ]

nginx1.12 が使用出来る状態(enabled)になっています。

念の為、コマンドを実行してみます。
 $ nginx -v
nginx version: nginx/1.12.2

無事にインストール出来ているようです。

以上、本当に備忘録でした。