web-dev-qa-db-ja.com

AngularJSでコントローラ間で通信する正しい方法は何ですか?

コントローラ間で通信する正しい方法は何ですか?

私は現在windowを含む恐ろしいファッジを使用しています。

function StockSubgroupCtrl($scope, $http) {
    $scope.subgroups = [];
    $scope.handleSubgroupsLoaded = function(data, status) {
        $scope.subgroups = data;
    }
    $scope.fetch = function(prod_grp) {
        $http.get('/api/stock/groups/' + prod_grp + '/subgroups/').success($scope.handleSubgroupsLoaded);
    }
    window.fetchStockSubgroups = $scope.fetch;
}

function StockGroupCtrl($scope, $http) {
    ...
    $scope.select = function(prod_grp) {
        $scope.selectedGroup = prod_grp;
        window.fetchStockSubgroups(prod_grp);
    }
}
465
fadedbee

編集:この回答で対処された問題は、angular.jsで解決されました バージョン1.2.7$broadcastは、未登録のスコープでのバブリングを回避し、$ emitと同じ速度で実行されるようになりました。 $broadcast performances are identical to $emit with angular 1.2.16

だから、次のことができる:

  • $broadcastから$rootScopeを使用します
  • $onローカル$scopeを使用してリッスンします

以下の元の回答

$rootScope.$broadcast + $scope.$onではなく、$rootScope.$emit + $rootScope.$onを使用しないことを強くお勧めします。前者は、@ numanによって引き起こされた深刻なパフォーマンスの問題を引き起こす可能性があります。これは、イベントがallスコープを介してバブルダウンするためです。

ただし、後者($rootScope.$emit + $rootScope.$onを使用)はnotの影響を受けるため、高速通信チャネルとして使用できます。

angularの$emitのドキュメントから:

登録されていることを通知するスコープ階層を通じてイベント名を上にディスパッチします

$rootScopeの上にスコープがないため、バブリングは発生しません。 $rootScope.$emit()/$rootScope.$on()をEventBusとして使用することは完全に安全です。

ただし、Controllers内から使用する場合は、1つの落とし穴があります。コントローラー内から$rootScope.$on()に直接バインドする場合、ローカル$scopeが破棄されたときに自分でバインドをクリーンアップする必要があります。これは、コントローラーが(サービスとは対照的に)アプリケーションの存続期間中に複数回インスタンス化されることがあり、その結果、バインドが合計されて最終的にメモリリークが発生するためです:)

登録を解除するには、$scope$destroyイベントでリッスンし、$rootScope.$onによって返された関数を呼び出します。

angular
    .module('MyApp')
    .controller('MyController', ['$scope', '$rootScope', function MyController($scope, $rootScope) {

            var unbind = $rootScope.$on('someComponent.someCrazyEvent', function(){
                console.log('foo');
            });

            $scope.$on('$destroy', unbind);
        }
    ]);

他のEventBus実装にも当てはまるので、それは実際にはangular固有のことではなく、リソースをクリーンアップする必要があるということです。

ただし、これらの場合はcanを使えば簡単です。たとえば、$rootScopeにモンキーパッチを適用し、$onRootScopeで発行されたイベントにサブスクライブする$rootScopeを与えることができますが、ローカル$scopeが破棄されたときにハンドラーを直接クリーンアップすることもできます。

そのような$rootScopeメソッドを提供するために$onRootScopeにモンキーパッチを適用する最もクリーンな方法は、デコレータを使用することです(実行ブロックはおそらく同様にうまくいきますが、pssst、誰にも言わないでください)

$onRootScopeを列挙するときに$scopeプロパティが予期せず表示されないようにするには、Object.defineProperty()を使用し、enumerablefalseに設定します。 ES5シムが必要になる場合があることに注意してください。

angular
    .module('MyApp')
    .config(['$provide', function($provide){
        $provide.decorator('$rootScope', ['$delegate', function($delegate){

            Object.defineProperty($delegate.constructor.prototype, '$onRootScope', {
                value: function(name, listener){
                    var unsubscribe = $delegate.$on(name, listener);
                    this.$on('$destroy', unsubscribe);

                    return unsubscribe;
                },
                enumerable: false
            });


            return $delegate;
        }]);
    }]);

