I have a simple question: what's the best ('cleanest', 'scaleable') path one should go when it comes to interact between (let's say) two controllers. Would that be to define a service and watch that service's return-value in order to react?
I setup a simple example here, where I watch the service's current value:
$scope.$watch(
function() {
return myService.getValue();
},
function(newVal) {
$scope.value1 = newVal;
});
and update that service's value when one of the buttons is clicked.
Can this be done better, smaller, cleaner somehow? What's the best practice here?
Cheers.
Use service to share data between controllers
Your case is trying to share data between controllers, rather than watch service's value in controllers, I think directly reference service object to controller's scope is a better way
So your view can be
<pre ng-controller="cntrl1">Value in cntrl1: {{ myService.value }} <button ng-click="update('value1')">Change to 'value1'</button></pre>
<pre ng-controller="cntrl2">Value in cntrl2: {{ myService.value }} <button ng-click="update('value2')">Change to 'value2'</button></pre>
and change your controllers to
app.controller('cntrl1', function(myService, $scope) {
$scope.myService = myService;
$scope.update = function(str) {
$scope.myService.setValue(str);
}
});
app.controller('cntrl2', function(myService, $scope) {
$scope.myService = myService;
$scope.update = function(str) {
$scope.myService.setValue(str);
}
});
Use $broadcast/$emit
Just as #squiroid points out, you can use $broadcast to broadcast events to any controllers who is monitoring targeted events.
Please note here, you'd better not use $rootScope.$broadcast + $scope.$on but rather $rootScope.$emit+ $rootScope.$onas $broadcast event will bubble down through all descendant scopes, which might lead to serious performance problems.
This is the best way to communicate b/w the controller sharing same data via sevice but it is limited b/w controllers having the same service:-
Instead you can also choose to broadcast events that are captured by other controllers and change that data accordingly this way is more scaleable but not clean :-)
Sender ctrl :-
$rootScope.$broadcast('update', 'Some data'); //method that lets pretty much everything hear it even $scope too.
or
$rootScope.$emit('update', 'Some data');// only lets other $rootScope listeners catch it
Listen Ctrl :-
$rootScope.$on('update', function (event, data) {
console.log(data); // 'Some data'
});
Related
I have a factory that needs to listen for a broadcast event. I injected $scope into the factory so I could use $scope.$on. But as soon as I add $scope to the parameter list I get an injector error.
This works fine:
angular.module('MyWebApp.services')
.factory('ValidationMatrixFactory', ['$rootScope', function($rootScope) {
var ValidationMatrixFactory = {};
return ValidationMatrixFactory;
}]);
This throws an injector error:
angular.module('MyWebApp.services')
.factory('ValidationMatrixFactory', ['$scope', '$rootScope', function($scope, $rootScope) {
var ValidationMatrixFactory = {};
return ValidationMatrixFactory;
}]);
Why can't I inject $scope into a factory? And if I can't, do I have any way of listening for events other than using $rootScope?
Because $scope is used for connecting controllers to view, factories are not really meant to use $scope.
How ever you can broadcast to rootScope.
$rootScope.$on()
Even though you can't use $scope in services, you can use the service as a 'store'. I use the following approach inspired on AltJS / Redux while developing apps on ReactJS.
I have a Controller with a scope which the view is bound to. That controller has a $scope.state variable that gets its value from a Service which has this.state = {}. The service is the only component "allowed" (by you, the developer, this a rule we should follow ourselves) to touch the 'state'.
An example could make this point a bit more clear
(function () {
'use strict';
angular.module('app', ['app.accounts']);
// my module...
// it can be defined in a separate file like `app.accounts.module.js`
angular.module('app.accounts', []);
angular.module('app.accounts')
.service('AccountsSrv', [function () {
var self = this;
self.state = {
user: false
};
self.getAccountInfo = function(){
var userData = {name: 'John'}; // here you can get the user data from an endpoint
self.state.user = userData; // update the state once you got the data
};
}]);
// my controller, bound to the state of the service
// it can be defined in a separate file like `app.accounts.controller.js`
angular.module('app.accounts')
.controller('AccountsCtrl', ['$scope', 'AccountsSrv', function ($scope, AccountsSrv) {
$scope.state = AccountsSrv.state;
$scope.getAccountInfo = function(){
// ... do some logic here
// ... and then call the service which will
AccountsSrv.getAccountInfo();
}
}]);
})();
<script src="https://code.angularjs.org/1.3.15/angular.min.js"></script>
<div ng-app="app">
<div ng-controller="AccountsCtrl">
Username: {{state.user.name ? state.user.name : 'user info not available yet. Click below...'}}<br/><br/>
Get account info
</div>
</div>
The benefit of this approach is you don't have to set $watch or $on on multiple places, or tediously call $scope.$apply(function(){ /* update state here */ }) every time you need to update the controller's state. Also, you can have multiple controllers talk to services, since the relationship between components and services is one controller can talk to one or many services, the decision is yours. This approach focus on keeping a single source of truth.
I've used this approach on large scale apps... it has worked like a charm.
I hope it helps clarify a bit about where to keep the state and how to update it.
Following situation:
There are two directives with controllers (A & B) - which are both children of another controller (C).
Controller A manages stuff for its model.
Now Controller C needs to call functions from Controller A to modify some stuff. And furthermore it needs access to some properties and read them.
I'm uncertain on what the right approach to communication is. And wheter to stick to one approach.
Following up is a small code example to illustrate the problem in a more concrete way.
First off there is a provider where components can register themselfes.
angular.module('components', [])
.provider('db', function(){
this.registerComponent = function(name, component){
...
}
});
Now there is a directive & a Controller (A) which displays concrete components.
angular.module('components')
.directive('componentDashboard', function(){
return {
scope:{
concreteComponents: '='
},
controller: function($scope){
$scope.model = concreteComponents;
$scope.model.someImportantProp = "foo";
$scope.addComponent = function(c){...}
}
}
})
That is basically the setup. The directive componentDashboard can display a set of components which registered to the db provider.
There are controller functions (A) like addComponent which needs to be called from outside of the controller (Controller B wants to call this). Furthermore Controller B wants to access different properties and so on.
Whats the preferred way of doing this?
At the moment there are these paradigmas used:
1) Factory hack ?! Basically there is a factory with some functions:
angular.module('components')
.factory('componentStub', function($log){
return {
addComponent : function(c){
$log.error("stub not overwritten");
}
}
})
These functions are now overwritten by the component's directive:
angular.module('components')
.directive('componentDashboard', function(componentStub){
return {
scope:{
concreteComponents: '='
},
controller: function($scope){
$scope.model = concreteComponents;
$scope.model.someImportantProp = "foo";
$scope.addComponent = function(c){...}
componentStub.addComponent = function(c){
$scope.addComponent(c);
}
}
}
})
2) event based
angular.module('components')
.factory('notificationCenter', function(){
return {
registerToNotification: function(id, not, cb){..}
}
})
.directive('componentDashboard', function(notificationCenter){
return {
scope:{
concreteComponents: '='
},
controller: function($scope){
$scope.model = concreteComponents;
$scope.model.someImportantProp = "foo";
$scope.addComponent = function(c){...}
notificationCenter.registerToNotification("foo", "doAddComponent", function(c){
$scope.addComponent(c)
}
}
}
})
At the moment there are both approaches used. There are some advantages of using this. Its developed fast, there are just few depencencies or restrictions to it. And it works.
But! I'm unsure about whether it is a good approach. I'm experiencing kinda bad maintenance on that and the more complex it gets(lets say some components can be added and some not -> states) the more it feels like not the right approach.
How should this problem be approached ?
Sorry for the long question, thanks in advice
Schemii
There are multiple methods how you can solve this. Ensuring that it is maintainable comes down to isolation: let the different components work on their own without dependencies of each other.
Combine any of the following tactics (from easiest to harder to implement):
Let the parent view pass the model (or parts of it) to child directives (via attributes)
$watch for changes in the main controller/directive to react on changes.
Let the parent controller/directive register ($scope.$on) callbacks to certain events. Childs can $emit events upwards to their parents. Parents can $broadcast events downwards to their children.
Let the child directive expose a callback/expression (see the & prefix in the isolate scope options)
Share a (singleton) service instance that will handle model changes. Inject this service where needed. Since this is a singleton, you'll have to make sure that you'll cleanup callbacks whenever a scope is destroyed otherwise you'll leak memory.
Hope this helps.
Cheers.
I am using socket.io to enable chat in my app and i am using a service SocketService to perform all the socket stuff. When a message came then i want to trigger a function of a controller from the service SocketService to make some changes in the UI.
So i want to know that how can i access the function of a controller from the service.
Sample Code:
.service('SocketService', function ($http,$rootScope,$q) {
this.connect = function(){
var socket = io();
socket.on('connect',function(){
// Call a function named 'someFunction' in controller 'ChatController'
});
}
});
This is the sample code for service.
Now the code for controller
.controller('ChatController',function('SocketService',$scope){
$scope.someFunction = function(){
// Some Code Here
}
});
You could achieve this by using angular events $broadcast or $emit.
In your case $broadcast would be helpful,
You need to broadcast your event in $rootscope that can be listen by all the child scopes which has $on with same event name.
CODE
.service('SocketService', function($http, $rootScope, $q) {
this.connect = function() {
var socket = io();
socket.on('connect', function() {
// Call a function named 'someFunction' in controller 'ChatController'
$rootScope.$broadcast('eventFired', {
data: 'something'
});
});
}
});
.controller('ChatController', function('SocketService', $scope) {
$scope.someFunction = function() {
// Some Code Here
}
$scope.$on('eventFired', function(event, data) {
$scope.someFunction();
})
});
Hope this could help you, Thanks.
I know this is an old question, but I have another option. I have a personal bias against $broadcast - it just doesn't feel very 'angularish', I prefer making explicit calls in my code.
So instead of broadcasting to the controller and triggering another digest cycle, I prefer to have the controller register itself to the service, as below. Just be careful not to introduce any circular dependencies if the controller makes use of the same service. This works best with the controllerAs syntax, so that the calling service does not need to care about $scope.
Yes, this is more code than $broadcast, but it does give the service total access to the entire controller - all of it's methods and properties.
.service('SocketService', function ($http,$rootScope,$q) {
var _this = this;
this.chatController = null;
this.registerCtrlr = function (ctrlr) {
_this.chatController = ctrlr;
};
this.unRegisterCtrlr = function () {
_this.chatController = null;
};
this.connect = function(){
var socket = io();
socket.on('connect',function(){
// Call chatController.someFunction if chatController exists
if (_this.chatController) {
_this.chatController.someFunction();
}
});
};
});
.controller('ChatController',['SocketService', '$scope', function(SocketService, $scope){
SocketService.registerCtrlr(this);
//-- make sure controller unregisters itself when destroyed - need $scope for this
$scope.$on('$destroy', function () {
SocketService.unRegisterCtrlr();
});
this.someFunction = function(){
// Some Code Here
}
}]);
I realize this post is old but I'd like to give my two cents after dealing with Angular JS for several years. I personally would reconsider this approach. Ideally with AngularJS you'd modify your controller/directive to facilitate transferring data to the view model and ultimately bind an HTML template to what I call "the user friendly" view model. This view model should simply reflect what you want the user to see and when in general. Using this method the moment connect event happens your view model which should be bound to the service's data will reflect changes to the data the moment the data arrives.
I have two controllers and want to notify one controller that some event has happened from the other controller. For this I am trying to use angular broadcast but have been unsuccessful. Please find below my code:
firstController.js
$rootScope.$on('xyz',function(){
alert('Called');
});
secondController.js
$rootScope.$broadcast('xyz');
Can someone please help in identifying what I am missing here?
Use either a combination of:
$rootScope.$broadcast();
$scope.$on();
// or
$rootScope.$emit();
$rootScope.$on();
$broadcast dispatches the event downward to all child scopes, so you can listen to it with the $scope service.
However, $emit dispatches upward through the scope hierarchy, and since $rootScope is the at the highest level, you can use $rootScope to dispatch and listen to the same event. This is also much better in regards to performance since the event doesn't propagate down through multiple scopes.
Please have a look here: jsfiddle
var app = angular.module('app', []);
app.controller('c1', function($rootScope, $scope){
$scope.click = function() {
$rootScope.$broadcast('xyz');
}
});
app.controller('c2', function($scope){
$scope.$on('xyz', function(){
alert("clicked");
});
})
in firstController.js use $scope instead $rootScope
$scope.$on('xyz', function(event, args) {
alert('Called')
});
I have been working with the excelent ngStorage plugin for angular.
When setting it up you can declare a $scope-node connected to the localstorage like this:
$scope.$store = $localStorage;
$scope.$store is now accessible in all controllers etc.
I want to remove some stuff from localstorage and access it using broadcast instead.
In my init I performed:
$scope.taskarr = [];
$rootScope.$broadcast('taskarrbroad',$scope.taskarr);
What is required in order to add, remove and $watch this array, none of the mentioned seem to work.
Here, nothing happens
controller('textController', function($scope,$routeParams){
$scope.$watch('taskarrbroad.length', function(){
console.log($scope.taskarr.map(function(task){
return task.content;
}).join('\n'));
})
})
Here I can access $scope.taskarr and update it, but the view isn't updated. $scope.$apply() didn't help either (the timeout is because it's already within a digest.
controller('stateSwitchController', function($scope, $routeParams, $timeout){
$scope.taskarr = $scope.$store[$routeParams.state].taskarr || [];
console.log($scope.taskarr);
$timeout(function() {
$scope.$apply();
})
}).
$broadcast is a way to send events to other parts of your application. When you broadcast an event, someone else has to listen to that even with $on(). Something like:
// Some controller
$rootScope.$broadcast('my-event', eventData);
// Some other controller
$scope.$on('my-event', function() {
console.log('my-event fired!')
});
$watch is something else, it's not an event listener per se, it's a way to attach a function that gets called when that value changes, and that value has to be on the scope. So your watch should look like this:
$scope.$watch('taskarr.length', function(){
});
Since you've named the array taskarr on the scope.