AngularJS」タグアーカイブ

MEANでデグー飼育アプリを作ってみた

概要

  • 前々からMEANスタックに興味があった
  • 2017/01/22ぐらいからチマチマMEANアプリ作り始めた
  • とりあえず自分にとって実用的であるのが望ましいので、デグー飼育支援アプリを作った
  • MEANだとこんなことができるよの紹介を含め、動作画面を掲載する
  • 完全に個人用なのでサービスの公開はしないが、ソースコードはGithubで公開している

システム構成

OS + MEAN構成は以下の通り

構成要素 バージョン
Debian 8.7
Mongodb 3.2.9
Express 4.14.0
AngularJS 1.4.1
NodeJS 7.4.0

その他、主に開発に用いたツールなどは以下の通り

構成要素 バージョン
npm 4.1.2
babel 6.22.2
eslint 3.0.1
less 3.3.0
gulp 3.9.1
jQuery 3.1.1
jQUeryUI 1.8.19
bootstrap 3.3.7
d3.js 3.5.16
c3.js 0.4.1

ソースコード

https://github.com/Sa2Knight/degu-log よりクローン可能。

環境構築手順も一応READMEにあるので、頑張ればローカルでも動作させられる(多分)
Zaimアカウント及びZaimAPIを利用するためのトークンが必要だったり敷居は高い。

機能一覧

高速な画面遷移

初っ端から機能というより特徴の話だが、本アプリはSPA(っぽい何か)でありながら、URLを変更しながら画面遷移を行える。
そのため、ブラウザの「戻る」「進む」も利用可能であり、URLを直接指定して特定のリソースにアクセスすることもできる。

AngularJSのオプションモジュールである、ng-routeを用いることで、それを簡単に実装できた。
しかも画面遷移が非常に早い。これだけでMEANで開発してよかったと思えるレベル。

簡易ブログ機能

飼育に関する簡易的なブログっぽいものを投稿することができる。
ブログは通常のCRUDの他、カレンダーを表示して記事を一覧することができる。

体重管理機能

デグーの体重を時系列で管理することができる。便宜上、我が家のデグー2匹(パズー、メイ)に特化しており、それぞれ日付に対する体重を記録することができる。
また、時系列に記録した体重を元に、折れ線グラフをリアルタイムで描画することができる。

ペット関連支出管理機能

本機能は、クラウド家計簿サービスであるZaim及び入力した家計簿記録を取得するZaimAPIを用いて実装している。
日頃から入力している家計簿データを元に、月ごとの支出額の一覧及び特定月の支払内容を閲覧できる。

外部APIの利用もやはりSPAとの相性が抜群だ

写真管理機能

写真をアップロードして管理することができる。写真には、タイトル及び1つ以上のタグを付与することができ、それらで検索することができる。
また、写真アップロード時にオリジナルと別にサムネイルを生成し、写真一覧画面にはサムネイルが表示され、クリックすることでオリジナルサイズを確認できるようにアンっている。

所感

MEANという、MもEもAもNもほぼ使ったこと無い状態から、1つずつ勉強しながらなんとか実用性も多少あるWebアプリを作ることができた。特にSPAを1から作るのは初めての経験だったので、従来型のWebアプリとの違いを感じながら楽しく開発することができた。以下、主な所感を列挙する

  • MongoDBはJavaScriptライクに読み書きを行え、スキーマに依存せずにデータを管理できるので手軽で楽しい
  • AngularJSは双方向データバインディングが一番強いと思ったが、フルスタックのMVCであればAngularJSでなくても良いかなとは思う。
  • Nodeを使えばだいたいのことは対応するNodeモジュールがあって、サーバサイドを一通りJavaScriptで書ける。強い。
  • Expressについては、今回はRestfulなAPIサーバとしてしか使わなかったので触り程度しかわからなかった
  • SPAはクライアントの待機時間が極端に短くなるのでストレス無く利用できることを強く実感
  • クライアントサイドの負担を増やし、サーバサイドの負担を減らす新しい分散システムだと感じた
  • SPAでも画面遷移ごとにURLを変化させたほうが絶対便利だと思った
  • サーバサイドはRestfulなAPIに特化させるように実装したほうが分担が明確で良い
  • JavaScriptはBabelを導入して、全てES6で記述した。やはり最新仕様のJavaScriptは書きやすい。もう戻りたくない。
  • ESLintを導入してコーディング規約を強制した。おかげで比較的高品質のコードを終始簡単に弄るすことができた
  • Bootstrapに頼りきりだった割にUIが雑で残念
  • 一応LESSも導入したが特に使いこなせてない
  • テスト(ユニット,E2E)を書きたかったが、その余裕がなかった。そのうち書きたい
  • とにかく新しい技術/ツールで溢れた開発で楽しい1ヶ月半だった

次はTypeScript及びAngular2に手を付けてみたい。Angular2のほうが良さげであれば、本アプリを移植することも検討する。

