読者です 読者をやめる 読者になる 読者になる

howdylikes

Google Developersってわかりづらいよね

AngularJS イベント通知 Scope#$emit, Scope#$broadcast, Scope#$on

AngularJS

$scopeのイベント通知関連の関数は以下の3つがある

  • Scope#$emit 派生元に向けてイベントを送信(自身を含む)
  • Scope#$broadcast 派生先に向けてイベントを送信(自身を含む)
  • Scope#$on イベントを受信

リファレンス
https://code.angularjs.org/1.4.7/docs/api/ng/type/$rootScope.Scope#$on

$emitと$on

$emit

$emitは自身と派生元に向けてイベントを投げます
イベントを区別するための名前と渡したいデータを指定
$emit(name, args);

$on

$onはイベントを受け取ります
$onはイベント名とイベントを受け取るコールバックを指定
$on(name, listener);

コールバックはeventとデータを受け取る事が可能
function(event, ...args)

eventの中には以下の4つのプロパティが存在する(メソッドは後述)

  • targetScope // $emitを実行した時のScope
  • currentScope // $onが呼ばれた時のScope
  • name // イベント名
  • defaultPrevented // イベントを防ぐ?動作変更用らしい

親、子、孫で同一イベント名を定義して孫から$emitする

angular.module('myapp', [])
    .value('printLog', function(event, data) {
        console.log('event.name:' + event.name) // イベント名
        console.log('event.targetScope.text:' + event.targetScope.text); // $emitないし$broadcastした$scopeからtextを取り出す
        console.log('event.currentScope.text:' + event.currentScope.text); // myControllerの$scopeからtextを取り出す
        console.log('event.defaultPrevented:' + event.defaultPrevented); // true or false (デフォルトはfalse)
        console.log('data:' + data);
    })
    .controller('myController', function ($scope, printLog) {
        $scope.$on('MyEvent', function (event, data) {
            console.info('myController-MyEvent');
            printLog(event, data)
        });
        $scope.text = "myControllerのscope";
    })
    .controller('myControllerChild', function ($scope, printLog) {
        $scope.$on('MyEvent', function (event, data) {
            console.info('myControllerChild-MyEvent');
            printLog(event, data);
        });
        $scope.text = "myControllerChildのscope";
    })
    .controller('myControllerGrandChild', function ($scope, printLog) {
        $scope.$on('MyEvent', function (event, data) {
            console.info('myControllerGrandChild-MyEvent');
            printLog(event, data);
        });
        $scope.text = "myControllerChildGrandchildのscope";
        $scope.$emit('MyEvent', 'myControllerGrandChild-$emit-MyEvent');
    });
// 実行結果
myControllerGrandChild-MyEvent
event.name:MyEvent
event.targetScope.text:myControllerChildGrandchildのscope
event.currentScope.text:myControllerChildGrandchildのscope
event.defaultPrevented:false
data:myControllerGrandChild-$emit-MyEvent

myControllerChild-MyEvent
event.name:MyEvent
event.targetScope.text:myControllerChildGrandchildのscope
event.currentScope.text:myControllerChildのscope
event.defaultPrevented:false
data:myControllerGrandChild-$emit-MyEvent

myController-MyEvent
event.name:MyEvent
event.targetScope.text:myControllerChildGrandchildのscope
event.currentScope.text:myControllerのscope
event.defaultPrevented:false
data:myControllerGrandChild-$emit-MyEvent

name、targetScope、dataはすべて同じ
currentScopeは必ず変わりdefaultPreventedは後述するpreventDefault()を使用することで変化します

$emitの伝搬図

f:id:howdy39:20151210213908p:plain

$broadcast

$broadcastは自身と派生先に向けてイベントを投げます
イベントを区別するための名前と渡したいデータを指定
$broadcast(name, args);

親から$broadcastを投げる

$broadcastをcontroller内で直接読んでも子に伝搬されません、おそらくその時点では子controllerは読み込まれてないからでしょう。

angular.module('myapp', [])
    .value('printLog', function(event, data) {
        console.log('event.name:' + event.name) // イベント名
        console.log('event.targetScope.text:' + event.targetScope.text); // $emitないし$broadcastした$scopeからtextを取り出す
        console.log('event.currentScope.text:' + event.currentScope.text); // myControllerの$scopeからtextを取り出す
        console.log('event.defaultPrevented:' + event.defaultPrevented); // true or false (デフォルトはfalse)
        console.log('data:' + data);
    })
    .controller('myController', function ($scope, printLog) {
        $scope.$on('MyEvent', function (event, data) {
            console.info('myController-MyEvent');
            printLog(event, data)
        });
        $scope.text = "myControllerのscope";
        this.broadcastMessage = function () { // 画面上からここを発火
            $scope.$broadcast('MyEvent', 'myController-$broadcast-MyEvent');
        }
    })
    .controller('myControllerChild', function ($scope, printLog) {
        $scope.$on('MyEvent', function (event, data) {
            console.info('myControllerChild-MyEvent');
            printLog(event, data);
        });
        $scope.text = "myControllerChildのscope";
    })
    .controller('myControllerGrandChild', function ($scope, printLog) {
        $scope.$on('MyEvent', function (event, data) {
            console.info('myControllerGrandChild-MyEvent');
            printLog(event, data);
        });
        $scope.text = "myControllerChildGrandchildのscope";
    });
// 実行結果
myController-MyEvent
event.name:MyEvent
event.targetScope.text:myControllerのscope
event.currentScope.text:myControllerのscope
event.defaultPrevented:false
data:myController-$broadcast-MyEvent

myControllerChild-MyEvent
event.name:MyEvent
event.targetScope.text:myControllerのscope
event.currentScope.text:myControllerChildのscope
event.defaultPrevented:false
data:myController-$broadcast-MyEvent

myControllerGrandChild-MyEvent
event.name:MyEvent
event.targetScope.text:myControllerのscope
event.currentScope.text:myControllerChildGrandchildのscope
event.defaultPrevented:false
data:myController-$broadcast-MyEvent

$broadcastの伝搬図

ちゃんと確認は取っていないのですがちょっと動かしてみたところDOMの順で呼ばれていそうな感じ
f:id:howdy39:20151210214833p:plain

その他 eventの2つのメソッド

eventの中には以下の4つのプロパティの他に2つのメソッドが存在

  • event.stopPropagation(); // イベントの伝搬を止める($emit時のみ使用可能。※$broadcast時はメソッドがない)
  • event.preventDefault(); // event.defaultPreventedをtrueにする

event.defaultPreventedはeventの伝搬時にちゃんと受け継ぎます。
dataは途中で書き換えても変わらないのでこれを使って制御可能。ただしtrueとfalseだけ。
細かく制御したい場合は別イベント名にして$emitしなおしてdataに制御用の値を入れるとかになりそう?