ぺーぺーSEのブログ

備忘録・メモ用サイト。

所謂Angular2でMEANスタックを作る

Angular(所謂Angular2)でMEANスタックのアプリを作成してみる。
と言いつつMongoDBについてはほぼ言及しない。

プロジェクト作成手順

以下のアプリケーションを1つのプロジェクト内に同梱して作成する。

  • フロントエンド
    • Angularベースのブラウザアプリケーション
    • サーバサイドのREST APIで通信する
    • Angular CLI を使用する
    • ソースコードディレクトリは src/
  • サーバサイド
    • NodeJS、ExpressベースのWebアプリケーション
    • ソースコードディレクトリは app/

フロントエンド、サーバサイドの順で作成すること。

フロントエンド

Angular CLIでプロジェクト作成

SCSS、Bootstrap、jQueryベースで作成する。

$ ng new mean2-sample --style=scss
$ cd mean2-sample
$ npm install ng2-bootstrap bootstrap jquery --save

angular-cli.json 修正

// 省略
"styles": [
  "../node_modules/bootstrap/dist/css/bootstrap.min.css", // 追加行
  "styles.scss"
],
"scripts": [
  "../node_modules/jquery/dist/jquery.min.js",           // 追加行
  "../node_modules/bootstrap/dist/js/bootstrap.min.js"   // 追加行
],
// 省略

bootstrap、jqueryのスクリプト、スタイルシートを追加。

src/app/app.module.ts 修正

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';
import { Ng2BootstrapModule } from 'ng2-bootstrap/ng2-bootstrap'; // 追加行

import { AppComponent } from './app.component';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule,
    Ng2BootstrapModule // 追加行
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

ng2-bootstrapのモジュールを追加。
その他Angularの詳細については以下参照。

blog.pepese.com

サーバサイド

Angular CLIで作成したプロジェクトにサーバサイド、つまりExpressを追加する。
サーバサイドアプリケーションのソースディレクトリは app/ だけで完結するようにする。
フロントエンドとは異なりトランスパイル不要。
フロントエンドトランスパイル後の dist をExpress側で公開ディレクトリに設定する。

サーバサイドアプリケーションを作成する方法は以下を参照。
「フロントエンドアプリケーションをプロジェクトに同梱する場合」のほうの構成。

blog.pepese.com

起動

$ ng build                                      // フロントエンドのビルド
$ NODE_ENV=production forever start app/app.js  // サーバサイドアプリ起動

Express入門

Node.jsのWebフレームワーク Express 触ってみた。
下記の記事を読んだテイで書く。

blog.pepese.com

ついでに Yarn も使ってみる。

インストール

Yarnのインストール

$ npm install -g yarn

Expressのインストール

$ npm install express --save

Expressだけインストールするなら上記。

Expressアプリケーションの構築

Expressプロジェクトの作成

$ npm install -g forever
$ mkdir express-sample          // プロジェクトディレクトリの作成
$ cd express-sample
$ yarn init
$ yarn add express@5.0.0-alpha.5 body-parser cookie-parser debug morgan pug serve-favicon request fs file-stream-rotator node-sass-middleware
$ touch .gitignore
$ mkdir app                     // サーバサイドExpressアプリ用のソースディレクトリ作成
$ touch app/app.js              // Expressアプリケーション起動ポイントの作成
$ mkdir app/models              // Mongoose(MongoDB)用のモデルディレクトリ作成
$ touch app/models/.gitkeep
$ mkdir app/repositories        // DAO/Repository用のディレクトリ作成
$ touch app/repositories/.gitkeep
$ mkdir app/config              // 設定ファイル用ディレクトリ作成
$ touch app/config/config.json  // 環境差分ファイル作成
$ mkdir app/log                 // ログ出力用ディレクトリ作成
$ touch app/log/.gitkeep
$ mkdir app/spec                // テストスクリプト用のディレクトリ作成
$ touch app/spec/.gitkeep

Expressアプリケーションのソースディレクトリは app/ だけで完結するようにする。

フロントエンドアプリケーションをプロジェクトに同梱する場合

この場合、以下のようにディレクトリを作成する。

$ mkdir app/api           // REST API用のコントローラディレクトリ作成
$ touch app/api/.gitkeep

フロントエンドアプリケーションのトランスパイル結果は express-sample/dist に出力されるものとし、サーバサイドアプリケーション( express-sample/app )は express-sample/dist をExpressの公開ディレクトリとする。
フロントエンドアプリケーションは app/api に実装したAPIにアクセスする。

フロントエンドアプリケーションをプロジェクトに同梱せず、Expressからテンプレートエンジンを使用する場合

この場合、以下のようにディレクトリを作成する。

$ mkdir app/controllers                  // VIEW用のコントローラディレクトリ作成
$ touch app/controllers/.gitkeep
$ mkdir app/views                        // 画面・テンプレート(Pugなど)用のディレクトリ作成
$ touch app/views/.gitkeep
$ mkdir app/public                       // 静的コンテンツ用のディレクトリ作成
$ mkdir app/public/javascripts           // JS用のディレクトリ作成
$ touch app/public/javascripts/.gitkeep
$ mkdir app/public/stylesheets           // SCSS用のディレクトリ作成
$ touch app/public/stylesheets/style.scss
$ mkdir app/public/images                // 画像用のディレクトリ作成
$ touch app/public/images/.gitkeep