このメソッドを使用すると、上記のコントローラーコードを次のように簡略化できます。

angular
    .module('MyApp')
    .controller('MyController', ['$scope', function MyController($scope) {

            $scope.$onRootScope('someComponent.someCrazyEvent', function(){
                console.log('foo');
            });
        }
    ]);

したがって、これらすべての最終結果として、$rootScope.$emit + $scope.$onRootScopeを使用することを強くお勧めします。

ところで、angularチームにangularコア内の問題に対処するよう説得しようとしています。ここで議論が行われています: https://github.com/angular/angular.js/issues/4574

これは、わずか100個の$broadcastで、適切なシナリオでテーブルに$scopebringが与えるパフォーマンスの影響を示すjsperfです。

http://jsperf.com/rootscope-emit-vs-rootscope-broadcast

jsperf results

454
Christoph

ここでの top answer は、@ zumalifeguardが述べたように(少なくとも> 1.2.16と "おそらく以前の"バージョンでは)もはや存在しないAngular問題からの回避策です。しかし、私は実際の解決策なしにこれらすべての答えを読んでいます。

私には今の答えは

  • $broadcastから$rootScopeを使用する
  • $onローカルの$scopeから取得し、イベントについて知る必要がある

だから公開する

// EXAMPLE PUBLISHER
angular.module('test').controller('CtrlPublish', ['$rootScope', '$scope',
function ($rootScope, $scope) {

  $rootScope.$broadcast('topic', 'message');

}]);

そして購読する

// EXAMPLE SUBSCRIBER
angular.module('test').controller('ctrlSubscribe', ['$scope',
function ($scope) {

  $scope.$on('topic', function (event, arg) { 
    $scope.receiver = 'got your ' + arg;
  });

}]);

プランカー

ローカルの$scopeにリスナーを登録すると、関連するコントローラが削除されたときに、リスナーは $destroy自体によって自動的に破棄される になります。

103
poshest

PubSub通信に $ rootScope。$ broadcast および$ scope。$を使用します。

また、この記事を参照してください: AngularJS - コントローラ間の通信

53

DefinePropertyにはブラウザの互換性の問題があるので、私たちはサービスを使うことを考えることができると思います。

angular.module('myservice', [], function($provide) {
    $provide.factory('msgBus', ['$rootScope', function($rootScope) {
        var msgBus = {};
        msgBus.emitMsg = function(msg) {
        $rootScope.$emit(msg);
        };
        msgBus.onMsg = function(msg, scope, func) {
            var unbind = $rootScope.$on(msg, func);
            scope.$on('$destroy', unbind);
        };
        return msgBus;
    }]);
});

そしてこのようにコントローラでそれを使用します。

  • コントローラー1

    function($scope, msgBus) {
        $scope.sendmsg = function() {
            msgBus.emitMsg('somemsg')
        }
    }
    
  • コントローラー2

    function($scope, msgBus) {
        msgBus.onMsg('somemsg', $scope, function() {
            // your logic
        });
    }
    
42
Singo

GridLinked は、 PubSub の解決策を投稿しました。サービスは ここ で見つけることができます。

また、彼らのサービスの図:

Messaging Service

20
Ryan Schumacher

実際にはEmitとBroadcastを使用するのは効率的ではありません。イベントによってスコープ階層が上下にバブルアップし、複雑なアプリケーションのパフォーマンスが低下する可能性があるためです。

サービスを利用することをお勧めします。ここに私が最近私のプロジェクトの一つにそれを実装した方法があります - https://Gist.github.com/3384419

基本的な考え方 - pubsub /イベントバスをサービスとして登録します。それからイベント/トピックを購読または発行する必要があるところにそのイベントバスを注入します。

15
numan salati

サービス内でgetおよびsetメソッドを使用すると、コントローラ間でメッセージを非常に簡単に受け渡すことができます。

var myApp = angular.module("myApp",[]);

myApp.factory('myFactoryService',function(){


    var data="";

    return{
        setData:function(str){
            data = str;
        },

        getData:function(){
            return data;
        }
    }


})


