Angular JS で複数のコントローラ間でモデル（状態や値）を共有する方法 3 種類

## はじめに

Angular JS で複数のコントローラ間でモデル（状態や値）を共有する方法として、次の 3 種類を解説します。

* モデルを共有するサービスを使用する (Shared Service)。
* 親コントローラのスコープを子コントローラで共有する (Parent Scope Sharing)。
* イベントを利用する (Pub/Sub)。

## Shared Service

複数のコントローラ間で共有するモデルをサービスとして作成し、そのサービスを複数のコントローラで参照します。
実装例を示します。

```html:app.html
<!DOCTYPE HTML>
<html ng-app="AngularJsStudy">
<head>
    <title>Shared State Service - AngularJS Study</title>
    <meta charset="utf-8" />
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <meta name="viewport" content="width=device-width" />
    <link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css" />
    <link rel="stylesheet" href="app.css" />
</head>
<body>
    <h4>Shared State Service</h4>
    <div class="container ng-cloak" ng-controller="ShareControllerA">
        <h5>This is ShareControllerA</h5>
        <input type="text" class="form-control" ng-model="data.text">
    </div>
    <div class="container ng-cloak" ng-controller="ShareControllerB">
        <h5>This is ShareControllerB</h5>
        <input type="text" class="form-control" ng-model="data.text">
    </div>

    <script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
    <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.3/angular.min.js"></script>
    <script src="//netdna.bootstrapcdn.com/bootstrap/3.0.0/js/bootstrap.min.js"></script>
    <script src="app.js"></script>
</body>
</html>
```

```javascript:app.js
(function() {
    var app = angular.module("AngularJsStudy", []);

    app.factory("SharedStateService", function() {
        return {
            text: 'SharedStateService'
        };
    });

    app.controller("ShareControllerA", function($scope, SharedStateService) {
        $scope.data = SharedStateService;
    });

    app.controller("ShareControllerB", function($scope, SharedStateService) {
        $scope.data = SharedStateService;
    });
}());
```

`ShareControllerA` と `ShareControllerB` では、スコープ `$scope` の `data` に `SharedStateService` をバインドしています。
こうすることで、AngularJS の一番の特長であるビューとモデルの双方向バインドを効果的に利用できます。

## Parent Scope Sharing

AngularJS ではコントローラをネストすることができます。
コントローラをネストした場合、子要素のコントローラは、親要素のコントローラのスコープを参照することができます。
実装例を示します。

```html:app.html
<!DOCTYPE HTML>
<html ng-app="AngularJsStudy">
<head>
    <title>Parent Scope Sharing - AngularJS Study</title>
    <meta charset="utf-8" />
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <meta name="viewport" content="width=device-width" />
    <link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css" />
    <link rel="stylesheet" href="app.css" />
</head>
<body>
    <h4>Parent Scope Sharing</h4>
    <div class="container ng-cloak" ng-controller="MasterController">
        <h5>Master Controller</h5>
        <input type="text" class="form-control" ng-model="text">
        <hr/>
        <div class="container" ng-controller="ChildControllerA">
            <h5>Child Controller A</h5>
            <input type="text" class="form-control" ng-model="$parent.text">
        </div>
        <hr/>
        <div class="container" ng-controller="ChildControllerB">
            <h5>Child Controller B</h5>
            <input type="text" class="form-control" ng-model="$parent.text">
        </div>
    </div>

    <script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
    <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.3/angular.min.js"></script>
    <script src="//netdna.bootstrapcdn.com/bootstrap/3.0.0/js/bootstrap.min.js"></script>
    <script src="app.js"></script>
</body>
</html>
```

```javascript:app.js
(function() {
    var app = angular.module("AngularJsStudy", []);

    app.controller("MasterController", function($scope) {
        $scope.text = "Shared Text";
    });

    app.controller("ChildControllerA", function($scope) {
    });

    app.controller("ChildControllerB", function($scope) {
    });
}());
```

`Shared Service` と同様の方法で、こちらの方が直感的かもしれませんが、
AngularJS のお勧めではビューに無関係なロジックはサービスにとあるので、
AngularJS らしく実装したいなら `Shared Service` の方でしょう。

## Pub/Sub

プロパティが変更されたら、`$scope` の `$emit()`, `$broadcast()` メソッドを利用してイベントを送信し、
`$scope` の `$on` メソッドでイベントを受信する方法です。
実装例を示します。

```html:app.html
<!DOCTYPE HTML>
<html ng-app="AngularJsStudy">
<head>
    <title>Pub/Sub - Share State</title>
    <meta charset="utf-8" />
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <meta name="viewport" content="width=device-width" />
    <link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css" />
    <link rel="stylesheet" href="app.css" />
</head>
<body>
    <h4>Pub/Sub</h4>
    <div class="container" ng-controller="Controller">
        <h5>Controller 1</h5>
        <input type="text" class="form-control" ng-model="text">
        <button type="button" class="btn btn-default" ng-click="setText()">設定</button>
    </div>
    <hr/>
    <div class="container" ng-controller="Controller">
        <h5>Controller 2</h5>
        <input type="text" class="form-control" ng-model="text">
        <button type="button" class="btn btn-default" ng-click="setText()">設定</button>
    </div>

    <script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
    <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.3/angular.min.js"></script>
    <script src="//netdna.bootstrapcdn.com/bootstrap/3.0.0/js/bootstrap.min.js"></script>
    <script src="app.js"></script>
</body>
</html>
```

```javascript:app.js
(function() {
    var app = angular.module("AngularJsStudy", []);

    app.factory("SharedService", ["$rootScope", function($rootScope) {
        var text = "Shared Text";

        return {
            text: {
                get: function() { return text; },
                set: function(t) {
                    console.log("[enter] text.set");
                    text = t;
                    $rootScope.$broadcast('changedText');
                    console.log("[leave] text.set");
                }
            }
        };
    }]);

    app.controller("Controller", function($scope, SharedService) {
        $scope.text = SharedService.text.get();

        $scope.setText = function() {
            console.log("[enter] setText");
            SharedService.text.set($scope.text);
            console.log("[leave] setText");
        };

        $scope.$on('changedText', function() {
            console.log("[enter] changedText");
            $scope.text = SharedService.text.get();
            console.log("[leave] changedText");
        });
    });
}());
```

実装例ではすべてのコントローラにイベントが届くように `$rootScope` の `$broadcast()` メソッドを利用してイベントを送信しています。

## 参考

* [Sharing Data, State and Models on AngularJS: Alternatives, comparison and my solution](http://www.blogeek.com.ar/2013/05/01/sharing-data-state-on-angularjs-alternatives-comparison-and-my-solution/)

参考サイトによると directive を用いてモデルを共有する方法もあるようです。