【AngularJS】AngularJSで任意のイベント通知を送信・ハンドリングする
AngularJSのイベントハンドリング
JavaScriptでは多くのイベントをハンドリングする手段がありますが、AngularJSも独自にイベントハンドリングするための機能を備えています。 例えば、AngularJSでのスクロールイベントのハンドリングは以下のように行います。
// Controller angular.module('myApp') .controller('MyController', ['$scope', function($scope){ $scope.$on('scroll', function($event, args){ // イベントハンドリング }); }]);
JS標準やJQueryなどと同様、イベント通知を受け取ったときに走らせるハンドラ関数を登録できます。
ところで、AngularJSではscroll
などの規定のイベントだけではなく、任意のイベント通知を送信することができます。
この仕組みを用いることで、例えば「特定のリスト項目が押されたとき」などのイベントを不特定多数のハンドラに通知することができます。
$rootScope.$broadcast
AngularJSでは$rootScope.$broadcast
サービスを使うことでイベントの通知を行うことが
できます。
以下のコードは、あるコントローラCtrlA
が管理するDOM領域内のボタンが押すとイベント通知が発生し、別のコントローラCtrlB
がそれをハンドリングする・・・という例を表しています。
HTML
<div ng-app="myApp"> <div class="ctrl1 mui-panel" ng-controller="Ctrl_A"> <h3>Controller1</h3> value : <input id="input" type="text" ng-model="value"/> <br> <input type="button" value="broadcast" class="mui-btn mui-btn--raised mui-btn--accent" ng-click="onButtonClicked()" /> </div> <div class="ctrl2 mui-panel" ng-controller="Ctrl_B"> <h3>Controller2</h3> value : {{value}} <br> </div> </div>
JS
let mod = angular.module('myApp',[]); // 状態変数を管理し、イベント通知も行うService mod.factory('broadcastService', [ '$rootScope', function($rootScope) { // 状態変数 let sharedValue = null; // イベントラベル const VALUE_CHANGED = 'VALUE_CHANGED'; // 状態変化+イベントを通知 const changeValue = function broadCast(newValue) { sharedValue = newValue; $rootScope.$broadcast(VALUE_CHANGED, newValue); }; // イベントハンドラ登録 const assignEventHandler = function assignEventHandler($scope, handler) { $scope.$on(VALUE_CHANGED, function(event, newValue) { handler(newValue); }); }; return { changeValue, getValue, assignEventHandler }; }]); // コントローラA // ボタンをクリックすると、broadcastServiceの持つ状態変数にフォーム値をセットする // 同時に状態変数が変化したことに対する通知処理が走る mod.controller('Ctrl_A', ['$scope', 'broadcastService' , function($scope, broadcastService){ $scope.onButtonClicked = function() { broadcastService.changeValue($scope.value); } }]); // コントローラB // 「broadcastServiceの持つ状態変数が変化する」というイベントに対するハンドラを登録 mod.controller('Ctrl_B', ['$scope', 'broadcastService', function($scope, broadcastService){ broadcastService.assignEventHandler($scope, function(newValue){ $scope.value = newValue; }); }]);
実行結果
解説
まずHTML側を見ていきます。
HTML上ではng-controller
が修飾された箇所が2つ存在し、これらの領域は別々のコントローラによって管理されています。そのため、値value
を参照する箇所が2つありますが、参照先は両者で異なります。例えば、Ctrl_A
配下のinput
フォームに値を入力しても、Ctrl_B
配下の{{value}}
は変化しません。Ctrl_B
配下の{{value}}
が変化するのは、Ctrl_B
が持つ$scope.value
に値をセットしたときになります。
<div class="ctrl1 mui-panel" ng-controller="Ctrl_A"> <h3>Controller1</h3> value : <input id="input" type="text" ng-model="value"/> <br> <input type="button" value="broadcast" class="mui-btn mui-btn--raised mui-btn--accent" ng-click="onButtonClicked()" /> </div>
<div class="ctrl2 mui-panel" ng-controller="Ctrl_B"> <h3>Controller2</h3> value : {{value}} <br> </div>
Ctrl_A
では、HTML上のボタンに対してクリックリスナーを提供しています。
リスナーが発火すると、input
フォームに入力された値が後述するbroadcastSerice
の管理する値にセットされます。同時に、値が変更した旨を知らせる通知処理が実行されます。
// コントローラA // ボタンをクリックすると、`broadcastService`の持つ状態変数にフォーム値をセットする // 同時に状態変数が変化したことに対する通知処理が走る mod.controller('Ctrl_A', ['$scope', 'broadcastService' , function($scope, broadcastService){ $scope.onButtonClicked = function() { broadcastService.changeValue($scope.value); } }]);
Ctrl_B
ではbroadcastService
の持つ状態変数が変化したことを監視するイベントハンドラを設定しています。
状態変数が変化したとき、すなわちCtrl_A
のクリックリスナーが走ったときに、$scope.value
に値がセットされてブラウザ上に表示されます。
// コントローラB // 「broadcastServiceの持つ状態変数が変化する」というイベントに対するハンドラを登録 mod.controller('Ctrl_B', ['$scope', 'broadcastService', function($scope, broadcastService){ broadcastService.assignEventHandler($scope, function(newValue){ $scope.value = newValue; }); }]);
最後にbroadcastService
です。
broadcastService
はsharedValue
と呼ばれる状態変数を持っており、この値はbroadcastService
内で管理されます。
changeValue
メソッドはこの状態変数を更新します。同時に、「状態変数を更新した」というイベントの通知を、$rootScope.$broadcast
を使って文字通りブロードキャストします。
assignEventHandler
はchangeValue
が発行した通知をキャッチし、任意の処理を実行するイベントハンドラを設定します。イベントハンドラは$scope.$on
で設定できます。また、実行する処理は予めassignEventHandler
の呼び出し側から設定します。
// 状態変数 let sharedValue = null; // ... // 状態変化+イベントを通知 const changeValue = function broadCast(newValue) { sharedValue = newValue; $rootScope.$broadcast(VALUE_CHANGED, newValue); }; // イベントハンドラ登録 const assignEventHandler = function assignEventHandler($scope, handler) { $scope.$on(VALUE_CHANGED, function(event, newValue) { handler(newValue); }); };
このイベント通知の仕組みによって、任意のイベントを発行できることが確認できました。
また、$scope.$on
によるイベントハンドリングでは特定のUIコンポーネントやモジュールを監視し続けるということをしなくても良いので、より疎結合なコード設計にしやすくできると考えられます。