ITの隊長のブログ

ITの隊長のブログです。Rubyを使って仕事しています。最近も色々やっているお(^ω^ = ^ω^)

2016年振り返り~2017年の目標

明日から来年ですね。

www.aipacommander.com

今年の1月4日に上の記事書いたので結果はどうなったでしょうか。という公開処刑をこれから行います。

IT技術について

びっくりするほど何もできていない(°ω°;

あらー。これは落ち込みますねーorz

全くできていないとまではいいませんが、機械学習とかチュートリアルぐらいしか終わっってないし。せいぜいやったといえばJavaScriptかな。目標をもう少し具体的にしないと比較することもできないねぇ。

ちなみに今年身に付けたと思った内容としては

  • CakePHP2

だけ。。。orz

後半触って苦労しているのは

  • CakePHP3
  • Angular2

かな・・・。

来年なんですが、ちょっと基礎を見直したいと思います。今年よく使った言語順として

  1. PHP
  2. JavaScript
  3. SQL
  4. Python
  5. Java
  6. Scala
  7. VB

こんな感じなので、1,2と3を抜かして4を中心に基礎勉強しようかなと。基本が大事と思った年なので。

プライベートで稼ごうにもどうしても本業に時間を多く時間を割いてしまい、特に今年の後半は中々勉強の時間や試す場が少なかったと思います。基本って結構疎かになっているような気がするので、来年前半はしっかり基礎を固めたいと思う。

具体的に何をする?

本を読みながら手を動かす

個人的に知見を広めるにはこれが一番手っ取り早いです。

ただ、手を動かしながら読むのは結構大変で時間もかかる(経験から)ので、半年を目標にして頑張ります。え? 結構スケジュールきびしいんじゃない? こうでもしないと私はぐーたらしてやらないのです(・ω・;

できるだけさっさと読み漁って手を動かして後半楽しみたい。

転職する

んー。これがいまいち考えがパッとしていません。

今Web会社にいるんですけど、"Web開発"ではないのです。んで、私はプログラマーになりたいのです。

コミュニティに遊びに行って、飲み会とかで他の方々とお話すると転職したほうがいいとよく言われているので、じゃあ是非!って気持ち自体はなっています。

「じゃあ転職せいや」ってなるとは普通思いますが、今このレベル、この習慣で別会社に席を移しても何も変わらないんじゃ・・・?という不安があります。

今技術がない理由を環境のせいにするのはなんかちょっと違うような気がしています。

環境はあくまで+@であって、やっぱり自身のレベル上げには自分次第なのでは?と思っているのです。

2016年はどう?って言われたときに、結局仕事をこなすだけの知識を取り入れたのみで、プログラマーとしては全然成長していないと思います。なので、2017年はコミュニティでLTするとか、自分の作品をGithubに3つアップするなど、そういったことを短い時間でできるようにしていきたいと考えています。これができたら転職しよう。そしたら、明るい転職活動ができるかも。今は不安しかないのでw

  • コミュニティはなんでも良いので、LTを最低2つ発表
  • Githubに自分の作品を3つアップ(サービスでも良い)

これで行きましょう。がんばろ。

ちなみに、今の不満も書いておくね

  • "開発"会社じゃないこと
  • 通勤と退勤で合計2時間かかること
  • リリースの環境が選べない。インフラ技術力がほぼ0な点が大きい。ので、リリース作業やテスト環境などの自動化ができない

給料はプログラマーの割には・・・とよく言われますが、そこに不満はないです。生活できているので。(まぁもっと貰えるならほしいけど)

英語

去年の目標

githubにて英語でコミット 字幕無しで映画を鑑賞

できませんでしたw残念。

ただ、個人的にはこれ結構いい感じじゃないかな? 身についているかさて置き、少しずつ習慣になってきている感じです。

アプリで毎日5つの英文を覚える

Real英会話

Real英会話

  • LT Box Co., Ltd.
  • 教育
  • ¥980

これこれ。すごくいいと思います。

スピーキングとか中々クリアできなくて、すげーイライラしますよw 英語の"R"って上顎の喉側に舌をつくかつかないかのところに置いて(要するに巻き舌)、”アール”って言わないと通じないとか結構リアル。

また、アプリなので、スマートフォンを持っていたらちょっとした待ち時間で勉強できるのでオススメです。

StackOverflowに投稿するようになった

日本の質問サイトで中々解決できなかったので、試しに1時間かけてw英文を書いて、プログラムのソースコードと一緒に投稿したらすぐに返事が返ってきた!

これはすごくうれしかった。英語ヘッタクソでも通じるもんだね。プログラムすごい。

stackoverflow.com

今は英語を使いたくて、なんか仕事でハマったら、逆にうれしい。「よっしゃ!これで投稿できる!」みたいなw

ちなみに、曖昧なことを書いたり、プログラムのソースコードを載せないと回答くれません。やっぱりソースコードを書かないと意図を組みにくいんじゃないかな?

洋書を購入してみた

上にも書いたのですが、Angular2を勉強するためにng-book2という電子書籍を購入しました。英語です。

英語を勉強したいってわけじゃなくて、Angular2を勉強したかった。けど、日本に書籍がないので(リリースが2016年9月ぐらいだから当たり前っちゃ当たり前か)、ヤケクソで購入しました。

いざ、読もうとするとやっぱり読めませんw

そこでGoogle翻訳ですよ。電子書籍なので、英文のコピペや単語を右クリックして意味を翻訳してもらいます。それでちまちま読んでいる感じ。

それを繰り返していると、"Notice,"とかよく出てくるので「ああ、”注目する”ところがあるんだな」とか、よく出てくる単語は勝手に覚えていきます。

最初、ストレスは溜まりやすいですが、目的があるならそれを理由にチャレンジするのもいいかもしれません。

読書

2016年の目標は

技術書 - 月に1冊 年間12冊 実用書 - 週に1冊 目標は53冊です。

さぁどうでしょう!

f:id:aipacommander:20161231152242j:plain

13冊!(内訳: 技術書: 4冊、実用書: 9冊)

orz

むりぽー。全くとどいてへんやん・・・

個人的にはGoogleの本に一番時間がかかりました。翻訳本って結構読みづらいですね。

また、好きになった本はこちらです。

f:id:aipacommander:20161231152311j:plain

好きになりすぎて、続きの本と布教用の本も買った。(布教しているけど、中々伝わらない(´・ω・`))

今副業でデモを作ったりしますが、短い時間でお客さんにデモを開発して見せるってめっちゃ大変です。ソニックガーデンさんはさらに、2、3つの案件は余裕でこなせるらしいです。すごい。

また、実際自分の本業でも実感することがありました。

本業の顧客からよく伺うのは

納品までに、申し訳ないがどうにか要件と違うことをやってほしい(要は仕様変更) 追加の発注は稟議を通すのが大変、または時間がかかるのでストレス 実際動かしてみるとやっぱり違う感じがする

というお話をよく伺いました。これってやっぱ納品型のビジネスってシステム開発に向いていないじゃないかなと考えています。仕様変更なんて100%発生します。自分はそのつもりですが、周りの人や経営陣は不満を感じています。

ただ、納品のない開発をまだ経験したことがないため、メリット・デメリットがよくわかっていないので(文章ではわかった気になっていますが、個人の成功事例がないので)、副業で試していこうと思います。

転職活動に是非この本の著者さんの会社にも挑戦したいなと考えています。

来年の目標は?

技術書は上で書いた通り、前半は全部で9冊です。

で、実用書なんですが、できればゆるーく読んでいこうと考えています。個人的に目標とすることは書評です。

実際読んだ本は、月日が立つと内容が薄れていきます。で、覚えている本って実は書評書いた本だったり手を動かした本なんですよね。

やっぱり実際試した本は記憶に残りやすいのか、良いと思いました。せっかく時間使って読んでいるので、読む本は書評が前提として読むようにしていきます。

  • 本数は20冊
  • 書評前提
  • あと必須じゃない限りは本を購入しない(買い過ぎ。積み本になっている本が多いorz)
    • 可能な限りすべて読んでから購入すること

生活

貯金ができるようになりました。ヤッター!!!

今貯金十万単位で貯金できてるよ!!!ヤッター!!!

元パチンカスで、浪費型な私が! 貯金できるようになりました!

これってなんかブログの記事になりそうなので、箇条書きで理由を書いておきます。

  • 使ったお金は必ずログに残す(Dr.walletを使っています)
  • 収入 > 消費の式が成り立つように月々生活する
    • 貯金額を1万以上毎月振り込む
    • コンビニは悪
  • ATMのカードを作らずに通帳を複数発行して、給与・支払いなどを毎月自分の足で歩いて引き落とし・振込などをする

大まかにこの3点です。これで借金や支払い滞納を返済しながら(今年は50万ぐらい返済できた!)貯金ができた!

まぁちょっと色々あって今年最後に大きな負債がでてきましたが、これは普通に返して2年を予定しています。

が、そんなに不安ではないですね。今ならもっと短くで返済できる見通しが立てれそう。がんばろ。

達成できないかったやつ

  • 引っ越し&結婚
    • 来年できるかな・・・?ってかやらないと
  • ダイエット
    • 見事に同じ体重のまま。まずい
    • 一応ジョギングは続いています。何故かやせないのよねー
  • ギター
    • 全く習慣がなくなりました。もったいない

まとめ

今年は色々な出会いがありましたー。みんなに負けないようにがんばりますー。

これから走ってきます。来年もよろしく。

Angular2を触り始めたがこいつがフレームワークってことを忘れていました

意味深なタイトルっぽいけど、今日の反省です。技術的な話はしないのでそのつもりで来た人はブラウザバック。

CakePHP3をサーバサイドで、フロントをAngular2で開発しようとしました。

CakePHP3側はJson APIとして使っていましたが、部分的にCakePHP側でもviewを用意していました。

例えばPostsControllerがあるとして、addとeditのページ(アクション)を用意しました。

<?php

class PostsController extends AppController {

    public function add() {
    }

    public function edit() {
    }
}

ただ、addとeditのformの中には動的なfieldがあり、そこをAngular2でやろうとしたのです。

これが間違いだった。

さぁ、いざ組んでみますと、Angular2のコードがモリモリ増えていきます。(やってみたらわかります)

ついには、CakePHP側のViewがすべてAngular2になりました。

用意したaddやeditはただ、jsを読む込ませるためのtemplate表示になりました。そのあとはフロント側のAngular2がデータをリクエストしてtemplateを元にviewを組み立てていく。ま、いいんじゃね?と思い開発を進めました。

が、困ったのはそのあと。

addページで保存したあと、次のアクションをどうしようか迷ったときです。

普通は、その場で編集しますか?と促したり、indexページへリダイレクトしたりするはずです。

私もそこで、そういうwindow.confirmを用意して、ユーザーに判断させるコードを実装しました。

        // ...
        if (window.confirm('データを保存しました。編集を続けますか?')) {
          this.postCrudService.saveData(response);
          this.router.navigate(['/posts/edit/' + response.data.id])
        } else {
          this.router.navigate(['/posts/'])
        }
        // ...

this.routerはAngular2のモジュールである、Routerモジュールです。こいつを使うことで、ページの移動などを実装することができます。this.postCrudServiceはDIしている自作のサービスクラスです。こいつで保存したデータを使いまわすようにしています。

私は、addで保存したら、編集ページに移動しようとしました。が、Angular2は思ったとおりに動作しませんでした。

私は、リロードすると思いましたが、しませんでした。そういえばsingle page applicationってことを忘れていました。

これは大変です。私は部分的にAngular2を使おうとしていましたが、CakePHP3でやろうとしていたことがAngular2でもできるようになっているのです。マジのマジでCakePHP3はJsonAPIになってしまった。

あらー、1日中連携しようともくもく頑張っていたのに、これじゃPostController->index()のtemplateもAngular2にしないとダメってことだな。あららのら。

確かに読む込むだけのaddやeditがあるってなんかキモいもんな。今回の場合だと

  • index - ページ読み込み用
  • add - jsonで新規保存
  • edit - jsonで編集保存
  • delete - jsonで削除

っていうAPIを用意して、全部Angular2に任せればよかったのか。

この休日ずっとAnguarl2(ng-book2)を勉強すればよかった。知らずにそのまま開発してしまった。

あー失敗した。やばい。やばい。やばいぃーー!!!

jQueryしか使ったことがない人がAngularのFormで試したことを書く

この記事はAngular Advent Calendar 2016 17日目の記事です。(遅刻しました)

この記事を書いている人

  • jQueryを使って、基本的な使い方でホームページのナビゲーション開閉やタブのアニメーションを書ける人
  • jQueryを使って、動的なFormを作ったり、サーバへリクエスト飛ばしたりする人
  • jQueryを使って、5000行ぐらいのシミュレーターみたいなアプリケーションを書いたことがある人

仕事で使っているというだけで、jQueryJavaScriptを深くは理解していません。大好き!ってわけでもありません。「javascript やりたいこと」でググって解決するレベルです。Googleがないと仕事できない。

そんな人がAngular2を試したら

  • 「AngularJS1.x触ったことあるしへーきへーき」 -> (°ω° オレノシッテイルアンギュラハドコ?
  • 「Typescript?ES6?なにそれ?」
  • 「webpack?gulpとかnpmじゃだめなの?」

↑の状態で挑んだら簡単に死亡いたしました。本当にありg(ry

で、そんな人がググり続けたとしても、古い情報なのか新しい情報なのかわからるはずもなく、地雷を踏みづづけるだけで、一向に闇から抜け出せません。マジで挫折一歩手前。

近くに簡単に質問できる人もコミュニティもないため、洋書ですが本を購入することにしました。

  • ng-book2

www.ng-book.com

記事の内容のほとんどが↑の本から学習したことなので、この記事読まなくて↑の本読んだ方が早かったりするかもです(^^ゞ ※英語の本です。 ※私は英語はほとんど理解してませんが、Google翻訳を使って読むとびっくりするほど内容が理解できます。ありがとうGoogle

この本購入してからAngular楽しいお(^ω^ = ^ω^)

本記事は ng-book2 を読んで、Angular Formで学習したこと、Angular Formで試したことのログをまとめた記事です。

Form in Angular2

この記事で試したソースコードgithubに上がっています。実際にコードを動かしたほうが理解すると思いますー。

github.com

はじめの環境構築

必要なアプリケーションのインストール

この記事は、Typescriptとangular-cliを使います。また、OSはOSXです。windowsの方はすみません(´・ω・`)

自分の環境は以下。

$ sw_vers 
ProductName:  Mac OS X
ProductVersion: 10.11.6
BuildVersion: 15G1108

$ node -v
v6.6.0

$ npm -v
3.10.9

それでは環境構築します。

$ npm install -g typescript
$ npm install -g angular-cli@1.0.0-beta.18
# angular-cliの監視オプションに使うツールらしい
# linuxの人はどうすればいいの? 
# -> https://ember-cli.com/user-guide/#watchman
# windowsの人はどうすればいいの?
# -> なんか必要ないんだって。nodea.jsに実装されているwatcherを使うんだって
$ brew install watchman

無事インストールできたら、確認。

$ ng version
angular-cli: 1.0.0-beta.18
node: 6.6.0
os: darwin x64

おk

プロジェクトを作りましょう

$ ng new angular-hello-form-application

これだけ。That's it. 感動。

動作確認してみましょう

angular-cliApacheとかwebアプリケーション的なものを用意しなくても、すぐに動作を確認できるオプションが用意されています。

$ cd angular-hello-form-application/
$ ng serve
** NG Live Development Server is running on http://localhost:4200. **
...

ng serve実行後、すぐにURLが表示されるので、それをコピってブラウザで確認してみてください。

f:id:aipacommander:20161226192107p:plain

この画面がでてきたら、環境構築完了です!

それでは本題に進みます。

FormControlとFormGroup

Angular2でFormを使いたいなら、この2つのモジュールを利用します。

FormControlは、1つのinputフィールドを意味します。

let firstName = new FormControl("Aipa");
console.log(firstName.value);  // "Aipa"
console.log(firstName.errors); // 型はStringMap<string, any> of errors
console.log(firstName.dirty);  // 値は変わったか検知。boolean
console.log(firstName.valid);  // validateの結果が登録されます。boolean

templateで使いたい場合はこう書きます。

<!-- formの中で書く -->
<input type="text" [formControl]="name">

んで、これらをまとめてくれるのが、FormGroupです。

let yourName = new FormGroup({
  firstName: new FormControl('Aipa'),
  lastName: new FormControl('Commander')
});

console.log(yourName.value); // -> {
//  firstName: "Aipa",
//  lastName: "Commander"
// }

console.log(yourName.errors); // StringMap<string, any> of errors
console.log(yourName.dirty);  // false
console.log(yourName.valid);  // true

というふうに、まとめることができます。

FormControlは1つのフィールド、FormGroupはフィールド全体を見てくれます。

例えば、firstNameのFormControlのバリデーションがfalseだった場合、yourNameのバリデーションもfalseになります。部分的にチェックしてエラーを表示したり、全体のバリデーションがうまくいかないとsubmitさせないなどができると思います。

yourName.formControl['firstName'].valid; // false
yourName.formControl['lasttName'].valid; // true
yourName.valid; // false

簡易フォームの実装

それでは、実際に実装してみましょー。

と、その前に、ちゃんと説明しようと書いていましたが、説明の仕方に悩んだときにこんな記事を見つけました。

;°ω°)モウコレデイイジャン

もっと早く会いたかったです。

まぁ自分は自分なりに説明書いてログにします。

本題に戻ります。先ほど作ったプロジェクトのディレクトリで、componentをgenerateします。

$ ng generate component first_form

まずは、generateしたcomponentに実装していきます。

おっと、その前にFormControlやFormGroupを使えるようにするために、~/angular-hello-form-application/src/app/app.module.tsで、FormsModuleとReactiveFormsModuleをimportします。

~/angular-hello-form-application/src/app/app.module.ts

import { FormsModule, ReactiveFormsModule } from '@angular/forms'; // <- add

@NgModule({
  declarations: [
    AppComponent,
    FirstFormComponent
  ],
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule
    FormsModule,        // <- add
    ReactiveFormsModule // <- add

これでおkです。

ちなみに、FormsModuleのみimportすると

  • ngModel
  • NgForm

を組み合わせて、Formを扱うことができます。ReactiveFormsModuleも一緒にimportすると

  • formControl
  • ngFormGroup

を含め、色々使えるようになります。

ふーん。Reactive リアクティブってなに?

ぐぐったらこの記事がでてきた。ふむ。

Webフロントエンドでリアクティブプログラミング

いまいちピンときていません。分かる人いたら教えて下さい。

まぁいまはなんだっていいでしょう。実装に話を戻します。

~/angular-hello-form-application/src/app/app.component.htmlにタグを追加します。先ほどgenerateしたcomponentをrenderできるように追記します。

~/angular-hello-form-application/src/app/app.component.html

<app-first-form></app-first-form>

formの実装に入ります。

~/angular-hello-form/application/src/app/first-form/first-form.componet.htmlに、formを追加します。

<form #f="ngForm" (ngSubmit)="onSubmit(f)">
  <div class="form-group">
    <label>お名前は?</label>
    <input type="text" placeholder="名前を入力してください" name="yourName" ngModel>
  </div>
  <div class="form-group">
    <button type="submit">送信</button>
  </div>
</form>

↑のように追加すればおkです。

突然あらわれる#f=ngFormngFormにびっくりしましたが、これはFormsModuleをimportするだけで、NgForm(<form #f="ngForm" ...>)がviewで使えるようになり、また自動的に<form>にアタッチされます。

それでは、component.tsへメソッドを追加します。

~/angular-hello-form/application/src/app/first-form/first-form.componet.ts

export class FirstFormComponent implements OnInit {
  // ...
  onSubmit(form: any): void {
    console.log(form.value);
  }
}

追加したフォームに"アイパー隊長"と入力し、送信ボタンをクリックすると、consoleから、Object {yourName: "アイパー隊長"} がでてきました。これでよし。

#f=ngForm#fはテンプレートで使用できるようするローカル変数です。つまりこの箇所はローカル変数を宣言している&値をセットしているのです。

#fがフォームの値となります。「テンプレートで使える変数」という理解でおkだと思います。

また、NgFormの型はFormGroupです。なので、変数fは、FormGroupとして扱うことができます。

最初のFormGroupとFormControlの関係を説明した通り、FormControlはFormGroupに追加されて扱えるようになります。

このフォームではngModelがセットされているinputタグの値は、自動的にFormControlを作成して、属性であるname=***で関連付けられます。そして、FormGroupである変数fにセットされています。

このフォームでsubmitボタンをクリックすると、onSubmitのイベントが発生します。その時実行されるのが、<form>の属性にある(ngSubmit)="onSubmit(f.value)"です。

Angularで()は、イベントをセットするときに使う属性と理解しています。んで、実行されるのが右側のコードです。onSubmit(f.value)は、Componentに実装してあげます。

上ですでに動きは確認しました。もちろんconsole.log()だけではなく、フォームにセットされた値を確認したり、バリデーションしたり、サーバへ送信したりなど、やりたいことを実装してあげれば良いです。

よっしゃ!理解したぞ!(多分)

ここまでが、単純にフォームを使うだけのチュートリアルです。

FormBuilderでFormのカスタマイズ

ngForm、ngModelを使って、暗黙的にFormControl、FormGroupを使ってみました。

ただ、このままだとカスタマイズができません。

実践では、もっと複雑に、詳細にカスタマイズするはずですので、今度はそれができるFormBuilderを使ってみましょう。

今、自分もこのFormBuilderを実践で使っています。

ng-book2をFormBuilderはどういうふうに使えばいいの? 語るよりはまずは実際に実装してみました。

コンポーネントをジェネレートします。

$ ng generate component use-form-builder

~/angular-hello-form/application/src/app/app.component.html

<h1>
  {{title}}
</h1>
<app-first-form></app-first-form>

<app-use-form-builder></app-use-form-builder> <!-- これを追加 -->

んじゃ、使用するComponentで、FormBuilderをimportします。

~/angular-hello-form/application/src/app/use-form-builder/use-form-builder.component.ts

import { FormBuilder, FormGroup } from '@angular/forms';

importしただけじゃ使えないので、DIしましょう。 ※"DI (dependency injection)"って何?の人。自分もよくわかっていないので、近くのエロい人たちに聞いてください。

export class UseFormBuilderComponent {
  myForm: FormGroup;

  constructor(formBuilder: FormBuilder) { // <- これがDI(らしい)
    this.myForm = formBuilder.group({
      'myName': ['Aipa']
    });
  }

  onSubmit(value: string): void {
    console.log('わたしの名前は' + value);
  }
}

これで、FormBuilderが使えるようになりました。constructor()の引数にセットすることで、このComponentで利用できるようになりました。

FormBuilderでよく使うメソッドは2つ

  • control - FormControlを新規で作成します
  • group - FormGroupを新規で作成します

この2つです。

formBuilder.group({})は、FormGroupを返します。

また、formBuilder.group({})の中に、KeyValueにセットされているmyForm: ['Aipa']はFormConotrolです。また、['Aipa']は初期値です。

ちなみに、arrayってなんてのもありますが、この記事の最後あたりで試したログがあります。

説明がだるくなってきたので、まずは動かしてみてみましょう。templateを用意して、ビルドしてみてください。

~/angular-hello-form/application/src/app/use-form-builder/use-form-builder.component.html

<h2>ふぉーむびるだーずふぁいたーずです。</h2>
<form [formGroup]="myForm" (ngSubmit)="onSubmit(myForm)">
  <div class="form-group">
    <label>お名前は?</label>
    <input type="text" placeholder="名前を入力してください" [formControl]="myForm.controls['myName']">
  </div>
  <div class="form-group">
    <button type="submit">送信</button>
  </div>
</form>

実行するとこんな画面が出力されるはず。初期値が入っていますね。

f:id:aipacommander:20161226192217p:plain

また、submitしてみると、consoleに"わたしの名前はAipa"とでるはず。これでおkです。

少し説明します。

<form>タグの属性に、[formGroup]="myForm"とあります。この[formGroup]は、ディレクティブとして動作しており、myFormをこのフォームのFormGroupとして利用することを宣言しています。

次に、<input>タグの属性に[formControl]="myForm.controls['myName']"とあります。これは既存のFormControlにFormBuilderで作成したformControlをバインドしています。これでComponent側で細かく設定する情報を付与することができます。

Validation

次はValidationを試します。

~/angular-hello-form/application/src/app/use-form/builder/use-form-builder.component.ts

import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';

@Component({
  selector: 'app-use-form-builder',
  templateUrl: './use-form-builder.component.html',
  styleUrls: ['./use-form-builder.component.css']
})

export class UseFormBuilderComponent implements OnInit {
  myForm: FormGroup;

  constructor(formBuilder: FormBuilder) { // <- これがDI(らしい)
    this.myForm = formBuilder.group({
      'myName': ['Aipa', Validators.required]
    });
  }

  ngOnInit() {
  }

  onSubmit(form: any): void {
    console.log(form);
    console.log('わたしの名前は' + form.controls['myName'].value);
  }
}

~/angular-hello-form/application/src/app/use-form-builder/use-form-builder.component.html

<h2>ふぉーむびるだーずふぁいたーずです。</h2>
<form [formGroup]="myForm" (ngSubmit)="onSubmit(myForm)">
  <div class="form-group" [class.error]="!myForm.controls['myName'].valid && myForm.controls['myName'].touched">
    <label>お名前は?</label>
    <input type="text" placeholder="名前を入力してください" [formControl]="myForm.controls['myName']">
    <div *ngIf="!myForm.controls['myName'].valid">myName is invalid</div>
    <div *ngIf="myForm.controls['myName'].hasError('required')">myName is required</div>
  </div>
  <div class="form-group" *ngIf="!myForm.valid">Form is invalid</div>
  <div class="form-group">
    <button type="submit">送信</button>
  </div>
</form>

ValidationはValidatorsをimportして利用します。

先ほど、key valueでformControlの設定をFormBuilder.groupへ渡しました、valueの値は配列であり、要素の0番目が初期値でした、Validationは要素の1番目にセットします。今回はValidators.required(空文字NG)をセットしました。

templateには、エラーを表示するようにngIfを用意しています。最初のほうで説明したformControlやformGroupにある、validを使っています。また、セットした特定のValidationのエラーを表示したい場合は、 myForm.controls['myName'].hasError('required')を使って出力します。

次は複数のValidationをセットします。4文字以上入力しないとエラーを出力するようにしてみます。Validators.minLenghtを使います。

「複数あるから・・・」って考えたら、要素の2番めに配列でValidatorsを渡したくなると思います。それでも動きました。が、本ではそんな説明がありませんでした。(ネットにはちらほらありますが)

配列で渡すのは正規な方法ではないのかな?

この記事ではValidators.compose([])を使います。

~/angular-hello-form/application/src/app/use-form/builder/use-form-builder.component.ts

  constructor(formBuilder: FormBuilder) {
    this.myForm = formBuilder.group({
      'myName': ['Aipa', Validators.compose([ // <- これを追加
          Validators.required,
          Validators.minLength(4)
        ])
      ]
    });
  }

~/angular-hello-form/application/src/app/use-form-builder/use-form-builder.component.html

<h2>ふぉーむびるだーずふぁいたーずです。</h2>
<form [formGroup]="myForm" (ngSubmit)="onSubmit(myForm)">
  <div class="form-group" [class.error]="!myForm.controls['myName'].alid && myForm.controls['myName'].touched">
    <label>お名前は?</label>
    <input type="text" placeholder="名前を入力してください" [formControl]="myForm.controls['myName']">
    <div *ngIf="!myForm.controls['myName'].valid">myName is invalid</div>
    <div *ngIf="myForm.controls['myName'].hasError('required')">myName is required</div>
    <div *ngIf="myForm.controls['myName'].hasError('minlength')">myName must be at least 4 characters long.</div> <!-- ここを追加 -->
  </div>
  <div class="form-group" *ngIf="!myForm.valid">Form is invalid</div>
  <div class="form-group">
    <button type="submit">送信</button>
  </div>
  <pre>{{myForm.controls['myName'].errors | json}}</pre>
</form>

全然関係ないですが、このValidators.minLength(4)、templateでエラーを出力する属性を用意したら何故か出力されませんでした。

最初書いていたコードはこちら。

<div *ngIf="myForm.controls['myName'].hasError('minLength')">myName must be at least 4 characters long.</div> <!-- ここを追加 -->
hasError('minLength') // "minLength" => ダメ、"minlenght" => おk

(°ω° キャメルケースじゃダメなのね。。。

<form>の閉じタグ1行前で、<pre>{{myForm.controls['myName'].errors | json}}</pre>で確認したら発覚しました。わかりづらい。

ちょっとハマったが、Validationを複数扱うときは、Validators.compose()を使えばおk.

Custom Validation

Validationの続きですが、自前でValidationも用意することができます。

返り値はStringMap<string, boolean>で返せば良いです。クラスがあるとわかりにくいのでようは

{ [s: string]: boolean }

これを返せばよい。

~/angular-hello-form/application/src/app/use-form/builder/use-form-builder.component.ts

function fullMatchStringAipa(control: FormControl): { [s: string]: boolean } {
  // 初期値がない場合はエラーになったので
  // nullチェックもする
  if (control.value) {
    if (!control.value.match(/^Aipa/)) {
      return {invalidAipa: true};
    }
  }
}

// ... 省略

  constructor(formBuilder: FormBuilder) {
    this.myForm = formBuilder.group({
      'myName': ['Aipa', Validators.compose([
          Validators.required,
          Validators.minLength(4),
          fullMatchStringAipa // <- これを追加
        ])
      ]
    });
  }

~/angular-hello-form/application/src/app/use-form-builder/use-form-builder.component.html

<div *ngIf="myForm.controls['myName'].hasError('invalidAipa')">myName is string "Aipa"</div>

hasError()で渡す値は、自前で用意したValidationの返り値{ [s: string]: boolean }s: stringの値を渡してください。

Watching for changes

formControlの値の変更を検知することができます。AngularJS1.xではすごく苦労した記憶があります(物覚え悪いからだと思いますが)。今回はどうでしょう。

~/angular-hello-form/application/src/app/use-form/builder/use-form-builder.component.ts

  constructor(formBuilder: FormBuilder) {
    this.myForm = formBuilder.group({
      'myName': ['Aipa', Validators.compose([
          Validators.required,
          Validators.minLength(4),
          fullMatchStringAipa
        ])
      ]
    });

    // 追加した記述
    this.myForm.controls['myName'].valueChanges.subscribe(
      (value: string) => {
        console.log('aipa value change: ', value);
      }
    );
  }

個人的にはすごくわかりやすかったです! こんなすぐ使えるのねー。

次は試したかったこと。です。

Form in Angular2 で、試したこと

Enter keyの無効化

フォームで入力中にEnter keyを2度押してしまって、ページが遷移するってことありません? わたしはよくあります。

あれが嫌で、作成するフォームはだいたい<form onsubmit="return false;">をしています。

これが良いか悪いかの判断はできませんが(できる方、教えてくださいm(_ _ )m )、とりあえずAngular2でもやりたかったので、試してみました。

<form [formGroup]="myForm"
      (ngSubmit)="onSubmit(myForm.value)"
      (keydown.enter)="keyDown($event)" <!-- 追加 -->
      class="ui form">
</form>

keydown.enterで、エンターキーが押されたときのイベントが発火したときに、メソッドをコールすることができる。Component側に下記を実装。

keyDown(event: any): void {
  console.log('You just clicked entry.');
  return event.preventDefault(); // enterを無効化
}

これでできました。

Input form bind date picker

日付入力のフォームには、date pickerをよく使いますよね。jQueryだったらすぐ使えます。

が、Angular2ではどう扱ったらいいのはわからず、ググり方もわからなかったので、時間を多く浪費しました。

pluginも色々試しましたが、何故かうまくいかなかったんですよねー。

色々学んできて、やっとできたので、これもメモしておきます。

3rd Party Library Installation

まず、必要なライブラリをインストールします。

$ npm install pikaday moment --save-dev 

今回はpikadayを使います。また、moment.jsも依存しているため、これもインストール。

んで、ビルドツールである、angular clijsonファイルに追記します。

~/angular-hello-form-application/angular-cli.json

"app": [{ 
      "styles": [
        "styles.css",
        "../node_modules/pikaday/scss/pikaday.scss"
      ],
      "scripts": [
        "../node_modules/pikaday/pikaday.js",
        "../node_modules/moment/moment.js"
      ]
}]

次に使いやすいようにdirectiveをを用意します。

$ ng generate directive date-picker
installing directive
  create src/app/date-picker.directive.spec.ts
  create src/app/date-picker.directive.ts
import { Directive, ElementRef, Input } from '@angular/core';

// 宣言?
declare let Pikaday: any;

@Directive({
  selector: '[appDatePicker]'
})
export class DatePickerDirective {

  // この変数は呼び元コンポーネントから値をもらうので
  // @Input()の値をselectorと同じにする
  @Input('appDatePicker') datePickerField: any;

  constructor(
    // pikadayのクラスのbindにはフィールドの情報が必要なため
    // ElementRefを使って自分のフィールドを渡す
    private elementRef: ElementRef
  ) {}

  ngOnInit() {
    var picker = new Pikaday({
      field: this.elementRef.nativeElement,
      format: 'YYYY/MM/DD',
      i18n: {
        previousMonth : '先月',
        nextMonth     : '来月',
        months        : ['1月','2月','3月','4月','5月','6月','7月','8月','9月','10月','11月','12月'],
        weekdays      : ['日曜日','月曜日','火曜日','水曜日','木曜日','金曜日','土曜日'],
        weekdaysShort : ['日','月','火','水','木','金','土']
      },
      onSelect: (date) => {
        // 何故かpikadayをbindしただけじゃ、formControlの値を変更することができない
        // onSelect時に、直接formControlへ選択した文字列を渡すようにする
        this.datePickerField.setValue(picker.toString());
      }
    });
  }

}

ちなみに、悩みとしては、angular cliでビルドしていますが、pikadayのcssなどが、htmlへinlineで追加されてしまい、他のcssに影響がでています。

上書きしてもいいですが、あとあと絶対面倒になるので、どうにか必要なページだけpikaday、pikadayのcssをロードできないか方法を探し中です。知っている人いましたら教えてください(><

追記 2016/12/26

必要なページだけ読み出す方法がわかりました。

まず、angular-cliに追加した情報を削除します。 ~/angular-hello-form-application/angular-cli.json

"app": [{ 
      "styles": [
        "styles.css",
        "../node_modules/pikaday/scss/pikaday.scss" // 削除
      ],
      "scripts": [
        "../node_modules/pikaday/pikaday.js", // 削除
        "../node_modules/moment/moment.js" // 削除
      ]
}]

んで、directiveをcomponentへ変更して、デコレータのパラメータを変更します。というか、Componentとして作り直したほうがいいかもしれません

~/src/app/date-picker/date-picker.component.ts

import { Component, ElementRef, Input, ViewEncapsulation } from '@angular/core';

// 宣言?
const Pikaday = require('../../../node_modules/pikaday/pikaday');
const PikadayStyle = require('../../../node_modules/pikaday/scss/pikaday.scss');

@Component({
  selector: '[appDatePicker]',
  template: '',
  styleUrls: [PikadayStyle],
  encapsulation: ViewEncapsulation.None
})
export class DatePickerComponent {

個別のページだけモジュールを使うって方法がわからなかったので、汚い英語ですが、stackoverflowに投稿して回答を待ちましたところ、「requireを使えばいけるよ」的な回答を頂いたので、試してみたらまじでうまくいきました(°ω°

stackoverflow.com

自分の実装はDirectiveで試していましたが、styleをあてたかったのと、Directiveのデコレータでstyleを指定する方法がわからなかったので、Componentへ変更しました。

また、Componentが吐き出すstyleは、cssセレクターに動的の属性が当てられます。しかし、pikadayのdomはComponentの属性が付与されないため、単純にComponentを用意するだけではstyleはあたりません。

なので、encapsulationの値に、ViewEncapsulation.Noneを渡してあげると、(よろしくないとは思いますが)属性を付与しなくなるので、pikadayのdomにstyleがあたるようになります。これでおkです。

これで必要なComponentだけstyleとmoduleをロードすることができるようになりました。githubの方も修正しています。

動的なフォーム

これが一番やりたかった。

ng-book2 にはのっていなかったので、できるか心配でしたが、色々ググって試してみたらできたのでメモしておきます。

$ ng generate component dynamic-form
installing component
  create src/app/dynamic-form/dynamic-form.component.css
  create src/app/dynamic-form/dynamic-form.component.html
  create src/app/dynamic-form/dynamic-form.component.spec.ts
  create src/app/dynamic-form/dynamic-form.component.ts

~/angular/hello-form/application/src/app/dynamic-form/dynamic-form.component.html

<h2>動的フォーム</h2>
<form [formGroup]="myForm" (ngSubmit)="onSubmit(myForm)">
  <div class="form-group">
    <label>お名前は?</label>
    <input type="text" [formControl]="myForm.controls['yourName']">
  </div>
  <!-- この下のタグを渡せないと動作しなかった。group -> arrayにも名前を用意しないといけないらしい -->
  <div formArrayName="whatAnimationDoYouLike">
    <div *ngFor="let testDatetime of myForm.controls.whatAnimationDoYouLike.controls; let i=index">
      <div [formGroupName]="i">
        <div class="delete-button">
          <span>Address {{i + 1}}</span>
          <button type="button" *ngIf="myForm.controls.whatAnimationDoYouLike.controls.length > 1" (click)="removeGroup(i)">フォームから削除</button>
        </div>
        <div class="form-group">
          <label>アニメの名前は?(必須)</label>
          <input type="text"
            [formControl]="myForm.controls['whatAnimationDoYouLike'].controls[i].controls['answer']">
        </div>
        <div class="form-group">
          <label>好きな理由は?(任意)</label>
          <input type="text"
            [formControl]="myForm.controls['whatAnimationDoYouLike'].controls[i].controls['reason']">
        </div>
      </div>
    </div>
  </div>

  <div class="margin-20">
    <button type="button" (click)="addGroup()">フォームを追加</button>
  </div>
  <pre>{{myForm.value | json}}</pre>
  <div class="form-group">
    <button type="submit">送信</button>
  </div>
</form>

属性formArrayName="whatAnimationDoYouLike"の箇所。これも必須です。Cannot find control with unspecified name attributeのエラーが発生します。group -> arrayにも名前を用意しないといけないらしい。

下はComponentです。動的なフォームを組みたい場合、FormArrayが必要になりますので、importしてください。

~/angular/hello-form/application/src/app/dynamic-form/dynamic-form.component.ts

import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, FormArray, Validators } from '@angular/forms';

@Component({
  selector: 'app-dynamic-form',
  templateUrl: './dynamic-form.component.html',
  styleUrls: ['./dynamic-form.component.css']
})
export class DynamicFormComponent implements OnInit {
  myForm: FormGroup;

  constructor(
    private formBuilder: FormBuilder
  ) { }

  ngOnInit() {
    this.myForm = this.formBuilder.group({
      'yourName': ['', Validators.required],
      'whatAnimationDoYouLike': this.formBuilder.array([
        this.initGroup()
      ])
    });
  }

  initGroup() {
    return this.formBuilder.group({
      'answer': ['', Validators.required],
      'reason': ['']
    });
  }

  addGroup() {
    const arrayControls = this.getFormArrayControls();
    arrayControls.push(this.initGroup());
  }

  removeGroup(i: number) {
    const arrayControls = this.getFormArrayControls();
    arrayControls.removeAt(i);
  }

  getFormArrayControls() {
    return <FormArray>this.myForm.controls['whatAnimationDoYouLike'];
  }

  onSubmit(form: any) {
    console.log(form);
  }
}

initGroup()で、複数のformControlをgroupingして値を返します。

addGroup()は、今現在のFormArrayを取得して、initGroup()から追加データをpush()して追加しています。

removeGroupはその逆です。現在のFormArrayを取得して、指定された要素番号でFormArrayを、removeAt(i)しています。i: numberはテンプレートからもらう情報です。

これでおkです。もしFormArrayの値をwatchしたいとかあれば、this.myForm.controls['whatAnimationDoYouLike'].controls[i].controls['answer']とかでvalueChangesを実行すればおkです。ながいねー。

まとめ(?)

1ヶ月前は「なんでこのフレームワークを選んだんだ(´;ω;`)ブワッ」と泣きそうになりましたが、理解してくると楽しくなってきますねー。

近くに教えてくれる人がいると手っ取り早いですが、そうじゃない場合はやっぱり本がいいのではと思います。

引き続き、Angular2を学んで、使えるようになったらまた色々と記事を上げていきたいです。

【Play Framework2.x】Mail Pluginを使って、なりすましっぽいメールを送信する(できました)

「なりすまし? ダメにきまってんでしょ!?」

そう却下できたらいいんですが、世の中色々な人や仕事がありますので、そうもいかなかった。

業務改善のためにいわゆる”なりすまし”のメールを、業者向けに送らないといけなかった。

不特定多数に送信するのはNGだが、自分たちのサーバやソリューション向けに送信するのは100歩ゆずっておkすることにした。

Play Frameworkのmail pluginを使っていました。以前このブログで記事書いたことがある。

www.aipacommander.com

MailerAPI mail = init();
mail.setFrom("名字 名前 様 <from@gmail.com>");
mail.setRecipient("to@gmail.com");
mail.setSubject("テストの件名ですよー");
mail.send(views.html.mail.contents.render().toString().trim());

こんな感じ。

しかし。。。

使っているメールサーバが外部のサービスのサーバだっため、登録されているメールアドレスのドメインアドレス以外はエラーするって仕様だったのだ! 隊長ピンチ!

じゃあどうしよ! しかしわたしは元(エセ)ネットワークエンジニアだったのでメールの仕様はこちらのサイトで勉強していたのね。

3 Minutes Networking

メーラーで確認できるtoとfromは基本書き換え可能なため、「headerいじればええんじゃね?」ってひらめいたのだ。

じゃあ実装変えます。

MailerAPI mail = init();
mail.setFrom("from@gmail.com");
mail.addHeader("from", "名字 名前 様 <from@gmail.com>"); // これを追加
mail.setRecipient("to@gmail.com");
mail.setSubject("テストの件名ですよー");
mail.send(views.html.mail.contents.render().toString().trim());

github.com

addHeaderっていうメソッドがあったので、そこでheaderを追加するようにした。

おkおk〜。っと思いきや。

あれあれあれあれ〜? めっちゃエラーがでてる。。。

iandeth.dyndns.org

ぐぐったらこちらの記事が。。。なるほど。マルチバイト悪だな。

じゃあこのコードをJavaで実装するようにしました。

MailerAPI mail = init();
mail.setFrom("from@gmail.com");
mail.addHeader("from", encodeFromString("名字 名前 様", "from@gmail.com>"));
mail.setRecipient("to@gmail.com");
mail.setSubject("テストの件名ですよー");
mail.send(views.html.mail.contents.render().toString().trim());

// ... 省略
private static String encodeFromString(String fromString, String mailAddress) {
    String fromText = "";
    try {
        String encodeFromText = new String(fromString.getBytes("ISO2022JP"));
        String baseEncodeFromText = new String(Base64.encodeBase64(encodeFromText.getBytes()));
        fromText = "=?iso-2022-jp?B?" + baseEncodeFromText + "?=" + " <" + mailAddress + ">";
    } catch (Exception e) {
        Logger.trace(e.toString());
    }
    return fromText;
}

Base64は↓のライブラリをimportした

weblabo.oscasierra.net

やっていることは単純で

  1. ISO2022JPでエンコードして
  2. ↑をBase64エンコードして
  3. エンコードした文字列の先頭に"=?iso-2022-jp?B?"と、末尾に"?="をつける
  4. ↑の文字列に"<メールアドレス>"の文字列を半角空白文字で結合する

これでよし。これでエラーが発生せず、安全(?)でなりすましできるようになりました。

と思いきや。。。。この↑のコードではうまくいかないことがわかりました\(^o^)/オワタ

どうやら、"duplicate from"にひっかかったみたいで、サーバに渡せてもメールサーバがエラーを送信者に返す事態に。

うぐぐぐ・・・!どないせーちゅーねん。

ということで、色々探した結果 Mail Pluginではできないことが発覚しました。タイトル変えます。

んじゃ、どうすればいいの? JavaMailを使いましょう。

import javax.mail.Message;
import javax.mail.PasswordAuthentication;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;

public class Mail
    public static void main(String args[]) {
      try {
          String makeFromText = "テスト様";
          String fromText = encodeFromString(makeFromText, "spoofing@gmail.com"); // なりすましのメール
          sendMail(
            "件名",
            fromText,
            "to@gmail.com",
            "mail body"
          );
      } catch (Exception $e) {
        // ...
      }
    }

    private static void sendMail(String subject, String from, String to, String body) throws Exception {
        // 定数は任意で設定ください
        String contentType = "text/plain; charset=UTF-8";

        Properties props   = new Properties();

        props.put("mail.transport.protocol", "smtp");
        props.put("mail.smtp.auth", "true");
        props.put("mail.smtp.starttls.enable", "true");
        props.put("mail.smtp.host", HOST);
        props.put("mail.smtp.port", PORT);
        props.put("mail.smtp.from", MAIL_LOGIN_ID); // これがSMTP Authに使うfrom

        Session mailSession = Session.getInstance(props);
        mailSession.setDebug(true);

        MimeMessage message = new MimeMessage(mailSession);
        message.addFrom(InternetAddress.parse(from)); // mail spoofing
        message.setRecipients(Message.RecipientType.TO, to);
        message.setSubject(subject);
        message.setContent(body, contentType);
        message.setHeader("Content-Transfer-Encoding", "7bit"); // 最初文字化けしたけど、この行を追加したらいけた

        Transport transport = mailSession.getTransport();
        try{
            System.out.println("Sending ....");
            transport.connect(HOST, PORT, FROM_MAILADDRESS, PASSWORD);
            transport.sendMessage(message,message.getRecipients(Message.RecipientType.TO));
            System.out.println("Sending done ...");
        }
        catch(Exception e) {
            System.err.println("Error Sending: ");
            e.printStackTrace();
        }
        transport.close();
    }
}

コメントにも書いていますが、PASSWORDなどの定数は任意で設定ください。

ちなみにGmailの場合は、Session mailSession = Session.getInstance(props);の箇所を下記に拡張する必要がある。

        Session mailSession = Session.getInstance(props,
            new javax.mail.Authenticator() {
                protected PasswordAuthentication getPasswordAuthentication() {
                    return new PasswordAuthentication(FROM_MAILADDRESS, PASSWORD);
                }
            }
        );

これでうまくいきましたー!やったーー!

ちなみに、最初↑のコードをどっからかパクってきて、動作させようとしましたが、いまいちピンと来ず、混乱していました。

頭を整理するためにも質問サイトで質問事項を書いて、何がわからないのかを質問してみました。

stackoverflow.com

汚い英語だと思いますが、返事が返ってくる! 何故かコメントで。。。(どうアクションすれば良いのだ)。あとみんなやさしいいい。うれしい。

I believe mail.smtp.from is the envelope MAIL FROM

↑は、mail.smtp.fromエンベロープの"MAIL FROM"と言っているのかな? というかメールセッションの中に"from"を設定するところがあるじゃん!

ってことで、コメントと逆の発想でmail.smtp.fromに、メールサーバにログインするときに使うMAIL FROMのIDを記述。

んで、おそらくエンベロープの"from"はmessage.addFrom(InternetAddress.parse(from)); // mail spoofing←ここでしょ。ってことで、ここに記述。

そしたらでキタ━━━━(゚∀゚)━━━━!!

コードはちゃんと読もう&あとでコメントに感謝して解決内容を投稿しておきます。

【Angular2】親子じゃない同じ階層にいるComponentのメソッドを使いたいとき

タイトルだけじゃよくわかりませんね。つまりこういうこと。

  • AppComponent
    • SearchComponent
    • FormComponent
    • ItemComponent
      • LinkComponent

みたいな構造があったときに、Linkでイベントが発生したらItemでとあるメソッドを実行する。とか、ItemからLinkに値を渡すってことはできるようになりました。要は親子関係だったら@Input()とか@Output()を使えばいけるのよね。

が、Formで追加したあとに、ItemのViewを更新したいとなった場合、どうやればいいの?って思ったので、調べたことメモ。

stackoverflow.com

↑が参考になりました。テンプレート上でコンポーネントの変数を用意して、同じ階層にいるコンポーネントに渡せば良いのです。

  • AppComponent.html
<search></search>
<form [itemComponent]="item"></form>
<item #item></item>

↑のように変数の宣言(?)は使う後でもおk。一旦全部解析してからリンクする感じなのかな? なんか多用しちゃうと読みづらいコードになりそう。。。

import { ItemComponent } from './item.component';

@Component({
  selector: 'form',
  templateUrl: './form.component.html'
})

export class FormComponent {
  @Input() private itemComponent: ItemComponent
  // ...
}

これで使えるようになりました。