$watchCollection

AngularJSのデータバインドを支える$watch で見たように、$watch ではオブジェクトの参照を監視するか、またはオブジェクトの中身まですべて監視(deep watch)するかを切り替えることができる。

その 2 種類の $watch の中間に位置付けられる $watchCollection というのもあり、1 階層分だけを監視(shallow watch)してくれる。

配列の場合

配列の場合に $watch、$watchCollection、および $watch (deep watch) がそれぞれどのように異なるのかを見ていく。

Read on →


AngularJS 1.3 についての記事『AngularJS 1.3: a new release approaches』が公式ブログのほうに上がりました。

日本でのエンタープライズ(業務系)なんかでは、まだまだ IE 8 は当たり前のようにサポート対象のブラウザになっているところが多いと思うので、ちょっと大きなニュースですね。

なお、AngularJS 1.2.x 以前を IE 8 で動作させるには、過去記事『AngularJS を古い IE に対応させるには』をご参照ください。

AngularJS 1.3 は IE 8 をサポートしない

サポートをやめる理由としては、IE 8 で動作させるためのコードのせいで性能に悪影響があるし、すでに IE 8 ユーザは全体の数パーセント程度だし、Microsoft による Windows XP のサポートも 2014年4月に終わるし、とのこと。動作性能の向上はもちろん、機能を追加していくのも速くなると。

どうしても IE 8 で動作させ続けたい場合は:

  • AngularJS 1.2.x を使い続ける
  • AngularJS 1.3 をトライする
  • IE 8 で動作する AngularJS を商用サポートとして提供する会社を見つける。

1.3 バージョンでは IE 8 用のハックを積極的に削除していくことはないようで、1.2.x バージョンのアプリケーションが IE 8 で動作しているのであれば、大部分は 1.3 でも動作するのではないかということ。ただし、1.3 では IE 8 向けのテストもバグフィックスも実施しなくなると。

Read on →


$watch

AngularJS の強力なデータバインドを支える仕組みのうち、まず $watch について取り上げる。

$watch を使えば、監視(Observe)したいオブジェクトやプロパティが変化したときに実行する処理(リスナー)を容易に記述できる。

$watch を利用する場所は scope のある directive や controller で、ng-model や ng-bind のようなデータバインドする directive を独自に実装する場合や、モデルの変更に応じて処理をバインドする場合などに使用できる。

$digest サイクル

$watch による変更検知処理は、ポーリング的(一定間隔で頻繁)に実施されるのではなく、以下のイベントが生じたときに $digest サイクル(または $digest ループ)と呼ばれる処理が実行され、その中で実行される。

イベント 概要
ナビゲーション ブラウザの location 変更時
ネットワーク $http, $resource レスポンス受信時
DOM イベント ng-click, ng-mouseover などの実行時
タイマー $timeout によるタイマー処理の実行時
Read on →


Forgiving

AngularJS: Expressions ページで Forgiving として説明されているように、HTML(テンプレート)で記述する AngularJS のバインド部分({{ result.title.value }}ng-if=“result.tags.length”)では、result、title、tags が、undefined や null でないかや object かどうかということを考慮したコードにしなくていい。

result が通信してサーバから取得するデータであれば、レスポンスが返るまでの間 result は undefined の状態になるけど、だからと言って{{ ((result || {}).title || {}).c }}とか、result && result.title && result.title.valueのようにコーディングしなくていい。

Read on →


AngularJS 1.2.4

AngularJS 1.2.4 がリリースされ、$animate 関連の Bug Fixes が入り ng-include をネストした ng-repeat でアニメーションが効かない問題も解消されたので、アニメーションをちゃちゃっと試す方法を紹介。

angular-animate.js

HTML に angular と angular-animate の js ファイルを記述する。

1
2
<script src="angular.min.js">
<script src="angular-animate.min.js">

ngAnimate モジュール

依存モジュールとして ngAnimate を記述する。

1
angular.module('app', [ 'ngAnimate' ])

CSS 定義

これだけでもうゴール間近で、あとはどんなアニメーションを適用するのかを考えて定義するだけ。

アニメーションを CSS で定義する方法と JavaScript で記述する方法があり、ここではちゃちゃっと試すのが簡単な CSS を例示する。

Read on →


angular.forEach

AngularJS 標準の Global API から、angular.forEach の紹介。

angular.forEach は、Object でも Array でも回してくれる。

angular.forEach(Object, Function)

1
2
3
4
var user = { name: 'ninja', gender: 'unknown', weapons: [ ..., ... ] };
angular.forEach(user, function(value, key) {
  // ...
});

オブジェクトを回す場合の Iterator function の引数は value, key の順。

angular.forEach(Array, Function)

1
2
3
4
var records = [ { ... }, { ... } ];
angular.forEach(records, function(record, i) {
  // ...
});

配列を回す場合の Iterator function は第1引数が配列の中身で、第2引数が配列インデックスとなる。

Read on →


AngularJS の minify 対策、めんどくさい

AngularJS の JavaScript コードを minify するには、function の引数でインジェクト(DI)する各 services の名前を、文字列として重複させて記述する必要がある。

これって、めっちゃめんどくさい。あほらしすぎる。