AngularJSでjQuery datetimepickerを使うためのディレクティブの作成

前提

要素 バージョン
AngularJS 1.4.1
jQuery 3.1.1
jQuery datetimepicker 2.5.4

概要

初めてAngularJSのディレクティブを自作したので舞い上がって投稿する。

本記事では。属性ディレクティブ”dateTimePicker”を定義し、以下のように利用できるようにする

呼び出し

<date-time-picker ng-model="datetime" format="Y/m/d" time=false />

パラメータ

パラメータ名 入力例 説明
ng-model datetime バインディングするAngularJSのデータモデル
format Y/m/d 入力される日時文字列のフォーマット
time true 時刻の入力を行うか

実行例

ディレクティブの作成

パラメータで指定しない部分については、以下の固定値を与えている
– bootstrap用のclassを設定する(form-control)
– AngularJS用のバリデーションを設定(required)
– 各種スクロールをオフにする

angular.module('app').directive('dateTimePicker', function() {
  return {
    restrict: 'E',
    require: 'ngModel',
    scope: {
      format: "@",
      time: "@",
    },
    replace: true,
    template: '<input type="text" class="form-control date-time-picker" required>',
    link(scope, element, attrs, controller) {
      if (scope.format === undefined) {
        scope.format = 'Y/m/d H:i';
      }
      scope.time = scope.time === 'true' ? true : false;
      $(element).datetimepicker({
        format: scope.format,
        timepickerScrollbar: false,
        timepicker: scope.time,
        scrollMonth: false,
        scrollTime: false,
        scrollInput: false,
        onChangeDateTime(dp, $input) {
          controller.$setViewValue($input.val());
        },
      });
    },
  };
});

ソースコード解説

