Posted at

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

More than 5 years have passed since last update.


はじめに

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


  • モデルを共有するサービスを使用する (Shared Service)。

  • 親コントローラのスコープを子コントローラで共有する (Parent Scope Sharing)。

  • イベントを利用する (Pub/Sub)。


Shared Service

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

実装例を示します。


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>



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;
});
}());


ShareControllerAShareControllerB では、スコープ $scopedataSharedStateService をバインドしています。

こうすることで、AngularJS の一番の特長であるビューとモデルの双方向バインドを効果的に利用できます。


Parent Scope Sharing

AngularJS ではコントローラをネストすることができます。

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

実装例を示します。


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>



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 メソッドでイベントを受信する方法です。

実装例を示します。


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>



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() メソッドを利用してイベントを送信しています。


参考

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