コントローラ間で通信する正しい方法は何ですか?
私は現在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);
}
}
編集:この回答で対処された問題は、angular.jsで解決されました バージョン1.2.7 。 $broadcast
は、未登録のスコープでのバブリングを回避し、$ emitと同じ速度で実行されるようになりました。
だから、次のことができる:
$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()
を使用し、enumerable
をfalse
に設定します。 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
で、適切なシナリオでテーブルに$scope
bringが与えるパフォーマンスの影響を示すjsperfです。
ここでの 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;
});
}]);
プランカー
Controller As
構文ローカルの$scope
にリスナーを登録すると、関連するコントローラが削除されたときに、リスナーは $destroy
自体によって自動的に破棄される になります。
PubSub通信に $ rootScope。$ broadcast および$ scope。$を使用します。
また、この記事を参照してください: AngularJS - コントローラ間の通信
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
});
}
GridLinked は、 PubSub の解決策を投稿しました。サービスは ここ で見つけることができます。
また、彼らのサービスの図:
実際にはEmitとBroadcastを使用するのは効率的ではありません。イベントによってスコープ階層が上下にバブルアップし、複雑なアプリケーションのパフォーマンスが低下する可能性があるためです。
サービスを利用することをお勧めします。ここに私が最近私のプロジェクトの一つにそれを実装した方法があります - https://Gist.github.com/3384419 。
基本的な考え方 - pubsub /イベントバスをサービスとして登録します。それからイベント/トピックを購読または発行する必要があるところにそのイベントバスを注入します。
サービス内で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>
元のコードに関して - スコープ間でデータを共有したいようです。 $スコープ間でデータまたは状態のいずれかを共有するには、ドキュメントはサービスの使用を提案します。
私は実際には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/
これは私が 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
]
私はどのように$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');
これが迅速で汚れた方法です。
// 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>
角度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);
}
完全なアーキテクチャーの概要は次のとおりです。 コンポーネント通信
このhello関数にはモジュールのどこからでもアクセスできます
コントローラー1
$scope.save = function() {
$scope.hello();
}
セカンドコントローラー
$rootScope.hello = function() {
console.log('hello');
}
AngularJSビルトインサービス$rootScope
を使用して、このサービスを両方のコントローラにインジェクトすることができます。これで、$ rootScopeオブジェクトで発生したイベントをリッスンできます。
$ rootScopeは$emit and $broadcast
と呼ばれる2つのイベントディスパッチャを提供します。これはイベント(カスタムイベントかもしれません)のディスパッチを担当し、$rootScope.$on
関数を使ってイベントリスナを追加します。
あなたは、$ 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を使用することもできます。それに応じてコントローラを使用してください。
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);
}
$rootscope
はアプリケーション全体からのアクセスであるため、サービスを使用する必要があります。負荷が増加するか、データがそれ以上ない場合はrootparamsを使用します。
サービスを作成して通知を使用します。
どの時点においても通知サービスはシングルトンであるため、永続化されたデータを提供することができるはずです。
お役に立てれば