行番号 抜粋 説明
01 directive(‘dateTimePicker’… ディレクティブ dateTimePickerを作成する
03 restrict: ‘E’ <dateTimePicker>の形式で利用できるようにする
04 require: ‘ngModel’ ng-model属性の指定を必須にする
05-08 scope: { 属性(format/time)を文字列で受け取る
09 replace: true, タグをtemplateで置き換える
10 template: ‘<input type=”text”… タグを置き換えるテンプレート
11-27 link(scope, element, attrs, controller) { ディレクティブの挙動を定義
16-26 $(element).datetimepicker({ datetimepickerの呼び出し
24 controller.$setViewValue($input.val()); datetimepickerによって値が書き換わった時に手動でデータバインディングする

実行結果

実行前

<date-time-picker format="Y/m/d H:i" time=true ng-model="blog.post.datetime"/>

実行後

<input type="text" class="form-control date-time-picker ng-isolate-scope ng-valid ng-valid-required ng-touched" required="" format="Y/m/d" time="false" ng-model="weight.post.date">

「AngularJS アプリケーションプログラミング」レビュー

書籍について

タイトル AngularJS アプリケーションプログラミング
著者 著: 山田 祥寛
発行日 2015/09/20
ページ数 497ページ
価格 3700円
Amazon https://www.amazon.co.jp/dp/4774175684
読了日 2017/03/08

メモなど

  • 前提として、AngularJS(1.4.1)で小さなアプリを開発しつつ、並行して読み進めたため0からのスタートではない
  • AngularJSの全体像を体系的に整理した本
  • 約500ページのボリュームで、AngularJSでできることを一通り解説している
  • この分厚い本を読まずともAngularJSアプリは開発できるが、その原理やより便利な使い方を知るためには必須
  • AngularJSそのものだけでなく、テストフレームワークやビルドツール、Chromeのデベロッパツールなど、AngularJSアプリ開発の周辺技術についても取り上げている
  • いわゆるハンズオンな内容でもなく、電車内で読み進めるだけで理解を深められた
  • AngularJSに興味がなくとも、JavaScriptMVCについて述べられた第1章だけでも読む価値あり

構成と感想

※ 実際の目次から多少弄ってます

1.JavaScriptの歴史

JavaScript誕生の経緯から始まり、不遇の時代を乗り越えてAjaxの登場によって再ブームを起こし、MVCフレームワークが誕生するまでが述べられている。

AngularJSに関心がなくとも、この章だけでも読み物として面白い。何故MVCフレームワークが必要になったのか?そもそもライブラリとフレームワークの違いは何か?フロントエンジニアなら抑えておいて当たり前の内容を丁寧に説明してる。

2.AngularJSの基本的な使い方とそのしくみ

  • モジュール
  • コントローラ
  • サービス
  • ディレクティブ
  • 双方向データバインディング

といった、AngularJSの構成要素である専門用語について、簡単なサンプルコードを通じて解説している。この章を読むだけで、AngularJSがどんなフレームワークなのかが見えてくる。

ここまで読んで「面白そう」と思ったら続きを読めばいいし、興味が沸かなくてもここまで読めばAngularJSとは何なのかの説明ができる。

3.標準ディレクティブ

ディレクティブとは、

<input type="text" ng-model="name">

のように、HTMLを拡張するためのタグや属性のことである。
ディレクティブは自作することもできるが、AngularJSでは多くの便利なディレクティブが提供されている。それらについて、いくつかピックアップして紹介してる。

4.標準フィルター

ビュー側でデータを出力する際に特定の加工を行ってから出力する「ビュー」について。例えば、以下の場合はobjectをJSON化して出力する。

{{ object | json }}

JSONの他にも、数値を丸めたり、大文字小文字を切り替えたりと言った定番の加工を行うフィルタが標準で数多く存在する。それらについて、いくつかピックアップして紹介してる

5.標準サービス

AngularJSの中でも特にコアな部分であるサービス。サービスとはAngularJSにおける「ロジック」を提供する。
MVCで言うならばModelに該当する。(AngularJSはMVWを主張しているが)

6.スコープオブジェクト

スコープはビューとコントローラを結びつけるオブジェクト。AngularJSにおいて非常に重要な役割を持つスコープだが、その使い方と原理を詳細に解説している。

Angular2ではスコープもコントローラも廃止されているらしいのでちょっと残念。

7.自作フィルタ

フィルタの自作について。といっても、フィルタは受け取った値を何らかの加工をして戻すだけなので非常に簡単。

フィルタは高頻度で呼び出されるため、あまり複雑な加工を行うわけにもいかないし、標準フィルタに強力なモノが多いのであまり自作する機会は無いかなと思った。

8.自作サービス

AngularJSアプリの開発=サービスをどんどん自作していくことかなと思う。特にMVCを正確に分離するのであれば、モデルは全てサービスを自前で実装していく必要がある。

サービスには

  • value
  • constant
  • factory
  • service
  • provider

の5種類があり、どれも微妙に特徴が異なる。それぞれについて説明がされているが、まぁfactoryかserviceを使えば良いかなといった感じ。

9.自作ディレクティブ

小規模なアプリ開発であれば縁が無さそうなディレクティブの自作。これを使いこなせればAngularJSを使いこなせてると言っても過言じゃ無さそう。

ディレクティブは出来ることは非常に広く、その分自作に必要なノウハウも広く深い。その分ページ数もかなり割いているが、やはり「ここまでやる必要あるか」と思ってしまう。

よりAngularJSを使い倒したいと思ったらもう一度読んで見ると良さそう。(おそらくその前にAngular2にうつるが)

10. ユニットテスト

AngularJSアプリのユニットテストの行い方について。もちろんテストの手段は様々あるが、本書ではAngularJSでは定番となっている、Karma + jasmineを採用している。

通常のWebアプリケーションのユニットテストとは異なり、依存モジュールを注入したりするなど、AngularJSらしいテスト手法が書かれている。

11. モック

この場合の「モック」とは、ユニットテストを円滑に行うためのダミーオブジェクトである。例えばテスト対象が何らかの外部サービスに依存する時、テスト用の特定の機能のみを持ったダミーオブジェクトを置き換えることで、外部サービスが稼働していなかったり、未実装だった場合でもユニットテストを行うことができる。

AngularJSでは、この「モック」が数多く標準で提供されている。例えば、HTTPリクエストに対するレスポンスを返却するモックでは、テスト対象のコードを弄らずとも、ダミーのHTTPレスポンスを返却するモックをテストコード中で呼び出すことで、レスポンスが返却されたように振る舞うことができる。

12. E2Eテスト

E2Eテストは、実際のユーザがブラウザを操作したときにアプリケーションが正しい振る舞いを行うことを検査する総合的なテストである。

本書では、Protectorというテストランナーを採用し、AngularJSにおけるE2Eテストについて説明している。個人的にはVisualStudioが必要とか書かれているのを見て意欲を失ってしまった。

AngularJSアプリは、原則としてjQueryによるDOMツリーの操作が行われないので、E2Eテストは書きやすいのかなと思った。

13. 関連ライブラリ

AngularJSはそれだけで非常に強力なフルスタックフレームワークだが、局所的な実装を全てサポートしているわけではない。そこで、実装内容に応じて、各種AngularJSライブラリを利用することで、さらにAngularJSアプリの実装の幅を広げることができる。(jQueryにおけるjQueryライブラリのようなもの)

AngularJSでは、フィルタ/ディレクティブ/サービスを自作することができるので、それらを含んだライブラリが(公式/非公式問わず)数多く存在する。本書では以下の一部のみを紹介しているが、他の膨大なライブラリの調べ方なども書いてある。

  • UI Bootstrap
    — Bootstrapをディレクティブで利用できるようにする
  • UI Event
    — 標準以外のイベント処理をできるようにする
  • UI Validate
    — 標準以外の、自作の検証機能を実装できる
  • UI Router
    — 高度なルーティング処理を実装できる。SPAには必須
  • UI Grid
    — グリッド表を生成する
  • angular-translate
    — 国際化対応ページを実装する
  • Angular Google Maps
    — GoogleMapをAngularJSで利用する
  • angular google chart
    — GoogleChartをAngularJSで利用する

14. 関連ツール

AngularJSとは直接関係ないが、AngularJSアプリを開発する上で役立つ以下の関連ツールについて紹介している。もちろんツールの紹介だけでなく、AngularJSで活用するための利用方法についても説明している。

  • Yeoman
    — 様々なフレームワークに対応したアプリの雛形を生成するアプリ。つまりAngularJSアプリの雛形を生成してくれる
  • Grunt
    — タスクランナーツール。TypeScriptのコンパイル、テストの実行、ソースコードのミニファイなどを逐次自動で行ってくれる
  • Bower
    — クライアントサイドのJavaScriptパッケージ管理ツール。ちなみにサーバサイドはnpmが主流

なお、本書ではタスクランナーツールとしてGruntを採用していたが、私はGurpを採用しているのでほぼ飛ばした。

所感

AngularJSは本当に面白い。あと3年ぐらい早く出会っていればこれまでフロントエンドの実装にあんなに苦しまなかった気がする。

しかし、互換性の一切ない、というかアーキテクチャが一新されたAngular2の登場によって少しずつAngularJSが不要になっていくのがとても悲しい。

AngularJSのノウハウがそのままAngular2で生きてくれれば嬉しいが、それはAngular2をやってみないとわからない。

AngularJSからexpressに画像をアップロードする

前提

要素 バージョン
node 7.4.0
npm 4.1.2
express 4.14.0
AngularJS 1.4.1

概要

  • AngularJSのページから画像ファイルをアップロードし、express側で保存させる
  • AngularJSには(少なくとも標準モジュールには)ファイルアップロードの仕組みが存在しないので、ディレクティブを自作して対応する

1.クライアントサイド

添付ファイルをモデルで扱うためのディレクティブを定義

app.directive('fileModel', ['$parse', function($parse) {
  return function(scope, element, attrs) {
    const model = $parse(attrs.fileModel);
    element.bind('change', function() {
      scope.$apply(() => {
        model.assign(scope, element[0].files[0]);
      });
    });
  };
}]);

上記ディレクティブは、以下のように利用する

<input type="file" name="myfile" file-model="my-file">

上記ディレクティブをinput[type=file]にバインドすることで、ファイル選択時にファイルの内容をAngularモデルにバインディングする。
これによって、添付されたファイルをAngularJSのモデルとして操作できるようになる。

ファイルアップロード用のサービスを定義

app.factory('fileUpload' , ['$http' , function($http) {
  return {
    upload(photo) {
      let formData = new FormData();
      formData.append('file' , photo);
      $http.post('/rest/photo/put', formData, {
        headers: {'Content-Type': undefined} ,
        transformRequest: null
      });
    },
  };
}]);

APIがあるであろう、’/rest/photo/put’に対して添付ファイルをPOSTする。
Content-Typeをundefinedに明示的に設定しておくと、AngularJS側で良い感じに設定してくれるので今回はおまかせする。
transformRequestをnullにしておかないと、POSTデータがJSONに変換される可能性があるので、一応明示的にNULLにしておく。

上記サービスを、添付ファイルを指定して実行することで、APIサーバに添付ファイルがPOSTされる

2.サーバサイド

Multerモジュールをインストール

Multerモジュールは、multipart/form-data形式のリクエストを扱うためのNodeJSのミドルウェア。これを用いることでexpress側で添付ファイルを処理することができる。

$ npm install --save multer

POSTされたファイルを保存する

Multerモジュールがインストールできたら、以下のコードをexpress側に追加する(※appはexpressオブジェクト)

var multer  = require('multer');
app.use(multer({ dest: './uploads/'}).any());

非常にシンプルだが、これだけで、アップロードファイルがPOSTされた際に、そのファイルを./uploadsに保存する処理が自動で行われる。

アップロードファイルの検証

アップロードされたファイルは、requestオブジェクトのfiles属性に含まれている。filesは配列であることから、複数ファイルの同時アップロードにも対応できる。

以下のコードでは、アップロードされたファイル(1件)の内容を出力し、無条件でsuccessを戻している。

app.post('/rest/photo/put' , function(req, res) {
  console.log(req.files[0]);
  res.send('success');
});

‘/rest/photo/put’に対して、画像ファイルを1件アップロードすると、以下のログが出力される

{ fieldname: 'file',
  originalname: '10.jpeg',
  encoding: '7bit',
  mimetype: 'image/jpeg',
  destination: './uploads/',
  filename: '652aa507d6e70b89e064bbddd3b33224',
  path: 'uploads/652aa507d6e70b89e064bbddd3b33224',
  size: 5297 }

ユニークなランダム文字列であるfilenameを含んだpathが既に出力に含まれているように、この時点で既に画像ファイルは保存されている。

もちろんファイル名を変更したり、保存する条件を指定したりもできるが、ここでは割愛する。

参考

AngularJSの専門用語整理その2

まさかの続き(前回)

digestサイクル

  • AngularJSがデータ双方向バインディングを実現するために、UIとモデルの差異を検証するサイクル

式(Expressions)

  • Angular式とか呼ばれてる
  • {{ 式 }} の形式でビューで展開できるJavaScriptの式っぽいもの
  • JavaScriptっぽいだけで別物
  • 全ての評価はデフォルトでスコープのプロパティに対して行われるので
  • undefinedをオブジェクトとして参照できる(無論結果は全てundefined)
  • フィルタが使える

$parse

  • Angular式を関数に変換するサービス
  • 対象のスコープを指定して関数を実行できる
    var getter = $parse('user.name');
    var context = {user:{name:'sasaki'}};
    console.log(getter(context)); // sasaki
    

$watch

  • 特定のオブジェクトやプロパティの変更を監視し、変更後に任意の処理を行わせるサービス
    $scope.hoge = 'foo';
    $scope.$watch('hoge' , function(oldValue, newValue) {
      console.log('hogeの内容が' + oldValue + 'から' + newValue + 'に変わりました');
    });
    
  • 関数を指定して、その戻り値の変化も検知できる
    $scope.$watch(function() {
      return $location.path();
    }, function() {
      console.log('パスが変わりました');
    });
    
  • watchはdigestサイクルの中で定期的に行われる

$apply

  • UIとモデルの値を同期させるサービス
  • digestサイクルの中で呼び出されるが、手動で呼び出すことも可能
  • 特にjQueryなどによってUIの値を直接書き換えた場合など、digestサイクルで検知できない場合に手動で同期させる必要がある

$inject

  • AngularJSにおける依存性注入のためのサービス
    コンストラクタ’My’に対して、依存するオブジェクト$scopeを注入してコントローラを作成する

    var My = function(s) {
      s.msg = 'Hello, AngularJS!';
    };
    My.$inject = ['$scope'];
    angular.module('myApp').controller('myController', My);
    

AngularJSとNode+express間でJSONデータのやり取りを行う

前提

要素 バージョン
node 7.4.0
express 4.14.0
AngularJS 1.4.1
Chrome 55.0x

概要

AngularJSアプリで、特定のデータをNode+expressで稼働しているAPIサーバにJSON形式でアップロードする。
一見単純そうだったが、ググった単一の情報では上手く動かなく、色々調べてやっと動いたので備忘録として残す。

AngularJSによるJSON文字列のPOST

AngularJSの$httpサービスを用いて、非同期でAPIサーバにPOSTする。その際、POSTデータのContent-Typeをapplication/jsonにする必要がある。

転送するデータは、便宜上定数で用意したuserとする。$httpサービスの機能で、JavaScriptオブジェクトは暗黙的にJSONに変換されPOSTできる。

正常にレスポンスが返却されると、successメソッドで指定したコールバック関数が呼び出されるので、そこでレスポンスボディを標準出力している。

app.factory('my_controller' , ['$http' , function($http) {
  let user = {name: 'sasaki' , age: 24};
  return {
    upload() {
      $http(
        url: '/rest/user/post',
        method: 'POST',
        data: user,
        headers: {'Content-Type': 'application/json; charset=utf-8'}
      ).success(function(data, status, headers, config) {
        console.log(data);
      });
    }
  };
}]);

Node+expressによるPOSTデータの受信

body-parserのインストール

Node側で、application/jsonのPOSTを裁くには、別途Nodeモジュールのbody-parserが必要になるため、以下のコマンドでインストールする。

npm install -D body-parser

POSTデータの受信

前項でインストールしたbody-parserを用いて、JSONを捌けるようにする。

var bodyParser = require('body-parser');
app.use(bodyParser.urlencoded({extended: true}));
app.use(bodyParser.json());

expressのルーティング機能を用いて、JSONを受け取る

reqオブジェクトのbodyに、JSONが文字列形式で含まれているのでそれを標準出力。
レスポンスは適当にsuccessと文字列を返しているだけ。

app.post('/rest/blog/post' , function(req , res) {
  console.log(req.body);
  res.send('success');
});

参考

AngularJSの専門用語整理

※AngularJSはとても奥が深く、まだ調べ始めたばかりなので誤りがあるかもしれません。
※AngularJSの専門用語と称していますが、基本的な概念はAngular2でも通用すると思います。
続きました

双方向データバインディング


AngularJS最大の機能の一つ。UIが表示しているデータと、JavaScriptが内部で保持しているデータを自動で同期させる機能。

ただし、同期が行われるのはUIに対するイベントによって値が変更された場合のみなので、jQueryなどで直接値を書き換えた場合には手動で同期する必要がある。

そもそもAngularを用いてフロントエンドの開発をする場合、闇雲にjQueryを使うべきではないと思われる。

モジュール


AngularJSアプリの最大単位。小規模であれば1アプリ1モジュールでも良い。規模が大きいなら、機能ごと、コンポーネントごとにモジュールを作るなどができる。

モジュール内で他モジュールを依存先として指定することができ、これはサードパーティー製のモジュールの機能を引き継ぐ時に使える。

例えば以下の場合は、ngRouteモジュール、ngAnimateモジュールに依存するmyappモジュールを作成できる。

myapp = angular.module('degulog', ['ngRoute' , 'ngAnimate']);

生成したモジュールに対して、各種コントローラ、サービス(後述)、ディレクティブ(後述)などを定義する。

サービス(service)


特定の目的を持った関数(オブジェクト)

AngularJSにおけるサービスは、シングルトンオブジェクトで、状態や振る舞いを保持することができる。そのため、一般的なMVCにおけるモデル層をサービスとして定義する。

サービスは使い方に応じて、Sercice,factory,provider,valueなどに分類されるが、モデルを記述する場合は基本的にfactoryを用いる。

以下はカウントアップ機能を備えたサービスの定義である。

myModule = angular.module(‘mymodule’ , []);
myModule.factory(‘countup’  , function() {
  let count = 0;
  return {
    add: () => ++count,
    get: () => count,
  };
});

上記のように、クロージャになるように記述することで、状態の保持ができるようになるのでモデルとして利用することができる。

ディレクティブ


HTML上で利用できる拡張タグ、クラス、属性のこと。<ng-***>はAngularJSに標準で搭載されたディレクティブ。

js

var app = angular.module("myApp", []);
app.directive("test", function () {
    return {
        restrict: "E",
        template: "<div>テスト</div>"
    }
})

html

<test></test>

これをブラウザで開くと、以下のように変換される

<test><div>テスト</div></test>

例はシンプルすぎるもので、ただの置換にも見えるが、ディレクティブでは要素に属性を付与したり、オブジェクトを操作したりと様々なことができる。

<

h2>

スコープ($scope)


スコープはServiceの1種で、ビューとコントローラの懸け橋となるオブジェクト。

コントローラでscope内に定義したデータ(メソッド)は、対応するビューから参照(実行)することができる。

逆に、ビュー側で名前付けしたDOMを、コントローラでscopeを通して読み書きしたり、イベントを監視することができる。

AngularJSではDOMを直接操作することは推奨されていないので、原則scopeを通してビューを操作する。

フィルター


データを出力する際に特定の加工を施す機能。例えば、テンプレートにて以下のような記述をすると、objectをJSONに変換して出力する

{{obj | JSON}}

フィルターはモジュールで自作することができ、入力に対する出力を返す関数であれば好きなように作成できる。

Debian8.7にMEAN開発環境を構築する

概要


MEANは近代的でフルスタックな開発フレームワーク。以下の構成要素の頭文字をつなげたもの

M MongoDB NoSQLなデータベース
E Express Node上で動くWebアプリケーションフレームワーク
A AnglarJS フロントエンドのMVCフレームワーク
N NodeJS サーバサイドのJavaSciprt実行環境(Webサーバの役割)

今回は、vagrantを用いて以下のように仮想環境を立ち上げて、そこにMEAN環境を構築した

$ vagrant box add debian https://github.com/holms/vagrant-jessie-box/releases/download/Jessie-v0.1/Debian-jessie-amd64-netboot.box

導入したOSは以下のとおり、Debian 8.7である

$ cat /etc/debian_version
8.7
$ uname -a
Linux Debian-jessie-amd64-netboot 3.16.0-4-amd64 #1 SMP Debian 3.16.39-1 (2016-12-30) x86_64 GNU/Linux

MongoDB


MongoDBはNoSQL(表形式じゃないDBMS)のデファクトスタンダード。

MongoDBのインストール

$ sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 7F0CEB10
$ echo "deb http://repo.mongodb.org/apt/ubuntu "$(lsb_release -sc)"/mongodb-org/3.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-3.0.list
$ sudo apt-get update
$ sudo apt-get install mongodb-org

適当にデータを入れておく

ここでは、mydbというデータベースを作成し、さらにUsersというコレクション(テーブルのようなもの)を作成し、そこに適当なオブジェクトを挿入する。

$ mongo
(省略)
> use mydb;
> db.users.insert({name: 'sasaki' , age: 24});
WriteResult({ &quot;nInserted&quot; : 1 })
> db.users.find({});
{ &quot;_id&quot; : ObjectId(&quot;5881d7ed4393e91ab4ebd18d&quot;), &quot;name&quot; : &quot;sasaki&quot;, &quot;age&quot; : 24 }

バイナリをダウンロード

何故か上記のやり方でバイナリが落ちなかったので別途入れる

$ curl -O https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-3.2.9.tgz
$ tar -zxvf mongodb-linux-x86_64-3.2.9.tgz
$ mv mongodb-linux-x86_64-3.2.9/bin/* /usr/local/bin

確認

$ mongo --version
MongoDB shell version: 3.2.9

NodeJS

NodeJSはサーバサイドでのJavaScriptの実行環境。ノンブロッキングIOを用いて大量アクセスを華麗に裁く

nodejsのインストール

aptを用いてインストールできるnodeは非常に古い。
とりあえず古いバージョンをインストールし、バージョンアップする手段を用いる。

$ sudo apt install nodejs
$ nodejs -v
v0.10.25

npmのインストール

npmはnodeで動くパッケージの管理ツール。必須。

$ sudo apt install npm
$ npm -v
4.1.2

nのインストール

nはnode本体のバージョンを管理するツール。npmを用いてインストールする

$ sudo npm -g install n
$ n --version
2.1.3

nodeを最新版に引き上げる

$ sudo n stable
$ node -v
v7.4.0

Express


expressはnode上で動作するWebアプリケーションフレームワーク。これもnpmを用いてインストールする

expressのインストール

$ npm install express -g
$ express --version
4.14.0

AngularJS


AngularJSはフロントエンドのMVCフレームワーク。こちらはCDNを用いるので、別途インストールの作業はない。

Hello,World


必要なnodeモジュールを追加でインストールする

ejsはテンプレートエンジン。mongodbはnodeからmongodbにアクセスするモジュール。

$ npm install ejs
$ npm install mongodb

nodeでmongodbに接続するためのスクリプトを作成

ここでは27017番ポートでmongodbが立ち上がっているものとし、データベース名がmydbであるとする。

var db;
var MongoClient = require('mongodb').MongoClient;
var assert = require('assert');
var url = 'mongodb://0.0.0.0:27017/mydb';

MongoClient.connect(url, function(err, mongodb) {
  assert.equal(null, err);
  console.log("Connected correctly to server");
  db = mongodb;
});

var collection = function(name) {
  return db.collection(name);
}

module.exports = collection;

メインスクリプトを作成

作業ディレクトリにapp.jsを作成する。この例では、8086番ポートでサーバを立ち上げている。

// express準備
var express = require('express');
var ObjectID = require('mongodb').ObjectID;
var collection = require('./mongo');
var COL = 'users';
var app = express();

// テンプレートディレクトリのパスを指定
app.set('views', __dirname + '/views');

// テンプレートエンジンをejsに指定
app.set('view engine', 'ejs');

// ルーティング設定
app.get('/', function (req, res) {
  collection(COL).find({}).toArray(function(err , docs) {
    res.render('index' , {name: docs[0].name});
  });
  //res.render('index');
});

// サーバー起動
var server = app.listen(8086, function () {
  var host = server.address().address;
  var port = server.address().port;
  console.log('Example app listening at http://%s:%s', host, port);
});

トップページを作成

views/index.ejsを作成する。この例では、AngularJS1.6.1をCDNから引っ張ってきている。
ややこしいが、<%= hoge %>が、ejsによるサーバサイドでの値出力。{{hoge}}が、AngularJSによるクライアントサイドでの値出力となる。

<!doctype html>
<html ng-app>
  <head>
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.1/angular.min.js"></script>
  </head>
  <body>
    <div>
      <label>Name:</label>
      <input type="text" ng-model="yourName" placeholder="Enter a name here">
      <hr>
      <h1>Hello {{yourName}}! My name is <%= name %>!</h1>
    </div>
  </body>
</html>

サーバを起動

$ node app.js
Example app listening at http://:::8086

ブラウザから8086番ポートにアクセスし、以下の画面が表示されれば成功

クライアントからの要求は、NodeJSがリクエストを初めに受け取る。NodeJS上で動作するexpressは、URLを元にルーティングし、MongoDBからデータを取得してejsテンプレートに引き渡している。ejsがテンプレートに値を埋め込むと、それをHTMLとしてクライアントに返却。クライアントはHTMLに含まれているAngularJSをダウンロードし、Angularアプリを描画する。そんな流れ。

AngularJS使ってみた

AngularJSとは


  • フロントエンドのMVC/MVVMフレームワーク
  • モデルとビューのデータ双方向バインディングが特徴
  • SPA開発に強い
  • カスタムタグ属性を用いることで、通常のHTMLに直接記述できる

AngularJSとAngular2の違い


Angular2はAngularJSの後継バージョンだが、AngularJSとは互換性を一切持たない。アーキテクチャが異なるため、今後も互換性は導入されないと言われている。

今回は、主に以下の理由からAngular2ではなくAngularJSを用いることにした。

  • Angular2は2016年9月にリリースされたばかりで、情報が比較的少ない
  • Angular2はTypeScriptでの利用を推奨している(JavaScriptでも書けるには書ける)

AngularJSによるサンプルコード


以降のサンプルコードは、コード全体のうち重要な部分のみ抜粋する。そのため、サンプルコードのみでは動作しないのがほとんどなので注意。AngularJSでこんなことができるよ、程度の参考にすること。

<!doctype html>
<html ng-app="myapp">
  <head>
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.1/angular.min.js"></script>
  </head>
  <body>
    <div>
      <input type="text" ng-model="inputText">
      <h1>Hello {{inputText}}!</h1>
    </div>
  </body>
</html>

AngularJSでは、特別なテンプレートファイルなどを用いること無く、通常のHTMLファイルを拡張する形で利用することができる。

HTMLタグに対して、AngularJS特有の情報を持たせるには、”ng-model”など、ng-XXXで表記される拡張タグを用いる。

また、モデルのデータは {{param}} の形式で、HTMLに直接埋め込むことができる。

モデルとビューの双方向データバインディング機能


AngularJSの大きな機能である「双方向データバインディング」とは、名前の通り、モデルが保持しているデータと、それを表示しているビューを全自動で同期してくれる機能だ。

前項のサンプルコードを例にすると、inputタグに、ng-model=”inputText”と指定することで、このテキストボックスの内容は”inputText”というモデルにバインディングさせる。

それによって、テキストボックスを用いて入力内容を変更すると、自動でinputTextという名前のモデルも更新される。そのため、{{inputText}}で出力している内容もリアルタイムに更新される。

従来はjQueryなどを用いて、テキストボックスの変更を検知したらイベントで出力内容を変更すると言った処理を行っていたが、それが不要になり、かつ同期漏れなどのプログラマのミスを無くすことができる。

モデルの変更監視


モデルの変更は当然モデルでも検知することができる。
以下のコードは、taskList.tasks(array)の内容が変更された場合に、内容を保存している

$scope.$watch('taskList.tasks' , function() {
  taskList.saveTasks();
} , true);

ルーティング

ここでのルーティングは、SPAを前提とした、非同期通信をベースとした画面遷移のことを指すが、
もちろんWAF別途導入した非SPAのアプリケーションを開発することも可能。

AngularJSのルーティングは、以下の特徴を持つ

  • URLによるルーティング
  • SPAだが、戻る・進む・更新に対応
  • 自動的に非同期でページを返却する

ルーティングのサンプルコード(の一部)

JS

app = angular.module('myapp', ['ngRoute' , 'ngAnimate']);

app.config(function($routeProvider) {
  $routeProvider
  .when('/' , {
    templateUrl: 'views/top.html'
  })
  .when('/taskList' , {
    templateUrl: 'views/taskList.html',
    controller: 'taskListController as taskList',
  })
  .otherwise({
    redirectTo: '/'
  });

HTML

<nav>
<div><a href="#/">トップ</a></div>
<div><a href="#/taskList">タスク一覧</a></div>
<div><a href="#/bookmark">ブックマーク一覧</a></div>
</nav>

リンククリックから、画面遷移までの流れはおそらく以下の通り。
以下は全てajaxを用いて行われるが、プログラマはそれを意識する必要がない。

  • $routeProviderを参照し、該当のルーティングを取得
  • 該当するコントローラオブジェクトを生成
  • 該当するテンプレートとバインディング
  • バインディングされたコントローラとテンプレートを返却
  • ブラウザが直前のテンプレート・コントローラを破棄し、受け取ったテンプレートを描画

また、コードを見ての通り、AngularJSでは、テンプレートとコントローラをそれぞれ指定することができる。それによって、同じテンプレートに対して異なるコントローラや、異なるテンプレートに同じコントローラを割り当てるなど、柔軟に実装することができる。

AngularJS所感


1日ちょっとだが、AngularJSを使って簡易アプリを作ってみての感想は以下の通り

  • 固定の設計パターンが多く、学習コストが高い
  • Angularの設計思想に基いて正しく扱えれば、jQueryは不要になると想う
  • ビューとモデルの双方向バインディングが強力。同期漏れによるバグが起こらないのは強い
  • しかし、理解して使わないと同期が高頻度で行われすぎてパフォーマンスが低下するかも?
  • バージョンによる記述方法の差異が大きい
  • 非同期通信はjQueryより遥かに直感的
  • AngularJS用の単体テストモジュールがあるらしく、テストが容易

総評すると、学習コストが高いが、チーム全体で設計パターンを理解していれば、非常に保守性の優れたコードが書けると思った。コードのバグも生まれにくくなると思うが、慣れるまではAngularJSの使い方の問題で実装に苦労するかも。

MEANスタック開発(MongoDB,Express,Angular,NodeJS)が流行ってきており、勢いだけならLAMPよりありそうなので、本格的に勉強してみる価値はある。

おまけ


今回のAngularJSお試しで開発した簡易アプリはこちらからダウンロードできる。
Webサーバを使わずにブラウザのみで動作するので、ダウンロードすることでAngularJSを試すことができる。

また、本記事ではかなり駆け足な紹介だったので、ソースコードを参照すればより具体的な使い方がわかるはず。