Call controller function from service in angularjs - angularjs

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.

Related

Angularjs: $scope.emit is not working

I am a newbie at angularjs and i am creating a web application to earn experience and practice. The problem i have is that $scope.$emit does not seem to be working, i am looking for ways to contact functions between controllers and so far i have found on the internet that $scope.emit and $scope.on seems to fit for this kind of task, if there is another way, i would like to know, anyways the code are written like this:
loginController.js
(function(angular)
{
var app = angular.module('Organizer');
// inject $scope to the controller in order to try $scope.$emit
app.controller('login', function($http, $scope)
{
// i define the scope like this so i can access inside other functions
var scope = this;
scope.processing = false;
scope.message = null;
scope.submit = function()
{
scope.processing = true;
// store data for post
var post = {
username: scope.username,
password: scope.password
};
// send post data
$http.post('api/default/login', post).
success(function(data, status)
{
// hide processing feedback and show the result
scope.processing = false;
scope.message = data.message;
}).
error(function(data, status)
{
scope.processing = false;
});
};
// Function i use to emit
this.closeDialog = function()
{
$scope.$emit('closeDialog');
};
});
})(angular);
siteController.js
(function(angular)
{
var app = angular.module('Organizer');
app.controller('site', function($mdDialog, $scope)
{
this.menu = ['test1', 'test2'];
this.dialog = function()
{
$mdDialog.show({
templateUrl: 'site/login',
});
};
// this does not seem to be working
$scope.$on('closeDialog', function(event)
{
console.log('close dialog');
});
});
})(angular);
Note: i am using angular material and you can see i am showing a dialog which is a login, the login has its controller (i wanted it to use the same site controller, but i don't know how) and this dialog has a button which calls the function closeDialog() in loginControler and should close the dialog, but for now for testing reasons i am just logging if it's calling the event
The $emit function propagate an event only to the scopes parents.
The $broadcast function propagate an event to the scopes childs.
So what you need depends on how the controllers are use it...
If you want an event to reach all the app you have to use the $rootScope:
$rootScope.$broadcast('myEvent');
Here you have the doc of the scope, include $emit and $broadcast
You could not emit or broadcast in dialog controller because dialog in angular material has isolated scope. Because of that, when you emit or broadcast an event, it does not go anywhere. The $emit and $broadcast only works when you have scope hierarchy. $emit propagate event up the hierarchy and $broadcast propagate event down the hierarchy.

How to call controller method from service in Angular?

I wonder if I can call controller method from service.
I know that Service is singleton and I can't inject $scope to the service.
In my case I manage Google Maps in service and want to open modal Dialog when user right clicks on Polygon.
As I know, to open/create new instance of dialog, somehow Service must notify controller to do that.
This is a template with controller + method and service: Template
var myApp = angular.module('myApp', []);
function MyCtrl($scope, gridService, $timeout) {
// how to call "foo" method from service?
$scope.foo = function(){
alert('called from service');
};
}
myApp.service('gridService', ['$timeout', function ( $timeout) {
var grid = {
fetching: false,
pristine: true,
pageType: 'Edit'
}
return {
gridSetup: function () {
return grid;
},
setGridSetup: function (newGrid) {
}
}
}]);
Thanks,
The answer is simple: you don't.
The service exists to manipulate data, nothing else. And it really shouldn't care "why" it's doing what it's doing. You ask the service to do something and wait for a response.
Personally I prefer using promises to resolve async operations (i.e. to notify the controller about a state change) since it's heavily supported by many angular services like $http.
But feel free to use callbacks of you wish.
Usually you do not need to call controller from the service - in general the single service could be used by any controller so service shall know nothing about them. In most cases controller calls to the service in react to some user's action and then you need to update view from controller somehow when service did its work (get response from server, etc.). I see following general ways how to do it.
1. Use callbacks.
//controller
$scope.onButtonClick = function() {
$scope.label = "wait, loading...";
function onSuccess(result) {
$scope.label = "done! result is " + result;
}
myService.doSomeWork(param1, param2, onSuccess);
}
//service
doSomeWork: function(param1, param2, onSuccess) {
$.get({...}, onSuccess);
}
So you provide a callback for each action.
2. Subscribe on events
You may use jQuery for events subscribing/triggering
//controller
$(myService).on('update', function() {
$scope.lastUpdateTime = new Date();
});
$scope.onButtonClick = function() {
myService.doUpdate();
}
//service
doUpdate: function() {
$.get({...}, function onOk() {
$(this).trigger('update');
});
}
3. Use promises
A lot of built-in angular services return promise objects, you may use them too:
//controller
myService.doSomething(param1).then(function(result) {
$scope.result = result;
});
//service
doSomething: function(param1) {
return $http.get({...});
}
4. Share some data
An example is $resource service - for example when you call query method it returns empty array-like object that could be safely put to scope and then fills it with values when http request is done.
//controller
$scope.options = myService.options;
$scope.onClick = function() { myService.update() }
//service
options: [],
update: function() {
var self = this;
$http.get({...}).success(function(res) {
self.options.splice(0, self.options.length); //to keep same array
self.options.push.apply(self.options, res.data.options);
});
}
In all these cases services and controllers are separated, services could be used with any controller and you may easily write unit-tests on services that will not break if you change your controller/view part somehow.
A possible solution would be to have a dialog service which you can inject into the grid service. So when the user right clicks on the polygon the handler would call open on the dialog service.
Take a look at the modal service on angular ui as an example.

How to make sure $http.get in service completes before directive is initialized with data?

How do I make it such that all the data "MyService" needs to retrieve is retrieved before my 'control' directive is created or some function to actually add those items is called? If not, is there some other recommended way involving controllers, etc. Have yet to see a basic example of similar sequence. Note that data may have more values added to it via other functions, in which a method would have to be called explicitly for those new data elements.
Service:
// var app = angular.module...
app.service('MyService', function($http) {
this.data = [];
// Called once to first initalize the data
$http.get('data.json').then(function data(response) {
this.data.push(response);
}
})
Directive (with its own isolate controller? or point to the main app controller)?:
myDirective.directive('control', ['Params', 'MyService', function(Params, MyService) {
// Call on a method "addData(MyService.data) when the data is actually loaded
}]);
I don't think there is any way to stop directive form initiating and wait for a async function call of a service. However, several alternatives here:
1. Use ng-route and set up a resolve with $routeProvider.
API: http://docs.angularjs.org/api/ngRoute.$routeProvider
Basically, you return a promise in a route's resolver, the route's view will wait for that promise to resolve. After resolve, it will load the view and initiate whatever controller or directive within the view.
2. $watch the data property of MyService.
When data changes, do whatever you want
$scope.$watch(function() {
return MyService.data;
}, function(newVal, oldVal) {
// do something
}, true);
3. Trigger a ready event on MyService, listen to MyService in your directive.
This requires you to include some event library such as 'EventEmitter`, and mix it into MyService.
4. $broadcast ready event on $rootScope from MyService, and listen to it in your directive.
app.service('MyService', function($http, $rootScope) {
this.data = [];
var _this = this;
// Called once to first initalize the data
$http.get('data.json').then(function data(response) {
_this.data.push(response);
$rootScope.$broadcast('MyServiceReady');
}
})
myDirective.directive('control', ['Params', 'MyService', function(Params, MyService) {
return function(scope) {
scope.$on('MyServiceReady', function() {
// do something
});
};
}]);

How do i use $on in a service in angular?

i have been able to get controller to use the $on listener
with $scope.$on.
but i don't see any documentation on how to get services to listen for events.
I tried $rootScope.$on, but that only allows one listener. i want listeners in multiple services regardless of whether their parent controllers are in scope or not.
after experimenting a fair bit it turns out that getting events to the service can be done with minimal code.
sample service code follows in case anyone else runs into this.
The sample saves and restores the service model to local storage when it gets the respective broadcasts
app.factory('userService', ['$rootScope', function ($rootScope) {
var service = {
model: {
name: '',
email: ''
},
SaveState: function () {
sessionStorage.userService = angular.toJson(service.model);
},
RestoreState: function () {
service.model = angular.fromJson(sessionStorage.userService);
}
}
$rootScope.$on("savestate", service.SaveState);
$rootScope.$on("restorestate", service.RestoreState);
return service;
}]);
Since $on is a scope method, you could create a scope in your service, then listen for events on it:
app.factory('myService', function($rootScope) {
var scope = $rootScope.$new(); // or $new(true) if you want an isolate scope
scope.$on('testEvent', function() {
console.log('event received');
})
return {}
});
function MyCtrl($scope, myService, $rootScope) {
$rootScope.$broadcast('testEvent');
}
fiddle
However, I would not recommend this approach, since scopes are not normally associated with services.

Communication impossible between controllers

PRELIMINARIES
I am developing a web app using angularjs. At some point, my main controller connects to a web service which sends data continuously. To capture and process the stream I am using (http://ajaxpatterns.org/HTTP_Streaming). Everything works like a charm. I would like to share these streaming data with another controller that will process and display them via a jquery chart library (not yet decided which one I gonna use but it is out of the scope of this question). To share these data I have followed this jsfiddle (http://jsfiddle.net/eshepelyuk/vhKfq/).
Please find below some relevant parts of my code.
Module, routes and service definitions:
var platform = angular.module('platform', ['ui']);
platform.config(['$routeProvider',function($routeProvider){
$routeProvider.
when('/home',{templateUrl:'partials/home.html',controller:PlatformCtrl}).
when('/visu/:idVisu', {templateUrl: 'partials/visuTimeSeries.html',controller:VisuCtrl}).
otherwise({redirectTo:'/home',templateUrl:'partials/home.html'})
}]);
platform.factory('mySharedService', function($rootScope) {
return {
broadcast: function(msg) {
$rootScope.$broadcast('handleBroadcast', msg);
}
};
});
PlatformCtrl definition:
function PlatformCtrl($scope,$http,$q,$routeParams, sharedService) {
...
$scope.listDataVisu ={};
...
$scope.listXhrReq[idVisu] = createXMLHttpRequest();
$scope.listXhrReq[idVisu].open("get", urlConnect, true);
$scope.listXhrReq[idVisu].onreadystatechange = function() {
$scope.$apply(function () {
var serverResponse = $scope.listXhrReq[idVisu].responseText;
$scope.listDataVisu[idVisu] = serverResponse.split("\n");
sharedService.broadcast($scope.listDataVisu);
});
};
$scope.listXhrReq[idVisu].send(null);
var w = window.open("#/visu/"+idVisu);
$scope.$on('handleBroadcast', function(){
console.log("handleBroadcast (platform)");
});
}
VisuCtrl definition:
function VisuCtrl($scope,$routeParams,sharedService) {
$scope.idVisu = $routeParams.idVisu;
$scope.data = [];
/* ***************************************
* LISTENER FOR THE HANDLEBROADCAST EVENT
*****************************************/
$scope.$on('handleBroadcast', function(event,data){
console.log("handleBroadcast (visu)");
$scope.data = data[$scope.idVisu];
});
}
Injection:
PlatformCtrl.$inject = ['$scope','$http','$q','$routeParams','mySharedService'];
VisuCtrl.$inject = ['$scope','$routeParams','mySharedService'];
PROBLEM DEFINITION
When running this code, it looks like only the PlatformCtrl controller listens for the handleBroadcast event. Indeed, having a look to the console all what is displayed is only handleBroadcast (platform) every time new data arrive. I am very surprised because I have read in the official documentation that the $broadcast function
dispatches an event name downwards to all child scopes (and their
children) notifying the registered ng.$rootScope.Scope#$on listeners.
Since all the scopes in a given app inherits from $rootScope, I do not get why the $on function in VisuCtrl is not launched every time new data are broadcasted.
What I think is that when you open a new browser window you are launching a new AngularJS instance. This way it's not possible that the two controllers are able to communicate via a service.
If you have problems with scopes communicating, you can inject the $rootScope and see whether all the scopes that should communicate are actually instanciated.
function VisuCtrl($scope, $routeParams, sharedService, $rootscope) {
console.log($rootScope);
}
Your request flow comes out of the angular, therefore it would not be recognized until the next $digest phase (see how angular handles two-way binding via dirty matching). To get in to the angular world you need to use $apply:
$scope.listXhrReq[idVisu].onreadystatechange = function() {
$scope.$apply(function () {
var serverResponse = $scope.listXhrReq[idVisu].responseText;
$scope.listDataVisu[idVisu] = serverResponse.split("\n");
sharedService.broadcast($scope.listDataVisu);
});
};
Could it be that your VisuCtrl hasn't been initialized yet, since you are using custom routing?
Is it still the same, when you navigate to /visu/:idVisu?

Resources