myApp.controller('FirstController',function($scope,myFactoryService){
    myFactoryService.setData("Im am set in first controller");
});



myApp.controller('SecondController',function($scope,myFactoryService){
    $scope.rslt = myFactoryService.getData();
});

hTML HTMLでは、あなたはこのようにチェックすることができます

<div ng-controller='FirstController'>  
</div>

<div ng-controller='SecondController'>
    {{rslt}}
</div>
14
Load Reconn

元のコードに関して - スコープ間でデータを共有したいようです。 $スコープ間でデータまたは状態のいずれかを共有するには、ドキュメントはサービスの使用を提案します。

  • コントローラ間で共有されるステートレスまたはステートフルコードを実行するには - 代わりに角度付きサービスを使用します。
  • 他のコンポーネントのライフサイクルをインスタンス化または管理するため(たとえば、サービスインスタンスを作成するため)。

参照:Angularドキュメントリンクはこちら

8
pkbyron

私は実際にはPostal.jsをコントローラ間のメッセージバスとして使い始めました。

AMQPスタイルのバインディング、郵便がw/iFrameとWebソケットを統合する方法など、メッセージバスとしての利点はたくさんあります。

Postalを$scope.$bus..に設定するためにデコレータを使用しました。

angular.module('MyApp')  
.config(function ($provide) {
    $provide.decorator('$rootScope', ['$delegate', function ($delegate) {
        Object.defineProperty($delegate.constructor.prototype, '$bus', {
            get: function() {
                var self = this;

                return {
                    subscribe: function() {
                        var sub = postal.subscribe.apply(postal, arguments);

                        self.$on('$destroy',
                        function() {
                            sub.unsubscribe();
                        });
                    },
                    channel: postal.channel,
                    publish: postal.publish
                };
            },
            enumerable: false
        });

        return $delegate;
    }]);
});

これは、トピックに関するブログ投稿へのリンクです...
http://jonathancreamer.com/an-angular-event-bus-with-postal-js/

5
jcreamer898

これは私が Factory/Services そしてsimple dependency injection(DI) でどうやってやるかです。

myApp = angular.module('myApp', [])

# PeopleService holds the "data".
angular.module('myApp').factory 'PeopleService', ()->
  [
    {name: "Jack"}
  ]

# Controller where PeopleService is injected
angular.module('myApp').controller 'PersonFormCtrl', ['$scope','PeopleService', ($scope, PeopleService)->
  $scope.people = PeopleService
  $scope.person = {} 

  $scope.add = (person)->
    # Simply Push some data to service
    PeopleService.Push angular.copy(person)
]

# ... and again consume it in another controller somewhere...
angular.module('myApp').controller 'PeopleListCtrl', ['$scope','PeopleService', ($scope, PeopleService)->
  $scope.people = PeopleService
]
3
Oto Brglez

私はどのように$rootscope.emitが相互通信を達成するために使われたかが好きでした。地球規模のスペースを汚染することなく、クリーンでパフォーマンスに優れたソリューションを提案します。

module.factory("eventBus",function (){
    var obj = {};
    obj.handlers = {};
    obj.registerEvent = function (eventName,handler){
        if(typeof this.handlers[eventName] == 'undefined'){
        this.handlers[eventName] = [];  
    }       
    this.handlers[eventName].Push(handler);
    }
    obj.fireEvent = function (eventName,objData){
       if(this.handlers[eventName]){
           for(var i=0;i<this.handlers[eventName].length;i++){
                this.handlers[eventName][i](objData);
           }

       }
    }
    return obj;
})

//Usage:

//In controller 1 write:
eventBus.registerEvent('fakeEvent',handler)
function handler(data){
      alert(data);
}

//In controller 2 write:
eventBus.fireEvent('fakeEvent','fakeData');
3
shikhar chauhan

これが迅速で汚れた方法です。

// Add $injector as a parameter for your controller

function myAngularController($scope,$injector){

    $scope.sendorders = function(){

       // now you can use $injector to get the 
       // handle of $rootScope and broadcast to all

       $injector.get('$rootScope').$broadcast('sinkallships');

    };

}

