【AngularJS】スコープの継承で地味にハマりがちなこと


これは[AngularJS Advent Calendar 2014](http://www.adventar.org/calendars/350)の23日目の記事です。
前回はcan_i_do_webさんの[AngularJS 2.0 のご様子](http://pote.hatenadiary.jp/entry/2014/12/20/205438)というお話でした。
先日出てきたこの話……。ﾂﾗﾀﾝ。


## 気を取り直して。

私の本日のネタは、表題の通り。
AngularJSに慣れ親しんだ方々には当たり前の内容かもしれませんが、そうではない人には「なんだこれ！？」的な話になるかと思います。


それではスタート。


## スコープの継承

コントローラーが階層化された際には、子スコープは親スコープを継承して派生します。

```html
<div ng-controller="hogeCtrl">
  <p>{{text}}</p>
  <div ng-controller="fugaCtrl">
    <p>{{text}}</p>
  </div>
</div>
```
```javascript
hogeCtrl = [
  '$scope',
  function($scope){
    $scope.text = 'Lorem ipsum';
  }
];
```
この場合、どちらの{{text}}も`Lorem ipsum`が表示されます。

ここで、hogeCtrl直下に$scope.textを書き換えるボタンを追加してみましょう。

```html
<div ng-controller="hogeCtrl">
  <p>{{text}}</p>
  <button ng-click="text='いろはにほへと'">button1</button>
  <div ng-controller="fugaCtrl">
    <p>{{text}}</p>
  </div>
</div>
```

このボタンを押すと、どちらの{{text}}も`いろはにほへと`に変わります。

ここまではおｋ？

では、今度はfugaCtrlの下に$scope.textを書き換えるボタンを追加してみましょう。

```html
<div ng-controller="hogeCtrl">
  <p>{{text}}</p>
  <button ng-click="text='いろはにほへと'">button1</button>
  <div ng-controller="fugaCtrl">
    <p>{{text}}</p>
　  <button ng-click="text='いろはにほへと'">button2</button>
  </div>
</div>
```

この、新しく追加されたボタンを押すと **下の{{text}}は`いろはにほへと`に変わりますが、上の{{text}}は`Lorem ipsum`のママ** です。

実際に動くコードは[こちら](http://plnkr.co/edit/KVa6CCrW281kvKlHmK9L?p=preview)。

## ここであなたはこう思うでしょう。

「親のスコープの値の変更は子に影響するけど、子のスコープの値の変更は親のスコープの値には影響しないんだ！！」

## さて、次に。

さっきのは忘れて、次にこのコードを見てください。

```html
<div ng-controller="hogeCtrl">
  <p>{{values.text}}</p>
  <button ng-click="values.text='いろはにほへと'">button1</button>
  <div ng-controller="fugaCtrl">
    <p>{{values.text}}</p>
　  <button ng-click="values.text='いろはにほへと'">button2</button>
  </div>
</div>
```
```javascript
hogeCtrl = [
  '$scope',
  function($scope){
    $scope.values = {
    	text: 'Lorem ipsum'
    };
  }
];
```

さきほどとの違いは、{{text}}が{{values.text}}のように連想配列のvalueになっている点ですね。

この状態で、上のhogeCtrl直下のボタンを押すと、先ほどのように両方ともに`いろはにほへと`に変更されます。

そして、下のfugaCtrl直下のボタンを押すと、さっきの例と同じく親のスコープの値は変更されないで、下の{{values.text}}だけ変更され、、、


## ！！！！！？？？？？

実際のコードは[こちら](http://plnkr.co/edit/KrR68SpZ6ev8JKiI5X46?p=preview)にあります。

一度触ってみてください。

結果としては、 **親のスコープの値も変更されてしまします** 。


## なんなの？これ？

結論から言うと、 **この継承には、Javascriptのプロトタイプ継承が使われている** ことがこの挙動の原因となっています。

最初の例と次の例の違いは何か。
対象となっている値が、 **プリミティブ型(数値、文字列など)かオブジェクト型**ということです。

プロトタイプ継承では、これらの継承では違う挙動を示します。
それが同じくこの、スコープの親子で発生している、というわけです。

プロトタイプ継承についてはここでは詳しくは説明しませんが、とりあえずこの「 **プリミティブ型とオブジェクト型とで継承時の挙動が変わる** 」ことだけは覚えておいたほうが良いです。ハマります。 **私も昔ハマりました**。

同じ話で、JavaScript Advent Calendar 2014の[こちらの記事](http://qiita.com/hkusu/items/6cc7bc63a0f09e88bf68#1-%E5%A4%89%E6%95%B0%E3%81%B8%E5%80%A4%E3%82%92%E4%BB%A3%E5%85%A5%E3%81%97%E3%81%9F%E5%A0%B4%E5%90%88%E3%81%AE%E6%8C%99%E5%8B%95)のような話もありますね。

本件とは直接は関係ないですが、[ng-ifが独立スコープを作る](http://qiita.com/norami_dream/items/ced9e6cd2f2f12b4e956)みたいな話もあったり、このあたりは地味にハマりどころですな。

## 対策法

`$parent.text`みたいに明示的に親スコープを参照するか、2つ目の例みたいにオブジェクトにしてしまうのが早いかと思います。
大規模な開発な場合はおとなしく`$emit`や`$broadcast`、`$on`を使いましょう。


## おまけ
こうやって祝日に律儀にAdvent Calendarに投稿していますが、本日は地味に私の誕生日だったりします。

ということで → **[Amazonウィッシュリスト](http://www.amazon.co.jp/registry/wishlist/TFBOWQCBG2T0)**


[管理画面 Advent Calendar](http://www.adventar.org/calendars/350)に私が本日投稿した記事、[管理画面 × AngularJS](http://qiita.com/migi/items/ba86cba2044340c799e8)も良かったら読んであげてくださいー。

おまけのおまけ。[うちの猫 Advent Calendar 2014](http://migi.tumblr.com/kahlua2014)にも投稿してます。

## 明日の投稿は……
IMAIMIAMIさんのこちらの記事ですー。
[クリスマスだし爆速でMEAN stackのWebサービスを作りながら入門する。](http://www.imaimiami.com/kurisumasudasibao-su-demean-stacknowebsabisuzuo-rinagararu-men-suru/)