とある角度から

お腹いっぱいたべられる幸せ

公式サンプルから学ぶAngularJS (2)

ということで、前回の記事の続きで、AngularJS公式サイトのサンプル3つ目を作っていきます。

の前に、今回のサンプルはFirebaseというサービスを利用しているので、事前に登録を済ませておく必要があります。
さくっとできるのでぜひ試してみてください→以前にFirebaseに登録してみた記事


ではさっそく

サンプルその3「Wire up a Backend」

ブックマークの登録・編集・削除ができるサンプルです。
Firebaseと連携し、リアルタイムに他ユーザの変更も反映するようになっています。

イメージが沸きやすい様に、先に動作サンプル

今回のサンプルでやっていることは、大きく次の3つ

  • ngRouteモジュールを使った、URLによるページ内表示内容の切り分け
  • AngularFireを使ったFirebaseサービスとの連携
  • 動的なフォームバリデーション

また、ソースの構成は以下

  • index.html (メインHTML)
  • project.js (メインJS)
  • list.html (テンプレートとして読み込む)
  • detail.html(テンプレートとして読み込む)

ここから、各ソースについて、自分なりの解説をしていきます。
※公式ページのサンプルソースとは、若干相違点もあります。

ソース

index.html
[a] AngularJS本体に加えて、ngRouteモジュールを読み込んでいます。ngRouteモジュールは、ng-view属性を指定した要素([d]の部分)に表示する内容をURL毎に設定することができる機能です。

[b] Firebaseサービスを使うためにAngularJS用Firebaseモジュールを読み込みます。

[c] さらに、見た目を少しよくしたいので、bootstrap.cssを読み込んでいます。(bootstrapはcssフレームワークです。簡単かつ迅速にサイトレイアウトを実現することが可能になります。ここでは詳細は省略します)


project.js
[a] まず、html全体に対するモジュール(project)を定義する際に、依存モジュールとしてngRouteおよびfirebaseを追加しています。これにより、このモジュール内にて、依存モジュールで新たに提供される要素にアクセスすることが可能になります。

[b] 'fbURL'という名前で、FirebaseサービスのベースURLを登録しています。
※value()は定数の定義ではなく、オブジェクトインスタンスの登録なので、文字列だけではなく数値、オブジェクト、関数等も登録できます。

[c] ここでは'Projects'という名前でFirebaseを利用するためのインスタンスを取得する関数を登録しています。
同じURLのサービスインスタンスを何度も作成する場合には、このようにfactory()を用いて登録しておきます。

[d] config()はモジュールの設定を行う関数です。ここではngModuleの設定を行っています。
URLに対して、対応するコントローラとテンプレート用HTMLを指定しています。パラメータとして値を渡したい場合には'/edit/:projectId'の用に記述します(20行目)

[e] 各コントローラの挙動を定義しています。先程パラメータとして渡した変数は、$routeParamからアクセスが可能です。(48,49行目)


list.html
[a] aタグのhrefには、最初に#をつけてページ外に飛ばないようにします。ngRouteモジュールを使って、ページ内の表示切替をする場合のテクニックです。

[b] ng-repeatには、filterやsearchを指定することで、表示する内容を制御することができます。ちなみに、orderByPriorityはFirebaseが返すオブジェクトを配列に変換するため、AngularFireが提供している機能です。Firebaseから取得したデータをng-repeatする際のおまじないだと考えて間違いないかと。


detail.html
[a1-a5] ここではフォームのバリデーションをしています。各フォームの部品に対して、以下のような属性を適宜追加することで、ユーザに優しい動的なフォームバリデーションが可能となります。

  • ng-class="{クラス名:条件}"で、指定した条件が満たされたときに、この要素に追加されるクラス名を指定
  • ng-show="条件" 条件が満たされたときに表示(満たされていないときには非表示になる)
  • ng-disabled="条件" 条件が満たされたときに無効

また上記に設定する条件は、基本的なものは既に提供されています→バリデーションに関する説明とか(英語)

今回使用しているものは以下

  • .$invalidで、入力値が無効かどうか(フォームの場合は、フォーム内の項目で1つでも無効なものがあれば無効判定)
  • .$pristineで、未だユーザが値を入力したことがない(一度でも何かを入力したらその後はずっとfalse)
  • .$error.xxxxで、xxxxに指定された形式でのフォーマットチェックを行います。urlでurl形式かどうか、invalidは必須判定

[b] このdetail.htmlテンプレートは、ブックマークの新規作成時と編集時に呼び出されますが、$scope.projectにFirebaseサービスインスタンスが設定されているのは編集時のみです。なので、project.$removeで削除ボタンの表示・非表示の制御を行っています。


大筋としては、こんな感じだと思います。解釈がおかしい点も多々あると思うので、鵜呑みにはしないでください。


ソースの説明が飛び飛びで分かりづらいと思うので、Firebaseとの連携、ngRouteモジュールの使用についてだけ、ざっくり以下に纏めました。

Firebaseとの連携部分について超ざっくり説明

1.firebase.js、angularfire.jsを読込

  <script src="https://cdn.firebase.com/v0/firebase.js"></script>
  <script src="https://cdn.firebase.com/libs/angularfire/0.7.1/angularfire.min.js"></script>

2.依存モジュールとして、'firebase'を追加

var mainModule = angular.module('project', ['firebase']);

3.取得したいデータに対するサービスインスタンスを取得

var instance = $firebase(new Firebase('https://intense-fire-xxxx.firebaseio.com/angularSample3/[キー]/[キー]/...'));

上記でFirebase内のid(複数指定でその階層までさかのぼる)に対応したデータに対応するオブジェクトを取得できる

4.取得したサービスインスタンスを使って、データの取得や変更を行う。
主なプロパティとメソッドとしては

  • instance.[キー]でその内部データにアクセス
  • instance.$add(オブジェクト, コールバック関数):その階層に新たにオブジェクトを追加
  • instance.$save():DOMの内容とFirebase内の情報を同期
  • instance.$remove()、対象のデータをFirebaseから削除
ngRouteモジュールの使用部分について超ざっくり説明

1.angular-route.jsを読込

<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.16/angular-route.min.js"></script>

2.表示を切り替えたい要素に対して、ng-view属性を追加

<div ng-view></div>

3.依存モジュールとして、'ngRoute'を追加

var mainModule = angular.module('project', ['ngRoute']);

4.angular.module.config()で、ngRouteモジュールの設定(=urlとコントローラ、テンプレートの紐付け)を行う
※各コントローラの挙動の定義も行う必要があります。

mainModule.config(function($routeProvider) {
  $routeProvider
    .when('/', {
      controller:'ListCtrl',
      templateUrl:'list.html'
    })
    .when('/edit/:projectId', {
      controller:'EditCtrl',
      templateUrl:'detail.html'
    })
    .when('/new', {
      controller:'CreateCtrl',
      templateUrl:'detail.html'
    })
    .otherwise({
      redirectTo:'/'
    });
})

5.aタグでのページ遷移時には、#を最初に指定する

  <a href="#/" class="btn">Cancel</a>

やってみて...

書き始めて10分後ぐらいに思ったことですが、1つのサンプルで3つのことを盛り込むよりも、個別にもっとシンプルなソースで、1つずつ説明したほうがきっといいんだろうなと思いました。。。
公式サンプルは、チュートリアルではなくて、こんなことができますよ程度に捉えたほうがいいのかも(いまさら)

次の公式サンプル4は、いずれまた。。。