ソースコードの作成

「フロントエンドアプリケーションをプロジェクトに同梱する場合」は以下を参照。

blog.pepese.com

ここでは、「フロントエンドアプリケーションをプロジェクトに同梱せず、Expressからテンプレートエンジンを使用する場合」で、最低限動くViewアプリを構築するために以下のスクリプトを実装する。

  • app/app.js
    • Expressアプリケーションのミドルウェアの設定と起動処理
  • app/controllers/router.js
    • Viewを処理するモジュールへのルーティングを行う
  • app/controllers/get_index.js
    • Index画面を表示する
  • app/controllers/get_users.js
    • Users画面を表示する
  • app/public/stylesheets/style.scss
  • app/views/layout.pug
  • app/views/index.pug
  • app/views/error.pug
  • app/config/config.json
    • 環境差分設定ファイル

app/app.js

app/controllers/index.js

app/controllers/get_index.js

app/controllers/get_users.js

app/public/stylesheets/style.scss

app/views/layout.pug

app/views/index.pug

app/views/error.pug

app/config/config.json

起動

$ NODE_ENV=production forever start app/app.js

NODE_ENV で環境を指定することができる。デフォルトは development

ちょっとExpressの機能解説

Expressアプリケーション

Expressアプリケーションの最小単位は以下のように作成する。

const express = require('express');
const app = express();

ミドルウェア関数

Expressは、単独では最小限の機能を備えたルーティングとミドルウェアのWebフレームワーク。
Expressアプリケーションは様々な ミドルウェア関数 呼び出しを行うことで様々な機能を実現する。
ミドルウェア関数はコールバック関数で以下の種類がある。

  • アプリケーション・レベルのミドルウェア
  • ルーター・レベルのミドルウェア
  • エラー処理ミドルウェア
  • 標準装備のミドルウェア
  • サード・パーティー・ミドルウェア

アプリケーション・レベルのミドルウェア

以下のように記述する。

app.use([path,] callback [, callback...])

path を記載しない場合は 全てのリクエスト に対してミドルウェア関数( callback :コールバック関数)が実行される。
コールバック関数には (req, res, next) の3つを渡すことができる。 next() を実行すると次のミドルウェア関数に処理が移る。( next() を実行しないと処理は終わる

app.use( (req, res, next) => {
  console.log('Time:', Date.now());
  next();
});

path を記載すると 指定したパスへのリクエスト に対してミドルウェア関数が実行される。

app.use('/user/:id', (req, res, next) => {
  console.log('Request Type:', req.method);
  next();
});
app.get('/user/:id', (req, res, next) => {
  res.send('USER');
});

app.useapp.[all|get|post|put|delete|etc] よりも全て前に記述する必要がある

ルーター・レベルのミドルウェア

ルーター・レベルのミドルウェアは、express.Router() のインスタンスにバインドされる点を除き、アプリケーション・レベルのミドルウェアと同じ。

const express = require('express');
const router = express.Router();

正直、 アプリケーション・レベルとルーター・レベルの違いはよくわからない
以下の方針で使い分けることにする。

  • アプリケーション・レベル
    • アプリ全体全てのHTTPメソッド へ影響する処理
    • app.use() を使用する
  • ルーター・レベル
    • 特定のパス特定のHTTPメソッド へのルーティング
    • router.[all|get|post|put|delete](path, callback [, callback...]) を使用する

エラー処理ミドルウェア

引数は (err、req、res、next) となり err が追加される。

app.use( (err, req, res, next) => {
  console.error(err.stack);
  res.status(500).send('Something broke!');
});

標準装備のミドルウェア

Expressの標準装備のミドルウェア関数は express.static のみ。
過去はもったあったらしいが、個別モジュール化されたらしい。

express.static(root, [options])

静的コンテツを提供する機能。
root 引数は、静的コンテンツを提供するルートディレクトリを指定する。
options オブジェクトにはプロパティを指定することができる。

var options = {
  dotfiles: 'ignore',
  etag: false,
  extensions: ['htm', 'html'],
  index: false,
  maxAge: '1d',
  redirect: false,
  setHeaders: function (res, path, stat) {
    res.set('x-timestamp', Date.now());
  }
}

app.use(express.static('public', options));

サード・パーティー・ミドルウェア

npm でモジュールをインストールして、アプリケーション・レベルまたはルーター・レベルでロードして使用する。

const express = require('express');
const bodyParser = require('body-parser'); // サード・パーティー・ミドルウェア

const app = express();

// Parsers for POST data
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));

モジュール呼び出し

module.exportsrequire を使用すると、モジュールの外部参照が可能になる。
以下のような index.js があったとすると