これは、兄弟コントローラのいずれかに追加する関数の例です。

$scope.$on('sinkallships', function() {

    alert('Sink that ship!');                       

});

そしてもちろんここにあなたのHTMLがあります:

<button ngclick="sendorders()">Sink Enemy Ships</button>
2
Peter Drinnan

角度1.5から始めて、それはコンポーネントベースの開発フォーカスです。コンポーネントが対話するための推奨される方法は、 'require'プロパティの使用とプロパティバインディング(入力/出力)を介してです。

コンポーネントは別のコンポーネント(たとえばルートコンポーネント)を必要とし、そのコントローラへの参照を取得します。

angular.module('app').component('book', {
    bindings: {},
    require: {api: '^app'},
    template: 'Product page of the book: ES6 - The Essentials',
    controller: controller
});

その後、ルートコンポーネントのメソッドを子コンポーネントで使用できます。

$ctrl.api.addWatchedBook('ES6 - The Essentials');

これは、ルートコンポーネントコントローラの機能です。

function addWatchedBook(bookName){

  booksWatched.Push(bookName);

}

完全なアーキテクチャーの概要は次のとおりです。 コンポーネント通信

1
kevinius

このhello関数にはモジュールのどこからでもアクセスできます

コントローラー1

 $scope.save = function() {
    $scope.hello();
  }

セカンドコントローラー

  $rootScope.hello = function() {
    console.log('hello');
  }

詳細はこちら

0
Prashobh

AngularJSビルトインサービス$rootScopeを使用して、このサービスを両方のコントローラにインジェクトすることができます。これで、$ rootScopeオブジェクトで発生したイベントをリッスンできます。

$ rootScopeは$emit and $broadcastと呼ばれる2つのイベントディスパッチャを提供します。これはイベント(カスタムイベントかもしれません)のディスパッチを担当し、$rootScope.$on関数を使ってイベントリスナを追加します。

0
Shivang Gupta

あなたは、$ emitと$ broadcastの角度イベントを使ってそれを行うことができます。私たちの知る限りでは、これが最も効果的で効果的な方法です。

まず一つのコントローラから関数を呼び出します。

var myApp = angular.module('sample', []);
myApp.controller('firstCtrl', function($scope) {
    $scope.sum = function() {
        $scope.$emit('sumTwoNumber', [1, 2]);
    };
});
myApp.controller('secondCtrl', function($scope) {
    $scope.$on('sumTwoNumber', function(e, data) {
        var sum = 0;
        for (var a = 0; a < data.length; a++) {
            sum = sum + data[a];
        }
        console.log('event working', sum);

    });
});

$ scopeの代わりに$ rootScopeを使用することもできます。それに応じてコントローラを使用してください。

0
Peeyush Kumar
function mySrvc() {
  var callback = function() {

  }
  return {
    onSaveClick: function(fn) {
      callback = fn;
    },
    fireSaveClick: function(data) {
      callback(data);
    }
  }
}

function controllerA($scope, mySrvc) {
  mySrvc.onSaveClick(function(data) {
    console.log(data)
  })
}

function controllerB($scope, mySrvc) {
  mySrvc.fireSaveClick(data);
}
0
Amin Rahimi

$rootscopeはアプリケーション全体からのアクセスであるため、サービスを使用する必要があります。負荷が増加するか、データがそれ以上ない場合はrootparamsを使用します。

0
abhaygarg12493

サービスを作成して通知を使用します。

  1. 通知サービスでメソッドを作成する
  2. Notification Serviceで通知をブロードキャストするための一般的なメソッドを作成します。
  3. ソースコントローラからnotificationService.Methodを呼び出します。必要に応じて永続化するために対応するオブジェクトも渡します。
  4. メソッド内では、通知サービスにデータを保持し、一般的な通知メソッドを呼び出します。
  5. 送信先コントローラで、ブロードキャストイベントをリスンし($ scope.on)、Notification Serviceからのデータにアクセスします。

どの時点においても通知サービスはシングルトンであるため、永続化されたデータを提供することができるはずです。

お役に立てれば

0
rahul