AngularJSの1.3から1.4へのバージョンアップ

[@tsuchikazu](https://twitter.com/tsuchikazu)です。半年ほど仕事でAngularと戯れています。

3/21に開催された[ng-japan](http://ngjapan.org/)では、あと1週間後ほどでリリースされるとアナウンスされていたAngular 1.4
あれから10週間ほど経った5/27。ついに、Angular1.4の安定版がリリースされました。

[Developer Guide: Migrating from 1.3 to 1.4](https://docs.angularjs.org/guide/migration)と[CHANGELOG](https://github.com/angular/angular.js/blob/master/CHANGELOG.md)から、Breaking Changeを中心に簡単にまとめましたので、公開します。

## 概要
Angular1.4では、主に[ngAnimate](https://docs.angularjs.org/api/ngAnimate)の大きなリファクタリングと、[ngMessages](https://docs.angularjs.org/api/ngMessages)の機能改善、[$cookies](https://docs.angularjs.org/api/ngCookies/service/$cookies)への新しいAPIが追加されました。

ngAnimateのリファクタリングは、今まで存在していたissueやバグフィックスと、より便利なアニメーションを提供するための新APIのために行われました。これまでJavascriptとCSSアニメーションは、同時に実行することができていましたが、1.4からはできなくなります。しかし、同じ効果を出すために[$animateCss](https://docs.angularjs.org/api/ngAnimate/service/$animateCss)が新しく提供されました。$animateCssを使うことで、JavascriptからCSSアニメーションを扱うことができ、今までより柔軟なアニメーションを提供できます。

```javascript
ngModule.animation('.slide-animation', ['$animateCss', function($animateCss) {
  return {
    enter: function(element, doneFn) {
      // this will trigger a `.ng-enter` and `.ng-enter-active` CSS animation
      var animation = $animateCss(element, {
        event: 'enter'
        // any other CSS-related properties
        //   addClass: 'some-class',
        //   removeClass: 'some-other-class',
        //   from: {},
        //   to: {}
      });

      // make sure to read the ngAnimate docs to understand how this works
      animation.start().done(doneFn);
    }
  }
}]);
```

ngMessagesは、エラーメッセージを動的に表示するための機能として、エラーメッセージの配列を受け取ることが出来るようになり、`ng-repeat`などで自由に表示することができます。`ng-message-exp`directiveも新しく提供されました。

```html
<div ng-messages="myForm.myEmail.$error" role="alert">
  <div ng-message="required">You did not enter your email address</div>
  <div ng-repeat="errorMessage in errorMessages">
    <!-- use ng-message-exp for a message whose key is given by an expression -->
    <div ng-message-exp="errorMessage.type">{{ errorMessage.text }}</div>
  </div>
</div>
```

`$cookies`は、`$cookieStore`がdeprecatedになり、`$cookies`に統合されました。また、pathやdomainを指定できるようになりました。

```javascript
var id = $cookies.get('id');
$cookies.put('id', id);
var object = $cookies.getObject('objectKey');
$cookies.putObject(object);

$cookies.put('key','value', {
  path : '/',
  expires : new Date(2015,7,1)
  secure : false,
  domain : 'tsuchikazu.net'
});
```

パフォーマンス面では、digest timeが最大30%、メモリ使用量は2%〜4%改善されています。その他、`ngRepeat`、`ngOptions`、`$http`、`filter`などで細かな変更が入っていますが、大きな変更はないため1.3から1.4へは簡単にmigrationできるでしょう。

では、細かいBreaking Changeを紹介します。

## Breaking Change
### [Animation](https://docs.angularjs.org/api/ngAnimate)
[c8700f0](https://github.com/angular/angular.js/commit/c8700f04fb6fb5dc21ac24de8665c0476d6db5ef)によって、animationに関するcallbackに`$animate`を使うようになります。

```javascript
// < 1.4
element.on('$animate:before', function(e, data) {
  if (data.event === 'enter') { ... }
});
element.off('$animate:before', fn);

// 1.4+
$animate.on(element, 'enter', function(data) {
  //...
});
$animate.off(element, 'enter', fn);
```

[c8700f0](https://github.com/angular/angular.js/commit/c8700f04fb6fb5dc21ac24de8665c0476d6db5ef)によって、`$animate.enabled()`の引数の順番が変わります。

```javascript
// < 1.4
$animate.enabled(false, element);

// 1.4+
$animate.enabled(element, false);
```

[c8700f0](https://github.com/angular/angular.js/commit/c8700f04fb6fb5dc21ac24de8665c0476d6db5ef)によって、 `$animate.enabled(element, false)`した際に子要素のanimationがdisabledになっていましたが、自要素もdisabledになります。

[c8700f0](https://github.com/angular/angular.js/commit/c8700f04fb6fb5dc21ac24de8665c0476d6db5ef)によって、animation promiseのthenの中で`$scope.apply`や`$scope.$digest`をする必要がなくなります。

```javascript
// < 1.4
$animate.enter(element).then(function() {
  $scope.$apply(function() {
    $scope.explode = true;
  });
});

// 1.4+
$animate.enter(element).then(function() {
  $scope.explode = true;
});
```

### Forms(ngMessages, ngOptions)
#### [ngMessages](https://docs.angularjs.org/api/ngMessages)
[c9a4421](https://github.com/angular/angular.js/commit/c9a4421fc3c97448527eadef1f42eb2f487ec2e0)によって、`ng-messages-include`の指定場所が`ng-messages`のattributeから`ng-messages`の子要素に移動しました。

```html
<!-- AngularJS 1.3.x -->
<div ng-messages="model.$error" ng-messages-include="remote.html">
  <div ng-message="required">Your message is required</div>
</div>

<!-- AngularJS 1.4.x -->
<div ng-messages="model.$error">
  <div ng-message="required">Your message is required</div>
  <div ng-messages-include="remote.html"></div>
</div>
```

#### [ngOptions](https://docs.angularjs.org/api/ng/directive/ngOptions)
[7fda214](https://github.com/angular/angular.js/commit/7fda214c4f65a6a06b25cf5d5aff013a364e9cef)によって、展開される<option>のvalueの値が、indexやkeyだったものが、hashKeyが出力されるようになります。

```html
<!-- AngularJS 1.3.x -->
<select ng-model="x" ng-option="i in items">
  <option value="1">a</option>
  <option value="2">b</option>
  <option value="3">c</option>
  <option value="4">d</option>
</select>
<!-- AngularJS 1.4.x -->
<select ng-model="x" ng-option="i in items">
  <option value="string:a">a</option>
  <option value="string:b">b</option>
  <option value="string:c">c</option>
  <option value="string:d">d</option>
</select>
```
ngModelへの反映は変わらないため、valueに依存しない限り影響はありません。
valueに依存している場合は`track by`を使って、回避することができます。


[7fda214](https://github.com/angular/angular.js/commit/7fda214c4f65a6a06b25cf5d5aff013a364e9cef)によって、`(key, value) in obj`syntaxを使った場合、keyのアルファベット順にソートされていたものが、`Object.keys(obj)`の順番になります

### Templating (ngRepeat, $compile)

#### [ngRepeat](https://docs.angularjs.org/api/ng/directive/ngRepeat)
[c260e73](https://github.com/angular/angular.js/commit/c260e7386391877625eda086480de73e8a0ba921)によって、`(key,  value) in obj"`syntaxを使っている場合、keyのアルファベット順でソートされていたのが、`for key in obj`の順番になります

#### [$compile](https://docs.angularjs.org/api/ng/service/$compile)
[6a38dbf](https://github.com/angular/angular.js/commit/6a38dbfd3c34c8f9efff503d17eb3cbeb666d422)によって、directiveのscopeに`&`で関数を受け取るようにしていて、directive使用時に関数を渡さなかった場合、空の関数がセットされていたが、undefinedがセットされるようになります。


### Cookies (ngCookies)
[38fbe3e](https://github.com/angular/angular.js/commit/38fbe3ee8370fc449b82d80df07b5c2ed2cd5fbe)によって、`$cookies`のプロパティへの`set/get`はできなくなり、`get/put`メソッドを呼び出すようになります。

```javascript
// < 1.4
var id = $cookies.id;
$cookies.id = id;

// 1.4+
var id = $cookies.get('id');
$cookies.put('id', id);
```

1.3までは、文字列や数値のcookieへの保存/参照は`$cookies`、オブジェクトの保存/参照は`$cookieStore`というように、分かれていました。1.4からは、`$cookies`に以下のメソッドが増え、`$cookieStore`はdeprecatedです。

 * get
 * put
 * getObject
 * putObject
 * getAll
 * remove

### Server Requests ([$http](https://docs.angularjs.org/api/ng/service/$http))
[5da1256](https://github.com/angular/angular.js/commit/5da1256fc2812d5b28fb0af0de81256054856369)によって、`transformRequest`によって`header`などを書き換え可能だったのが、もともと意図した使われ方では無かったため、書き換え不可になります。

```javascript
// < 1.4
function requestTransform(data, headers) {
    headers = angular.extend(headers(), {
      'X-MY_HEADER': 'abcd'
    });
  }
  return angular.toJson(data);
}

// 1.4+
$http.get(url, {
  headers: {
    'X-MY_HEADER': function(config) {
      return 'abcd'; //you've got access to a request config object to specify header value dynamically
    }
  }
})
```

### Filters(filter, limitTo)
#### [filter](https://docs.angularjs.org/api/ng/filter/filter)
filterされる値にarray以外が指定された場合、何も置きなかったのが、エラーになります

#### [limitTo](https://docs.angularjs.org/api/ng/filter/limitTo)
limitに不正な値が指定された場合、空のobjectやarrayを返していたのが、そのまま返すように変わります。

## おわりに
このように、1.4では大きなBreaking Changeがありません。今、仕事で開発しているものは、`$cookies`の修正をするだけで、ほぼテストが通りました。
みなさん早速バージョンアップしていきましょう。使用しているAngularモジュールへのプルリクエストも出すチャンスですので、みんなで修正して1.4の世界で生きていきましょう。
間違いなどあったら、ご指摘・編集リクエストお願いします。

そして、次へ待ち構える1.5や、2.0への準備も忘れずに。

* [AngularJSモダンプラクティス](http://qiita.com/armorik83/items/5542daed0c408cb9f605)
* [AngularJSの継続的なバージョンアップ](http://www.slideshare.net/TsuchiKazu/angular-js-47156399)