router.post('/', (req, res) => {
  ...
}
module.exports = router; // 外部モジュールからrequireできるようになる

app.js から以下のように index.js の機能を呼び出すことができる。

const index = require('./index');

Expressアプリケーションの設定

Expressでは、以下の記載で「key-value」形式でアプリケーションに対して様々な設定ができる。

app.set(name, value);
app.get(name);

代表的な設定項目としては以下(詳細)があるが、単にインメモリキーバリューストア(好きな「key-value」を設定できる)としても使用できる。

Property Type Description Default
case sensitive routing Boolean ルーティング時、URLの大文字・小文字を区別する。 N/A (undefined)
env String 環境名。productionとかdevelopmentとか。 process.env.NODE_ENV or “development”
etag Varied Etag レスポンスヘッダの設定を行う。 weak
jsonp callback name String JSONP callback名を設定する。 “callback”
json replacer Varied JSON.stringify で利用される引数 replacer の設定。JSONの各メンバの値の変換の設定ができる。 N/A (undefined)
json spaces Varied JSON.stringify で利用される引数 space の設定。JSONパース時に見やすいようにインデントのスペースを設定できる。 N/A (undefined)
query parser Varied URLクエリパラメータのパースの設定ができる。 simpleextended 、カスタマイズから選択できる。 simple はNodeJSが内包するNativeのクエリパーサ。 extended は サードパーティモジュール qs 。カスタマイズは関数を設定し、その関数はクエリパラメータの文字列を受け取り、キー・バリューのオブジェクトを返却するよう実装する。 “extended”
strict routing Boolean ルーティングにおいてURLの /foo/foo/ を区別するかどうか設定する。 N/A (undefined)
subdomain offset Number サブドメインにアクセスするために削除するホストのドット区切り部分の数。 2
trust proxy Varied 信頼するWebプロキシの設定を行う。詳細は、ここを参照。 false (disabled)
views String or Array Viewを配置するディレクトリを文字列もしくは配列で設定する。 process.cwd() + ‘/views’
view cache Boolean Viewテンプレートコンパイルキャッシュを有効にする。 true in production, otherwise undefined.
view engine String Viewエンジンの設定を行う。 N/A (undefined)
x-powered-by Boolean “X-Powered-By: Express” HTTPヘッダを有効にする。 true

モジュール間で変数をやりとりする方法

  • global変数を使う
    • const global.hoge = 'hogehoge'; みたいに、頭にglobalを付けるとglobalスコープで扱われる。
    • ただし、 require('./global.js'); のようにグローバル変数を定義したモジュールを使用するモジュールでロードする必要がある。
    • グローバル領域を汚染するので注意が必要
  • 共有変数用のモジュールを作成する
    • module.exports = {}; とだけ書いた common.js を作成する
    • 他モジュールからは require('./common').hoge = hoge; で変数を代入でき、 const hoge = require('./common').hoge で変数を参照できる
    • どんな変数を作ったからわからなくなるので、 common.js にはコメントくらい残しておく必要がある

LINEチャットボットをNodeJSとExpressで

NodeJSとExpressでメッセージをオウム返しするLINEのチャットボットを作ってみた。
Expressについては以下参照。

blog.pepese.com

LINE部分は LINE@Messaging API を使用した。

作成手順

$ express express-sample --view=pug --git
$ cd line-chatbot && npm install
$ npm install request crypto --save
$ mkdir config
$ touch config/config.json

Viewはなんでもいいが、デフォルトは嫌なのでとりあえす指定してみた。
以下をそれぞれ後述の通り編集する。

  • routes/index.js
  • config/config.json

routes/index.js

config/config.json

実行

$ npm start

https でWebサーバか何かを立てて、 http://[FQDN]:3000/ へプロキシさせる。
その後、LINEアカウントの Messaging API へ上記のWebサーバのエンドポントを Webhook URL として登録する。

ちょっと解説

LINEのMessage APIのAPI Referenceはここ
LINEに限らずWebhookに対応するアプリケーションを作成する際には、そのWebhookのサービスに合わせたインターフェースで開発する必要がある。
ここでは以下について記載する。

  • Webhook リクエスト
    • エンドユーザからメッセージを受信した際、LINE側のサーバから送信されるHTTPリクエスト
  • Reply message リクエスト
    • Webhook リクエストを受け、メッセージを返却する場合にLINE側のサーバへ送信するHTTPリクエスト

Webhook リクエスト

{
  "events": [
      {
        "replyToken": "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA",
        "type": "message",
        "timestamp": 1462629479859,
        "source": {
             "type": "user",
             "userId": "U206d25c2ea6bd87c17655609a1c37cb8"
         },
         "message": {
             "id": "325708",
             "type": "text",
             "text": "Hello, world"
          }
      }
  ]
}
  • X-Line-Signature:{Signature}
    • リクエストの送信元がLINEであることを確認するために署名検証するためのもの
    • ヘッダの値と、request body と Channel secret から計算した signature が同じものであることをリクエストごとに 必ず検証必要がある
      1. Channel secretを秘密鍵として、HMAC-SHA256アルゴリズムによりrequest bodyのダイジェスト値を得る。
      2. ダイジェスト値をBASE64エンコードした文字列が、request headerに付与されたsignatureと一致することを確認する。

Reply message リクエスト

{
    "replyToken":"nHuyWiB7yP5Zw52FIkcQobQuGDXCTA",
    "messages":[
        {
            "type":"text",
            "text":"Hello, user"
        },
        {
            "type":"text",
            "text":"May I help you?"
        }
    ]
}

replyToken にWebhookで送られてきたボディの events[].replyToken を付与する必要がある。
messages の配列で複数のメッセージを返却することができる。
また、ヘッダへ以下を付与する。

  • Content-Type:application/json
  • Authorization: Bearer {ENTER_ACCESS_TOKEN}
    • {ENTER_ACCESS_TOKEN} に Channel Access Token を付与する

Angular入門

フロントエンドMVCフレームワーク Angular (所謂Angular2)を触ってみた。
ここでは Angular CLI を使う。
なお、 ndenv が入ってるテイで書く。導入は下記記事参照。

blog.pepese.com

Angular CLI

インストール

$ npm install -g @angular/cli
$ ndenv rehash
$ ng version
                             _                           _  _
  __ _  _ __    __ _  _   _ | |  __ _  _ __         ___ | |(_)
 / _` || '_ \  / _` || | | || | / _` || '__|_____  / __|| || |
| (_| || | | || (_| || |_| || || (_| || |  |_____|| (__ | || |
 \__,_||_| |_| \__, | \__,_||_| \__,_||_|          \___||_||_|
               |___/
@angular/cli: 1.0.0-beta.32.3
node: 7.5.0
os: darwin x64
@angular/common: 2.4.8
@angular/compiler: 2.4.8
@angular/core: 2.4.8
@angular/forms: 2.4.8
@angular/http: 2.4.8
@angular/platform-browser: 2.4.8
@angular/platform-browser-dynamic: 2.4.8
@angular/router: 3.4.8
@angular/cli: 1.0.0-beta.32.3
@angular/compiler-cli: 2.4.8

以降、 ng コマンドでいろいろできるようになる。

プロジェクト作成と起動

$ ng new angular-sample
$ cd angular-sample
$ ng serve

プロジェクト構成

.
├── README.md
├── .angular-cli.json                * Angular CLI 設定ファイル
├── .editorconfig                    * エディタの設定ファイル
├── .gitignore
├── e2e                              * エンドツーエンドテストディレクトリ
│   ├── app.e2e-spec.ts
│   ├── app.po.ts
│   └── tsconfig.json
├── karma.conf.js                    * karma(テストランナー)の設定ファイル
├── package.json                     * npmの設定ファイル
├── protractor.conf.js               * e2eテストの設定ファイル
├── tslint.json                      * TypeScriptのLinter設定ファイル
└── src                              * ソースファイルディレクトリ
    ├── app                          * アプリケーションコードディレクトリ
    │   ├── app.component.css
    │   ├── app.component.html
    │   ├── app.component.spec.ts
    │   ├── app.component.ts
    │   └── app.module.ts
    ├── assets
    ├── environments
    │   ├── environment.prod.ts
    │   └── environment.ts
    ├── favicon.ico
    ├── index.html                    * トップ画面、ルートコンポーネントを記載
    ├── main.ts                       * ルートモジュールのロード(メイン)
    ├── polyfills.ts
    ├── styles.css
    ├── test.ts
    └── tsconfig.json                 * TypeScriptの設定ファイル

.ditorconfigEditorConfigというプロジェクトの設定ファイルで、エディタへプラグインなどを入れることにより様々なエディタの設定をプロジェクト単位で可能にする。
テストは Jasmine (テスティングフレームワーク)と Karma (テストランナー)の組み合わせで実行される。
e2e(エンドツーエンド)テストは protractor

主要なコマンド

  • ng new
    • 新規にAngularプロジェクトを作成する
  • ng build
    • src 配下をビルドして dist へ出力する
  • ng serve
    • Webサーバを起動してアプリケーションを実行する
  • ng generate
    • blueprints から選択して新たにコードを生成する
  • ng eject
    • ng generate などで作成したコードを取り除き、webpackの設定やスクリプトを変更する
  • ng get
    • 設定(key-value)から値(value)を取得する
  • ng set
    • 設定(key-value)に値(value)を追加する
  • ng lint
    • Linterを実行する
  • ng test
    • テストを実行する
  • ng e2e
    • e2eテストを実行する
  • ng xi18n
    • コードからi18nメッセージを抜く

上記以外や詳細なオプションは ng help を参照。

blueprints の種類

  • module
    • ng generate module sample
    • モジュール( sample/sample.module.ts )が作成される。
    • 自動で指定したモジュール名でディレクトリが切られることに注意
  • component
    • ng generate component sample
    • コンポーネント(以下)を作成し、 app.module.ts に自動登録(importおよびメタデータ declarations への追加)される。
      • sample.component.tssample.component.spec.tssample.component.htmlsample.component.scss
    • なお、既に存在するモジュール名で作成すると、そのモジュール名のディレクトリ内に作成される。
  • directive
    • ng generate directive sample
    • ディレクティブ(以下)を作成し、 app.module.ts に自動登録(importおよびメタデータ declarations への追加)される。
      • sample.directive.tssample.directive.spec.ts
  • pipe
    • ng generate pipe sample
    • パイプ(以下)を作成し、 app.module.ts に自動登録(importおよびメタデータ declarations への追加)される。
      • sample.pipe.tssample.pipe.spec.ts
  • service
    • ng generate service sample
    • サービス(以下)を作成する。
      • sample.service.tssample.service.spec.ts
  • class
    • ng generate class sample
    • 普通のクラス( sample.ts )が作成される。
  • interface
    • ng generate interface sample
    • 普通のインターフェース( sample.ts )が作成される。
  • enum
    • ng generate enum sample
    • 普通の列挙型( sample.enum.ts )が作成される。

ディレクトリを切りたい場合は / を加えて、 ng generate service sample/sample-service のようにする。

Angular CLI のバージョンアップ方法

グローバルでは以下。

$ npm uninstall -g @angular/cli
$ npm cache clean
$ npm install -g @angular/cli@latest
$ ndenv rehash

ローカルプロジェクトでは以下。

$ rm -rf node_modules dist # use rmdir on Windows
$ npm install --save-dev @angular/cli@latest
$ npm install

Angularの基本

Angularのアーキテクチャは以下の要素から構成される。

  • モジュール(@NgModule)
  • コンポーネント(@Components)
  • テンプレート(Templates)
  • メタデータ(Metadata)
  • データバインディング(Data Binding)
  • ディレクテイブ(@Directive)
  • パイプ(@Pipe)
  • サービス(Services)・DI(Dependency injection / @Injectable)
  • フォームとバリデータ
  • Angularライブラリ

上記の各々について後述する。

モジュール(@NgModule)

Angularのアプリケーションはモジュール単位で機能を管理する。
モジュールは後述の コンポーネントテンプレート を包含する。

Angularアプリケーションは少なくとも1つ ルートモジュール を持ち、AppModule と命名する。
ルートモジュールは、 src/main.ts からロードされる。
通常のアプリケーションの場合、ルートモジュールは1つだが、巨大なアプリケーションの場合、複数のルートモジュールを持つこともある。

モジュールは以下のように作成する。

import { NgModule }      from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';

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

AppModule クラスに @NgModule デコレータ (Javaでいうアノテーション)を付与した形になっている。
デコレータにAngularに則した設定を行い、クラスには何も書かない(たぶん)。
@NgModule の代表的なプロパティは以下の通り。ちなみにデコレータに設定するプロパティを メタデータ という。

  • imports
    • このモジュールに他のモジュールを取り込み、このモジュール内で定義されているコンポーネントやテンプレートが他のモジュールのクラスを使用できるようになる。
    • 他のモジュールの providersexports に定義されたものを使用できるようになる。
  • providers
    • このモジュールおよび関係するコンポーネント・サービスへインジェクトするためのサービスクラスを定義・インスタンス化する。
  • declarations
    • このモジュールに所属させるビュークラスを指定する。Angularのビュークラスには、 コンポーネントディレクティブパイプ がある。
  • exports
    • このモジュール内のクラスを他のモジュールのコンポーネント・テンプレートで使用可能にする。
    • imports の逆。
  • bootstrap
    • ルートコンポーネント を定義する。ルートコンポーネントはアプリケーションのメインビュー。
    • ルートモジュールにだけbootstarp プロパティを設定する。

以下が ルートモジュール を指定する方法( src/main.ts )。

import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';

platformBrowserDynamic().bootstrapModule(AppModule);

また、モジュール内では以下のようにライブラリから他のモジュールをロードすることもできる。

import { Component } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';

ロードしたライブラリモジュールは、 @NgModuleimports プロパティに指定することにより使用できるようになる。

imports:      [ BrowserModule ],

コンポーネント(@Component)

コンポーネントはビューの役割を担う。
コンポーネントクラス内で定義したフィールド変数やメソッドは、テンプレートから直接使用できる。
ロジックは基本的にコンポーネント内に記載し、サービスクラスをDIしたりする。

@Component({
  moduleId: module.id,
  selector:    'hero-list',
  templateUrl: './hero-list.component.html',
  providers:  [ HeroService ]
})
export class HeroListComponent implements OnInit {
/* . . . */
}

@Component の代表的なメタデータプロパティは以下。

  • moduleId
    • このモジュールが定義されるファイルのES/CommonJSモジュールID
  • selector
    • このコンポーネントのHTMLタグ名を定義する。
  • template
    • テンプレートを直接記載する。
  • templateUrl
    • テンプレートファイルのパスを指定する。
  • styles
    • スタイルシート(CSSやSass)を直接記載する。このコンポーネント外には影響が無い。
  • styleUrls
    • スタイルシートファイルのパスを指定する。このコンポーネント外には影響が無い。
  • providers
    • このコンポーネントがDI経由で使用するサービスクラスを指定する。
    • コンポーネントクラスの constructor の引数でも指定する必要がある。

その他の @Component のメタデータはここで確認できる。

テンプレート(Templates)

テンプレートはコンポーネントのDOMとして使われるHTML。
コンポーネントクラスの @Component デコレータのメタデータに直接記載( template )することも、別ファイルとして作成して読み込む( templateUrl )ことも可能。
テンプレート内では、HTMLの属性として ディレクティブ*ngFor など)、コンポーネントとのデータ・機能のやりとりに データバインディング を使用することができる。

  • コンポーネントフィールドへのアクセス
    • *ngFor : コレクションフィールドの値にアクセス
    • *ngFor="let hero of heroes" : コレクションheroesの各要素をheroへ代入
  • コンポーネントのメソッド呼び出し
    • (click)="onClickMe()" : クリック時に onClickMe() メソッドを呼び出す
    • (keyup)="onKey($event)" : キーアップ時にイベントを引数に onKey() メソッドを呼び出す

また、テンプレートにはスタイルシート(CSS、Sassなど)を指定することができ、@Component デコレータのメタデータ( stylesstyleUrls )で指定することができる。

メタデータ(Metadata)

メタデータは デコレータ@ から始まるJavaでいうアノテーション)に設定するプロパティ。
Angularにクラスがどのように挙動するか知らせる役割。
@Injectable@Input@Output など様々なデコレータにメタデータを設定できる。
もう十分前述しているのでこの程度で。

データバインディング(Data Binding)

テンプレートとコンポーネント間のデータ・機能のやりとりを行う機能。
以下のように4種類ある。

  • 単方向バインド(interpolation)
    • <li>{{hero.name}}</li>
    • コンポーネントの hero.name プロパティの値を <li> 表示する
  • プロパティバインド(property binding)
    • <hero-detail [hero]="selectedHero"></hero-detail>
    • 親コンポーネントの selectedHero の値を、子コンポーネントの hero へ渡している
  • イベントバインド(event binding)
    • <li (click)="selectHero(hero)"></li>
    • ユーザのクリックにより selectHero() メソッドが呼び出される。
  • 双方向バインド(Two-way data binding)
    • <input [(ngModel)]="hero.name">
    • ngModel を用いて 参照と更新 を両方同時に実現する。

ngModel のようにテンプレートのHTMLタグの属性として記述できるAngularの機能を ディレクティブ という。

イベント

<button (click)="onClickButton()">ボタン1</button>
<button (click)="onClickButtonWithEvent($event)">ボタン2</button>

(click) のように、イベント名をカッコで囲んだ属性に、イベントを処理するメソッドを記述する。
引数なしの記述と、イベントオブジェクト $event を指定する記述ができる。
コンポーネントクラス内では以下のようにメソッド定義する。

onClickButtonWithEvent(event:any) {
   // targetプロパティでイベントを発生させたオブジェクトを取得
   var button = event.target;
   // ボタンのラベルをtextContentプロパティから取得して表示
   alert("ボタン「" + button.textContent + "」が押下されました");
 }

ディレクテイブ(@Directive)

ディレクティブは前述の通り、テンプレートのHTMLタグの属性として指定できるAngularの機能。
モジュール・コンポーネント間のデータの授受や、ループ・条件分岐といった動的なDOMの機能を提供する。

<li *ngFor="let hero of heroes"></li>
<hero-detail *ngIf="selectedHero"></hero-detail>

@Directive デコレータでディレクティブを自作できる。
ここでは割愛。参考

パイプ(@Pipe)

Pipeはテンプレート内で文字列操作の機能を提供する。
例えば、Dateオブジェクトをあるフォーマットに整形して表示できる。

// コンポーネントクラスのフィールド
let birthday = new Date(1985,3,1); // これで1985年4月1日 なことに注意

上記をテンプレードで以下のように指定することにより yy/MM/dd 形式で表示できる。

<p>{{ birthday | date:"yy/MM/dd" }}</p> <!-- 1985/04/01 と表示される -->

他にも下記のようなパイプがある。

  • DatePipe
    • 上記例。日付を整形する。
    • date_expression | date[:format]
  • UpperCasePipe
    • 文字列を大文字にする。
    • expression | uppercase
  • JsonPipe
    • 入力値にJSON.stringfyを実行して返す。
    • expression | json

また、パイプはこんな {{ birthday | date | uppercase}} 感じでチェーンできる。

さらに、@Pipe デコレータでパイプを自作できる。
ここでは割愛。参考

サービス(Services)・DI(Dependency injection / @Injectable)

サービスは export された通常のtypescriptクラス。
DIを使用してコンポーネントや他サービスクラスにサービスクラスをインジェクトできる。

作成例

$ ng generate service sample/sample
installing service
  create src/app/sample/sample.service.spec.ts
  create src/app/sample/sample.service.ts
  WARNING Service is generated but not provided, it must be provided to be used

上記で作成した src/app/sample/sample.service.ts をDIするには、使用するモジュール・コンポーネントにimport、メタデータ providers およびコンストラクタへの指定を行う。
以下コンポーネントへの導入例。

...
import { SampleService } from './sample/sample.service' // インポート

@Component({
  ...
  providers: [SampleService], // providers に追加
  ...
})
export class AppComponent {
  title: string;

  constructor(
    private sampleService: SampleService // コンストラクタに設定
  ){
    this.title = sampleService.getTitle();
  }
}

DI・インジェクタ

サービスをモジュールやコンポーネントで使用できるようにインジェクトする機能。
前述の通り、モジュール・コンポーネントにimport、メタデータ providers およびコンストラクタへの指定を行う。

@Injectable デコレータがついたサービスクラスは多段Inject(DIのDI)することが可能になる。
一段Injectはデコレータが無しでもOKだがつけておいても問題ない。

フォームとバリデータ

http://codezine.jp/article/detail/9596?p=4

Angularライブラリ

Angularのライブラリには様々用意されており、APIリファレンスはここ
リファレンスではクラスの前に記号がついており、それぞれ以下の意味。

記号 意味
D Directive。ディレクティブ。
P Pipe。パイプ。
@ Decorator。デコレータ。Javaでいう所謂アノテーション。
C Class。普通のTypeScriptのクラス。
I Interface。普通のTypeScriptのインターフェース。
F Function。関数。
E Enum。普通の列挙型。
T Type Alias。なんかわからん。
K Const。定数クラス。

その他話題、メモ

TypeScriptの型解決について

解決方法には以下がある。

  • tsd
  • typings
  • @types

Angularは型定義が含まれているため、上記3つは不要。ただし、他のライブラリを用いる場合 @types を使用する。

Spring Security / Spring Session入門

Spring Securityは認証・認可の機能を持つSpringのライブラリ。
Spring Sessionを用いて Redis にセッションを格納する設定も試してみる。

ここでは、簡単なログイン画面でログインする機能を作成する。
以下の構成で記載する。

  • Java Configで設定する方法
    • Springの設定をJavaベースで設定する方法
  • XML Configで設定する方法
    • Springの設定をXMLベースで設定する方法
  • 共通作成物
    • Java Config、XML Configによらない共通の作成物

また、ここで紹介の作成物については以下参照のアプリケーションに対して、Thymeleaf画面・Spring Securityによる認証・Spring SessionによるRedisセッション管理機能を追加する。

blog.pepese.com

blog.pepese.com

Java Configで設定する方法

pom.xml

  • org.springframework.boot:spring-boot-starter-security を使用しているのがポイント

com.pepese.sample.config.WebSecurityConfig

  • org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter を継承し、 @EnableWebSecurity アノテーションを付与する
  • configure メソッドの引数により設定対象を変更できる
    • WebSecurity web : Spring Security Filter Chain (springSecurityFilterChain) に関する設定が可能
      • web.Xxx() で様々設定できる。詳細はここ
    • HttpSecurity http : XML Configの http タグと同じ設定が可能
      • http.Xxx() で様々設定できる。詳細はここ
  • configureGlobal メソッドで認証処理ロジックのサービスクラスをSpring Securityの認証マネージャに設定している

com.pepese.sample.config.SessionConfig

  • クラスに @EnableRedisHttpSession アノテーションを付与するとSpring Sessionの設定はほぼ完了
  • connectionFactory メソッドで Redis へセッションを格納する設定
  • ホスト名やポートは application.properties に設定する

XML Configで設定する方法

pom.xml

spring-mvc.xml

ThymeleafのView Resolverを設定。

spring-security.xml

  • <sec:http pattern="/resources/**" security="none" />
    • Spring Session処理対象外の設定
  • <sec:http>
    • 認証周りの設定
  • ユーザ認証処理ロジックのサービスクラス( UserDetailsServiceImpl )のBean定義
  • <sec:authentication-manager>
    • UserDetailsServiceImpl を認証マネージャに登録

spring-session.xml

  • RedisHttpSessionConfiguration のBean定義でSpring Sessionの設定
  • LettuceConnectionFactory のBean定義でRedis接続の設定

web.xml

  • contextConfigLocationspring-security.xmlspring-session.xml を追加。
  • Spring Securityの設定として springSecurityFilterChain という名前で org.springframework.web.filter.DelegatingFilterProxy をFilter定義しているのがポイント。
  • Spring Sessionの設定として springSessionRepositoryFilter という名前で org.springframework.web.filter.DelegatingFilterProxy をFilter定義しているのがポイント。
    • さらに Spring SecurityのFilter定義より上 に設定する必要がある

共通作成物

com.pepese.sample.model.User

  • 認証するユーザクラス
    • パスワードを固定にしているのはサンプルなので

com.pepese.sample.service.security.UserDetails

  • Spring Securityが提供するユーザ認証用のクラス( org.springframework.security.core.userdetails.User )を継承して作成
  • 自作のUserクラスを認証対象のクラスとして設定する

com.pepese.sample.service.security.UserDetailsServiceImpl

  • Spring Securityが提供するユーザ認証用サービスクラス( org.springframework.security.core.userdetails.UserDetailsService )を継承して作成
  • 本来なら認証対象のユーザデータをDBなどから取得して突合するが、サンプルということで new してる

application.properties

com.pepese.sample.controller.HelloController

  • @AuthenticationPrincipal でログイン済みのユーザ情報を取得できる
  • なお、 /logout のパス・ロジックは Spring Security にてもつ

index.html

Viewは、Java Configの場合は resources/templates 配下、XML Configの場合は WEB-INF/templates 配下に配置する。

login.html

error.html

Git入門

本記事は移転しました。

約3秒後にリダイレクトします。

リダイレクトしない場合はここをクリックしてください。

JenkinsでMavenレポートを出力する

JenkinsでMavenレポートを出力する。

MavenとJenkinsを使ってJavaのレポート出力をやってみた。
下記を読んだテイで書く。

blog.pepese.com

Maven

mvn clean test site をたたいてHTML形式のレポートを出力する親POMを下記に作った。

親POM

※2017年2月17日時点での最新バージョンにしている。
javancss-maven-plugin はJava8未対応だったためコメントアウト。

プラグインの概要

reporting タグの部分だけ記載する。

  • org.apache.maven.plugins:maven-site-plugin
    • mvn site コマンドの実装
    • 以下の「Project Information」を出力する
      • Dependencies :依存ライブラリの一覧
      • Dependency Convergence :同一依存ライブラリの全てのバージョンを集約して表示
      • Dependency Information :このプロジェクトの依存の書き方
      • Dependency Management :最終的に適用される依存ライブラリの一覧
      • Distribution Management :配布するリポジトリの情報
      • About :POMのdescriptionの記載
      • Plugin Management :最終的に適用されるプラグインの一覧
      • Project Plugins :buildおよびreportingに使用されるプラグインの一覧
      • Project Summary :プロジェクト情報のサマリ
  • org.apache.maven.plugins:maven-javadoc-plugin
    • Project Reports の「JavaDocs」および「Test JavaDocs」を出力する
  • org.apache.maven.plugins:maven-surefire-report-plugin
    • Project Reports の「Surefire Report」を出力する
      • テストの件数、エラー数、失敗数等を出力する
  • org.apache.maven.plugins:maven-jxr-plugin
    • Project Reports の「Source Xref」および「Test Source Xref」を出力する
      • JavaソースコードをHTML形式で出力する
  • org.apache.maven.plugins:maven-pmd-plugin
    • Project Reports の「PMD」を出力する
      • PMDはJavaコードを分析して潜在的なバグを探すツール
  • org.apache.maven.plugins:javancss-maven-plugin
    • Project Reports の「JavaNCSS」を出力する
      • JavaNCSSはJavaコードの品質や複雑度に関するメトリクスを出力する
  • org.jacoco:jacoco-maven-plugin
    • Project Reports の「JaCoCo」を出力する
      • JaCoCo による単体試験のカバレッジ測定結果
  • org.apache.maven.plugins:maven-checkstyle-plugin
    • Project Reports の「Checkstyle」を出力する
  • org.codehaus.mojo:findbugs-maven-plugin
    • Project Reports の「FindBugs」を出力する
  • org.apache.maven.plugins:maven-project-info-reports-plugin
    • プロジェクトの情報を出力する
      • cim、help、issue-tracking、license等

以上の情報がHTML形式で target/site 配下へ出力される。

testFailureIgnore について

親POMのbuild⇒plugins⇒pluginのmaven-surefire-pluginの設定で testFailureIgnoretrue にするとテストでコケてもレポート出力するようになる。
と、同時に mvn test でコケてもSUCCESS となってしまう。
こうなってくると、Jenkinsで DailyTestで失敗しても気づかない ことになってしまう。
なので、 testFailureIgnore はPOMで設定せず、以下のようにするといい。

  • JenkinsによるDailyTest
    • mvn test -Dmaven.test.failure.ignore=false
      • これでテストでコケたら FAILURE になる(ちなみにデフォルトfalse)
  • Jenkinsによるレポート出力
    • mvn clean test site -Dmaven.test.failure.ignore=true
      • これでテストでコケても SUCCESS になり、レポートが出力される

Jenkins

Jenkinsのジョブで mvn clean test site をした後、Jenkinsの HTML Publisher Plugin で下記のように設定すればJenkinsのジョブのページにSiteで出力したレポートへのリンクが作成される。

  • Publish HTML reports :チェックボックスをオン
    • HTML directory to archive
      • 例) target/site/
    • Index page[s]
      • 例) index.html
    • Report title
      • 例) HTML Report

レポートの表示が崩れている(CSS/JSが適用されていない様子)場合は下記を参照。

blog.pepese.com

また、今回Mavenで出力しなかったJavaコード解析情報について、下記のJenkinsプラグインを使用するとカバーできる。

  • DRY Plugin
    • コピペコードのような重複したコードをチェックしてくれる
  • Task Scanner Plugin
    • ソースコードを任意の文字列で検索するチェックをかけることができる
      • FIXME、TODO、XXX、deprecated、System.out.printlnとかスキャンしてくれる
  • Warnings Plugin
    • コンパイラの警告をチェックしてくれる
  • Step Counter Plugin
    • ファイル行数をカウントしてくれる