ngmin

そこで ngmin を使う。

1
npm install -g ngmin
1
ngmin somefile.js somefile.annotate.js

以下のコードが、

somefile.js
1
2
3
4
5
6
7
8
9
angular.module('app', [])

  .controller('TodoCtrl', function($scope, $timeout, Projects) {

    // comments
    $scope.createTask = function(task) {
      task.title = "New Task";
    };
  });

ngmin 後は、こうなる。

somefile.annotate.js
1
2
3
4
5
6
7
8
9
10
angular.module('app', []).controller('TodoCtrl', [
  '$scope',
  '$timeout',
  'Projects',
  function ($scope, $timeout, Projects) {
    $scope.createTask = function (task) {
      task.title = 'New Task';
    };
  }
]);
Read on →


controller, require

その1その2その3に引き続き、今回もカスタム directive について。

今回のサンプルコードは、UI Bootstrap の Tabs からの一部抜粋で、controller requireオプションについて見ていく。

tabs.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
angular.module('ui.bootstrap.tabs', [])
.controller('TabsetController', ['$scope', function TabsetCtrl($scope) {
  var ctrl = this,
      tabs = ctrl.tabs = $scope.tabs = [];

  ctrl.select = function(tab) {
    angular.forEach(tabs, function(tab) {
      tab.active = false;
    });
    tab.active = true;
  };
  // ...
}])

.directive('tabset', function() {
  return {
    restrict: 'EA',
    transclude: true,
    replace: true,
    require: '^tabset',
    scope: {},
    controller: 'TabsetController',
    templateUrl: 'template/tabs/tabset.html',
    compile: function(elm, attrs, transclude) {
      return function(scope, element, attrs, tabsetCtrl) {
        // ...
      };
    }
  };
})

.directive('tab', ['$parse', function($parse) {
  return {
    require: '^tabset',
    restrict: 'EA',
    replace: true,
    templateUrl: 'template/tabs/tab.html',
    transclude: true,
    scope: {
      heading: '@',
      onSelect: '&select',
      onDeselect: '&deselect'
    },
    controller: function() {},
    compile: function(elm, attrs, transclude) {
      return function postLink(scope, elm, attrs, tabsetCtrl) {
        // ...
        scope.$watch('active', function(active) {
          setActive(scope.$parent, active);
          if (active) {
            tabsetCtrl.select(scope);
            scope.onSelect();
          } else {
            scope.onDeselect();
          }
        });
        // ...
        scope.select = function() {
          if (!scope.disabled ) {
            scope.active = true;
          }
        };
        tabsetCtrl.addTab(scope);
        scope.$on('$destroy', function() {
          tabsetCtrl.removeTab(scope);
        });
        // ...
      };
    }
  };
}])
Read on →


replace, transclude, scope

前々回前回に引き続き、今回もカスタム directive について。

今回のサンプルコードは、UI Bootstrap の Alert からで、replace transclude scopeオプションについて見ていく。

alert.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
angular.module("ui.bootstrap.alert", []).directive('alert', function() {
  return {
    restrict:'EA',
    templateUrl:'template/alert/alert.html',
    transclude:true,
    replace:true,
    scope: {
      type: '=',
      close: '&'
    },
    link: function(scope, iElement, iAttrs) {
      scope.closeable = "close" in iAttrs;
    }
  };
});
template/alert/alert.html
1
2
3
4
<div class="alert" ng-class="type && 'alert-' + type">
    <button ng-show="closeable" type="button" class="close" ng-click="close()">&times;</button>
    <div ng-transclude></div>
</div>
1
2
3
<alert ng-repeat="alert in alerts" type="alert.type" close="closeAlert($index)">
  {{alert.msg}}
</alert>
Read on →


compile と link

前回に引き続き、今回もカスタム directive について。

2回目の今回は、compilelinkの使い分けについて。

compile のサンプルコード

まずはcompileを利用するサンプルコードを、『Mastering Web Application Development with AngularJS』から抜粋して見ていく。

このコードでは、Bootstrap の理由を前提として、以下に示すように Bootstrap でのボタンデザインに必要な CSS class の値を追加する処理を、compileで実装している。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
myModule.directive('button', function() {
  return {
    restrict: 'E',
    compile: function(element, attrs) {
      element.addClass('btn');
      if (attrs.type === 'submit') {
        element.addClass('btn-primary');
      }
      if (attrs.size) {
        element.addClass('btn-' + attrs.size);
      }
    }
  };
});
  • <button>の class 属性にbtnを追加
  • <button type="submit">のように type="submit"がある場合には、class 属性にbtn-primaryを追加
  • <button size="...">のように size 属性がある場合には、btn-largeなど、btn- と size 属性で指定した文字列を組み合わせた文字列を class 属性に追加

コードからわかるように、compile オプションの function 第1引数にあるelementは、jQuery(jqLite)オブジェクトになっているので、直接 jQuery の API を利用できる。つまり$(element)や、$(button)などとしてやる必要はない。

第2引数のattrsではattrs.sizeのようにドット区切りで属性値にアクセスできるので、HTML テンプレート側の値を簡単に参照することができる。

Read on →