狛ログ

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

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

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


2018年9月19日水曜日

knex.js の transaction でなぜか delete されないときに注目すること。

9月 19, 2018

オフィス狛 技術部 CTOの Taka-yamです。

knex.jsは、Node.jsでActiveRecordを用いてSQLを発行するライブラリです。
fluentにSQLを構築でき、大変便利だなと感じる一方で、
トランザクション中にSELECT分を使用する際にちょっとした落とし穴があり注意が必要です。

例えば、booksというidとnameを保存するシンプルなテーブルがあるとして考えます。
id |  name  |
---+-------------+
 1 |  我輩は猫である|
 2 | こころ       |
 3 | 三四郎       | 

例えば、このテーブルのid1のデータを 削除->存在チェック->登録と実行します。
// knexの定義
const knex = require('../db/knex');

knex.transaction(trx => {
    
    // 全て削除
    knex.from('books').del();

     // 消えているか確認(存在チェック)
    const user = knex.from('books') . where('id', 1).first();

    if (typeof user !== 'undefined') {
        throw new Error(' データがあるよ')
    }

    // 登録
    const books = {id: 1, name: '銀河鉄道の夜'}
    knex.insert(books).into('books');
}

上記の メソッドは存在チェック後のifでエラーを投げます。

なぜか?

これは selectがクロージャ内に渡されているKnex.Transactionインスタンスを使用していない ため、
トランザクションとは別に処理を走らせてしまっているためです。

クロージャ内にいるので一見すると同じトランザクション として扱われるのかなと思ってしまいますが、実際には下記のようにしないといけません。
knex.transaction(trx => {
    
    // 全て削除
    trx.from('books').del();

     // 消えているか確認(存在チェック)
    const user = trx.from('books').where('id', 1).first();

    if (typeof user !== 'undefined') {
        throw new Error(' データがあるよ')
    }

    // 登録
    const books = {id: 1, name: '銀河鉄道の夜'}
    trx.insert(books).into('books');
}

selectに関しては下記のようにも 書けます。
const user = knex.transactioning(trx).from('books') . where('id', 1).first();

例えば処理を共通化して別メソッドとして切り出したりしていると、
思わぬところでトランザクションが途切れてしまっていることがあり、
「???」になることがあります。

また、個人的にですが、Realmなどのライブラリですと、
クロージャ内がトランザクション領域として扱われるので
勘違い して同じように使用してはまってしまったという経緯もありました。

もし同じような現象で困っている方がいらっしゃいましたら、
上記のような可能性も考えてみてください。

2018年9月3日月曜日

Illustratorでカラーバリエーションのあるようなアイコンやイラストをできるだけ楽に作る。

9月 03, 2018

この記事は2018年9月時点のオブジェクトを再配色機能の説明です。
2020月11月に新しい機能などの説明を加えた記事を投稿しています↓


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

今回はカラーバリエーションのあるようなアイコンをできるだけ楽に作る方法をご紹介したいと思います。
アプリ内の画像などを作っている時に背景色によってアイコンの色が変わったり、状態によってアイコンカラーが変わったりするときの画像がすこーし簡単に作れるようになります。


Illustratorで描いたイラストです。
パーツが少し多いのでもうちょっと一部の色を明るくしたいなと思ったり、カラーバリエーションのある画像が欲しいと思うとちょっと大変ですよね。
こういう時に使える機能が「オブジェクトを再配色」機能です。


まず色を変えたいオブジェクト全体を選択し、Illustratorメニューバーの「編集」の「カラーの編集」から「オブジェクトを再配色」を選ぶかツールバーのオブジェクトを再配色アイコン」クリックします。

下のようなオブジェクトを再配色パレットが出てきます。

編集ボタンを押すと色相環のような画面になるので右下の「ハーモニーカラーをリンク」ボタンをクリックしておきます。(これをクリックするとまとめて色相や彩度が変わります)
色相環のような図の中の丸い色つきの丸を移動させると色相が変わるのでこれで一気に全体的な色が変えられます!

色を変えてみました。


一部だけ色が変えたい場合は「ハーモニーカラーをリンク」ボタンを押さずに変えたい部分の色のみの位置を動かしたり、指定画面(編集ボタンの横にある「指定」ボタンで画面が戻ります)から色を変えたりできます。

一部色を変えてみました。

簡単に絵のカラーパターンができました!

グラデーションで使われている色などもこの機能で変えられるので、カラーバリエーションのある画像がササッと数枚作れます。
使う機会が多そう&時短になる機能なので、ぜひ使ってみてください。


2018年8月30日木曜日

AngularでyoutubeAPI(Iflame Player API)を使ってyoutube動画を埋め込み表示する。

8月 30, 2018
オフィス狛 技術部のHammarです。

いろんなサイトでyoutube動画が見れるように工夫しているところが多いですが、今回はAngularでyoutubeAPI(Iflame Player API)を利用して、サイトにyoutube動画を埋め込み表示する方法を書いてみたいと思います。

■実装環境

・Angular
・ngx-youtube-playerモジュールを利用

今回使用するngx-youtube-playerモジュールはインストール済みとします。
まずモジュールのimportを記述します。
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { YoutubePlayerModule } from 'ngx-youtube-player';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppComponent } from './app';

@NgModule({
  imports: [BrowserModule, YoutubePlayerModule],
  declarations: [AppComponent],
  bootstrap: [AppComponent]
})
export class AppModule {}

platformBrowserDynamic().bootstrapModule(AppModule);

次にyoutubeを埋め込みたい画面のファイルに下記のように記述します。
このあたりの記述はnpmjs.comのサンプルの記載とほぼ同じ感じです。
import { Component } from '@angular/core';
@Component(
  selector: 'app',
  template: `
        <youtube-player
      [videoId]="id"
      (ready)="savePlayer($event)"
      (change)="onStateChange($event)"
    ></youtube-player>
    `
})

