Works by

Ren's blog

@rennnosuke_rk 技術ブログです

【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;
  });
}]);
実行結果

codepen.io

解説

まず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です。 broadcastServicesharedValueと呼ばれる状態変数を持っており、この値はbroadcastService内で管理されます。

changeValueメソッドはこの状態変数を更新します。同時に、「状態変数を更新した」というイベントの通知を、$rootScope.$broadcastを使って文字通りブロードキャストします。

assignEventHandlerchangeValueが発行した通知をキャッチし、任意の処理を実行するイベントハンドラを設定します。イベントハンドラ$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コンポーネントやモジュールを監視し続けるということをしなくても良いので、より疎結合なコード設計にしやすくできると考えられます。

参考

AngularJS

www.muicss.com