Wie kommuniziert der Controller richtig?
Ich verwende derzeit einen schrecklichen Fudge mit 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);
}
}
Edit: Das in dieser Antwort angesprochene Problem wurde in angle.js Version 1.2.7 behoben. $broadcast
vermeidet das Sprudeln über nicht registrierte Bereiche und läuft genauso schnell wie $ emit .
So, jetzt können Sie:
$broadcast
aus dem $rootScope
$on
von dem lokalen $scope
, der über das Ereignis Bescheid wissen mussOriginalantwort unten
Ich empfehle dringend, $rootScope.$broadcast
+ $scope.$on
nicht zu verwenden, sondern $rootScope.$emit
+ $rootScope.$on
. Ersteres kann zu ernsthaften Leistungsproblemen führen, die von @numan aufgeworfen werden. Das liegt daran, dass das Ereignis durch alle Bereiche sprudelt.
Letzteres (mit $rootScope.$emit
+ $rootScope.$on
) leidet jedoch an not und kann daher als schneller Kommunikationskanal verwendet werden!
Aus der Winkeldokumentation von $emit
:
Sendet einen Ereignisnamen durch die Bereichshierarchie nach oben und benachrichtigt die registrierten
Da es keinen Bereich oberhalb von $rootScope
gibt, gibt es kein Sprudeln. Es ist absolut sicher, $rootScope.$emit()
/$rootScope.$on()
als EventBus zu verwenden.
Es gibt jedoch ein Problem bei der Verwendung von Controllern. Wenn Sie direkt von einem Controller aus an $rootScope.$on()
binden, müssen Sie die Bindung selbst bereinigen, wenn Ihr lokaler $scope
zerstört wird. Dies liegt daran, dass Controller (im Gegensatz zu Diensten) während der gesamten Lebensdauer einer Anwendung mehrmals instanziiert werden können, was zu Bindungen führen würde, die schließlich zu Speicherlecks führen.
Hören Sie zum Aufheben der Registrierung einfach das $scope
-Ereignis von $destroy
und rufen Sie die von $rootScope.$on
zurückgegebene Funktion auf.
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);
}
]);
Ich würde sagen, das ist nicht wirklich eine winkelspezifische Sache, da es auch für andere EventBus-Implementierungen gilt, dass Sie Ressourcen bereinigen müssen.
Allerdings machen Sie can Ihr Leben für diese Fälle einfacher. Zum Beispiel könnten Sie den Patch $rootScope
mit einem Affenflecken versehen und ihm einen $onRootScope
geben, der Ereignisse abonniert, die auf dem $rootScope
ausgegeben werden, aber auch den Handler direkt bereinigt, wenn der lokale $scope
zerstört wird.
Der sauberste Weg, um den $rootScope
für die Bereitstellung einer solchen $onRootScope
-Methode zu verwenden, würde durch einen Dekorierer erfolgen (ein Laufblock wird es wahrscheinlich auch gut machen, aber pssst, sag es niemandem)
Um sicherzustellen, dass die $onRootScope
-Eigenschaft beim Auflisten über $scope
nicht unerwartet angezeigt wird, verwenden wir Object.defineProperty()
und setzen enumerable
auf false
. Denken Sie daran, dass Sie möglicherweise eine ES5-Scheibe benötigen.
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;
}]);
}]);
Mit dieser Methode kann der Controller-Code von oben vereinfacht werden:
angular
.module('MyApp')
.controller('MyController', ['$scope', function MyController($scope) {
$scope.$onRootScope('someComponent.someCrazyEvent', function(){
console.log('foo');
});
}
]);
Als letztes Ergebnis empfehle ich Ihnen dringend, $rootScope.$emit
+ $scope.$onRootScope
zu verwenden.
Übrigens, ich versuche das Winkelteam davon zu überzeugen, das Problem im Winkelkern zu lösen. Hier gibt es eine Diskussion: https://github.com/angular/angular.js/issues/4574
Hier ist eine Jsperf, die zeigt, wie viel Leistung $broadcast
in einem anständigen Szenario mit nur 100 $scope
s zur Tabelle bringt.
http://jsperf.com/rootscope-emit-vs-rootscope-broadcast
Das oberste Antwort war eine Umgehung eines Angular-Problems, das nicht mehr existiert (zumindest in Versionen> 1.2.16 und "wahrscheinlich früher"), wie @zumalifeguard erwähnt hat. Aber ich lese alle diese Antworten ohne eine wirkliche Lösung.
Es scheint mir, dass die Antwort jetzt sein sollte
$broadcast
aus dem $rootScope
$on
vom lokalen $scope
, der über das Ereignis Bescheid wissen mussAlso zu veröffentlichen
// EXAMPLE PUBLISHER
angular.module('test').controller('CtrlPublish', ['$rootScope', '$scope',
function ($rootScope, $scope) {
$rootScope.$broadcast('topic', 'message');
}]);
Und abonnieren
// EXAMPLE SUBSCRIBER
angular.module('test').controller('ctrlSubscribe', ['$scope',
function ($scope) {
$scope.$on('topic', function (event, arg) {
$scope.receiver = 'got your ' + arg;
});
}]);
Plunker
Controller As
-SyntaxWenn Sie den Listener im lokalen $scope
registrieren, wird er wird automatisch von $destroy
selbst zerstört } gelöscht, wenn der zugehörige Controller entfernt wird.
Verwenden Sie $ rootScope. $ Broadcast und $ scope. $ On für eine PubSub-Kommunikation.
Siehe auch diesen Beitrag: AngularJS - Kommunikation zwischen Controllern
Da defineProperty ein Problem mit der Browserkompatibilität aufweist, können wir über die Verwendung eines Dienstes nachdenken.
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;
}]);
});
und verwende es wie folgt im Controller:
controller 1
function($scope, msgBus) {
$scope.sendmsg = function() {
msgBus.emitMsg('somemsg')
}
}
controller 2
function($scope, msgBus) {
msgBus.onMsg('somemsg', $scope, function() {
// your logic
});
}
GridLinked hat eine PubSub -Lösung veröffentlicht, die ziemlich gut zu sein scheint. Der Dienst ist hier zu finden.
Auch ein Diagramm ihres Dienstes:
Die Verwendung von Emit und Broadcast ist eigentlich ineffizient, da das Ereignis in der Hierarchie des Bereichs nach oben und unten sprudelt, was bei einer komplexen Anwendung leicht zu einer Leistungsabsenkung führen kann.
Ich würde vorschlagen, einen Dienst zu nutzen. So habe ich es kürzlich in einem meiner Projekte implementiert - https://Gist.github.com/3384419 .
Grundidee - Registrieren Sie einen Pubsub/Event-Bus als Dienst. Fügen Sie diesen Eventbus dann an der Stelle ein, an der Sie Ereignisse/Themen abonnieren oder veröffentlichen müssen.
Wenn Sie innerhalb eines Dienstes get- und set-Methoden verwenden, können Sie Nachrichten sehr einfach zwischen Steuerungen weiterleiten.
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();
});
in HTML HTML können Sie dies überprüfen
<div ng-controller='FirstController'>
</div>
<div ng-controller='SecondController'>
{{rslt}}
</div>
Bezüglich des ursprünglichen Codes - es scheint, dass Sie Daten zwischen Bereichen austauschen möchten. Um Daten oder Status zwischen dem $ -Zonenbereich freizugeben, schlagen die Dokumente die Verwendung eines Dienstes vor:
Ich habe tatsächlich angefangen, Postal.js als Nachrichtenbus zwischen Controllern zu verwenden.
Der Message-Bus bietet viele Vorteile, wie z. B. AMQP-Style-Bindungen, die Integration von Post mit iFrames und Web-Sockets und vieles mehr.
Ich habe einen Dekorateur verwendet, um Postal auf $scope.$bus
... einzurichten.
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;
}]);
});
Hier ist ein Link zu einem Blogbeitrag zum Thema ...
http://jonathancreamer.com/an-angular-event-bus-with-postal-js/
So mache ich das mit Factory/Services und 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
]
Ich mochte die Art und Weise, wie $rootscope.emit
für die Kommunikation untereinander verwendet wurde. Ich empfehle die saubere und leistungsfähige Lösung, ohne den Weltraum zu verschmutzen.
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');
Hier ist der schnelle und schmutzige Weg.
// 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');
};
}
Hier ist eine Beispielfunktion, die Sie in einem beliebigen Controller hinzufügen können
$scope.$on('sinkallships', function() {
alert('Sink that ship!');
});
und hier ist natürlich dein HTML:
<button ngclick="sendorders()">Sink Enemy Ships</button>
Auf diese Hallo-Funktion können Sie überall im Modul zugreifen
Controller eins
$scope.save = function() {
$scope.hello();
}
zweiter Controller
$rootScope.hello = function() {
console.log('hello');
}
Beginnend mit Winkel 1.5 und dem komponentenbasierten Entwicklungsfokus. Die empfohlene Methode für die Interaktion von Komponenten ist die Verwendung der Eigenschaft 'Request' und der Eigenschaftsbindung (Eingabe/Ausgabe).
Eine Komponente würde eine andere Komponente (zum Beispiel die Wurzelkomponente) erfordern und einen Verweis auf ihren Controller erhalten:
angular.module('app').component('book', {
bindings: {},
require: {api: '^app'},
template: 'Product page of the book: ES6 - The Essentials',
controller: controller
});
Sie können dann die Methoden der Stammkomponente in Ihrer untergeordneten Komponente verwenden:
$ctrl.api.addWatchedBook('ES6 - The Essentials');
Dies ist die Hauptkomponenten-Controller-Funktion:
function addWatchedBook(bookName){
booksWatched.Push(bookName);
}
Hier ist eine vollständige architektonische Übersicht: Komponentenkommunikation
Sie können den eingebauten AngularJS-Dienst $rootScope
verwenden und diesen Dienst in Ihre beiden Controller einbinden. __ Sie können dann Ereignisse überwachen, die für das $ rootScope-Objekt ausgelöst werden.
$ rootScope bietet zwei Ereignis-Dispatcher mit dem Namen $emit and $broadcast
, die für das Auslösen von Ereignissen verantwortlich sind (möglicherweise benutzerdefinierte Ereignisse), und verwenden die Funktion $rootScope.$on
, um den Ereignislistener hinzuzufügen.
Sie können dies tun, indem Sie winklige Ereignisse verwenden, die $ emit und $ broadcast sind. Nach unserem Wissen ist dies der beste, effizienteste und effektivste Weg.
Zuerst rufen wir eine Funktion von einem Controller aus auf.
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);
});
});
Sie können $ rootScope auch anstelle von $ scope verwenden. Verwenden Sie Ihren Controller entsprechend.
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);
}
Sie sollten den Dienst verwenden, da $rootscope
Zugriff von der gesamten Anwendung aus ist und die Auslastung erhöht, oder Sie können die rootparams verwenden, wenn Ihre Daten nicht mehr sind.
Ich werde einen Dienst erstellen und eine Benachrichtigung verwenden.
Da der Notification Service zu jedem Zeitpunkt Singleton ist, sollte er in der Lage sein, persistente Daten bereitzustellen.
Hoffe das hilft