export class AppComponent {
  player: YT.Player;
  private id: string = 'qDuKsiwS5xw';

  savePlayer(player) {
    this.player = player;
  }
}

これで基本的な下準備は完了で、
private id: string = 'qDuKsiwS5xw';
の部分で対象動画のyoutubeのidをセットすれば動画の埋め込みは完了です。
すごく簡単ですね!

で、カスタマイズも結構簡単にできますので、いくつか紹介したいと思います。

■動画が完了したタイミングで何か処理をする 

上記のcomponent.tsファイルに
  onStateChange(event) {
    if (event.data === 0) {
        // 動画終了してからの処理
    }
  }
のように追記して動画終了してから自動で何か処理をさせることもできます。
動画のステータスは
    -1 – 未開始
    0 – 終了
    1 – 再生中
    2 – 一時停止
    3 – バッファリング中
    5 – 頭出し済み
で判定することができますので、ステータスが変わったタイミングでなにかの処理を入れたりすることができます。

■プレイヤーのコントロールを別ボタンでやる

たとえば動画の再生や停止をyoutube内部のコントローラーではなく、プレイヤー外部にボタンを新たに設定してそこで制御したい場合などは下記のように追記するだけで実装可能です。
まずhtml側に下記のようなボタンを作成しておきます。
<button (click)="playVideo()" ><p>再生</p></button>

component.tsファイルに下記関数を設定します。
// 再生ボタン
playVideo() {
    this.player.playVideo();
}
これだけで制御が可能になります。
上記の要領で停止や音量の変更、ミュートやシークも簡単に制御ができます。

■動画終了後の表示のカスタマイズ

動画が終了すると関連の動画も勝手に表示されたりして、表示動画とは全く違うジャンルの動画リスト等もデフォルトで表示され、サイトによっては余計な情報も表示されてしまうので、この表示をいくつか消したいときがあると思います。
このあたりの表示内容についてもカスタマイズ可能です。
動画終了後の関連動画を非表示にする場合、component.tsファイルのtemplate部分を下記のように記述します。
@Component({
  selector: 'app',
  template: `
        <youtube-player
      [videoId]="id"
      (ready)="savePlayer($event)"
      (change)="onStateChange($event)"
      [playerVars]="{'rel': 0}"
    ></youtube-player>
    `
})

上記の[playerVars]="{'rel': 0}"という記述が関連動画を非表示にするパラメータになります。
上記以外にも、動画の再生が始まる前に動画のタイトルやアップロードしたユーザーなどの情報カスタマイズや、動画のプレーヤーコントロールを表示するかどうかを指定したりすることができます。

上記で紹介したのはほんの一部で、下記公式サイトにいろいろなカスタマイズのリファレンスやプレイヤーパラメータの詳細が載っていますので、ぜひ参考にしてみてください。

・Iflame Player APIリファレンス
https://developers.google.com/youtube/iframe_api_reference?hl=ja#Playback_controls
・youtubeプレイヤーパラメータ
https://developers.google.com/youtube/player_parameters?playerVersion=HTML5&hl=ja

2018年8月26日日曜日

AngularでChart.jsを使った時の「Failed to create chart: can't acquire context from the given item」に対応する。

8月 26, 2018

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

Chart.jsは、『グラフと言えばChart.js』ぐらいに有名なライブラリです。
Abgularで使用する場合、npm install で簡単に取り込む事が出来ます。

インストールは本家のGitHub、 Angularで使う場合はこちらのサイト
に非常に詳しい説明があります。

さて、上記を参考にいざ使用してみると・・・・

ブラウザのコンソールに
Failed to create chart: can't acquire context from the given item
が出る事が分かりました。
グラフ自体は問題なく動いているんですけどね。

で、色々と試行錯誤した結果、問題ある箇所は、先のサイトで言うところの
<div *ngIf="chart">
  <canvas id="canvas">{{ chart }}</canvas>
</div>

である事が分かりました。これを・・・
<div [hidden]="chart === null">
  <canvas id="canvas">{{ chart }}</canvas>
</div>

多分、
[hidden]="!chart"
でも大丈夫だと思います。(と言うか、上記の方が良いかも)

要は、「*ngIf」を使ってしまうと、falseになった時に、レンダリングされないので、
chart.jsから「id="canvas"」という要素が見つけられないのでしょうね。

ちょっとハマったので、
同じようなエラーが出た方は、上記で回避してください。