AngularJS, Using a Service to call into a Controller - angularjs

I am new to Angular, what I would like to accomplish is: From a Service / Factory to call methods directly into a controller.
In the following code, I would like from the valueUserController I would like to create a method from the service myApi and set the value inside the valueController.
Here is my code:
modules/myApi.js
var MyApi = app.factory('MyApi', function()
var api = {};
api.getCurrentValue = function() {
// needs to access the Value controller and return the current value
}
api.setCurrentValue = function(value) {
// needs to access the Value controller and set current value
}
api.getValueChangeHistory = function() {
// access value controller and return all the values
}
);
controllers/value.js
app.controller('valueController', function($scope) {
var value = 0;
function getValue() {
return value;
}
function setValue(inValue) {
value = inValue;
}
// ......
});
controllers/valueUser.js
app.controller('valueUserController', function($scope, myApi) {
function doStuff() {
var value = myApi.getValue();
value++;
myApi.setValue(value);
}
});
I am finding to do this in AngularJS pretty difficult and I haven't found any similar post on here.
Thanks for any help,
Andrea

Trying to communicate with a specific controller from a service is not the correct way of thinking. A service needs to be an isolated entity (which usually holds some state), by which controllers are able to interact with.
With this in mind, you can use something like an event pattern to achieve what you are looking for. For example, when your service completes some particular process, you can fire an event like so:
$rootScope.$broadcast('myEvent', { myValue: 'someValue' });
Then any controller in your system could watch for that event and perform a specific task when required. For example, inside your controller you could do the following:
$scope.$on('myEvent', function(event, data){
// Do something here with your value when your service triggers the event
console.log(data.myValue);
});

Related

How to store controller functions in a service and call them in AngularJS

I need to execute functions of some controllers when my application ends (e.g. when closing the navigator tab) so I've thought in a service to manage the list of those functions and call them when needed. These functions changes depending on the controllers I have opened.
Here's some code
Controller 1
angular.module('myApp').component('myComponent', {
controller: function ($scope) {
var mc = this;
mc.saveData = function(objectToSave){
...
};
}
});
Controller 2
angular.module('myApp').component('anotherComponent', {
controller: function ($scope) {
var ac = this;
ac.printData = function(objects, priority){
...
};
}
});
How to store those functions (saveData & printData) considering they have different parameters, so when I need it, I can call them (myComponent.saveData & anotherComponent.printData).
The above code is not general controller but the angular1.5+ component with its own controller scope. So the methods saveData and printData can only be accessed in respective component HTML template.
So to utilise the above method anywhere in application, they should be part of some service\factory and that needs to be injected wherever you may required.
You can create service like :
angular.module('FTWApp').service('someService', function() {
this.saveData = function (objectToSave) {
// saveData method code
};
this.printData = function (objects, priority) {
// printData method code
};
});
and inject it wherever you need, like in your component:
controller: function(someService) {
// define method parameter data
someService.saveData(objectToSave);
someService.printData (objects, priority);
}
I managed to make this, creating a service for managing the methods that will be fired.
angular.module('FTWApp').service('myService',function(){
var ac = this;
ac.addMethodForOnClose = addMethodForOnClose;
ac.arrMethods = [];
function addMethodForOnClose(idModule, method){
ac.arrMethods[idModule] = {
id: idModule,
method: method
}
};
function executeMethodsOnClose(){
for(object in ac.arrayMethods){
ac.arrMethods[object].method();
}
});
Then in the controllers, just add the method needed to that array:
myService.addMethodForOnClose(id, vm.methodToLaunchOnClose);
Afterwards, capture the $window.onunload and run myService.executeMethodsOnClose()

two-way binding in angular factories

I wrote an Angular factory to store an object that I need to pass to diferent controllers. The factory looks like this:
app.factory('SearchEngineConfigs', function () {
var configs = {
isInternational: false,
TripType: 1,
journeys: []
}
var _setInternational = function(val) {
configs.isInternational = val;
}
var _setTripType = function(val) {
configs.TripType = val;
}
var _setJourneys = function(journeys) {
configs.journeys = journeys;
}
var _getConfigs = function() {
return configs;
}
return {
setInternatioanl: _setInternational,
setTripType: _setTripType,
setJourneys: _setJourneys,
getConfigs: _getConfigs
}
});
So I have this factory injected in my controllers. In one of the controllers I'm setting the values of configs' factory like this:
SearchEngineConfigs.setInternatioanl($scope.searchEngine.isInternational);
SearchEngineConfigs.setTripType($scope.searchEngine.TripType);
SearchEngineConfigs.setJourneys($scope.journeys.slice());
So far so good. What is happening now is that everytime I change let's say the $scope.searchEngine.isInternational without calling the factory method SearchEngineConfigs.setInternatioanl this change still being reflected into the factory and thus, updating this property in another controller that is using that factory at the same time of the first controller. How can I avoid this to happen? I only want to change the value of the object inside the factory when I explicity make a call to the correponding factory method.
You could use angular.copy to avoid allowing any references to your internal state objects for existing outside your factory.
Note that you could have to do this on both the input and output, as either could create a leak.
One way of ensuring this was consistant would be to use a decorator function:
function decorate(func) {
return function() {
const argumentsCopy = _.map(arguments, a => angular.copy(a));
const result = func.apply(factory, argumentsCopy);
return angular.copy(result);
};
}
...which in turn is used like this:
var factory = {
setInternatioanl: decorate(_setInternational),
setTripType: decorate(_setTripType),
setJourneys: decorate(_setJourneys),
getConfigs: decorate(_getConfigs)
}
return factory
You can either use the new keyword to have different instances of the service.
Something like, var searchEngineConfigs = new SearchEngineConfigs(); and then use it to invoke factory methods.
Or, you can use the angular.copy() while setting the variables in your service to remove the reference which is causing the update in service.
Something like,
var _setInternational = function(val) {
configs.isInternational = angular.copy(val);
}

Is it ok to bind whole service to controller scope?

For instance this service :
services.factory('ElementsService', function () {
var currentElement = 'default element';
var service = {
getCurrentElement: function () {
return currentElement;
},
setCurrentElement: function (elmnt) {
currentElement = elmnt;
}
}
return service;
I often find useful to do the following from controllers :
controllers.controller('ElementsCtrl', function($scope, ElementsService) {
$scope.elementsService = ElementsService;
});
To be able to bind the service variables in the html and stay up to date if the variables get changed by some other controller or service. Like so :
<p>The current element is : {{elementsService.getCurrentElement()}}</p>
My question is : is this ok or should I avoid doing this?
Sure the concept is OK and saves having to make a number of different scope variables
Another way you can do it is
angular.extend($scope, ElementsService );
Then in the view you immediately have access to the same data and methods that are returned from the factory
<button ng-click="setCurrentElement(someObj)">test</button>

Call controller function from service in 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.

Fire event from a service without "polluting" the $rootScope

I'm building an app in angularjs, where I have a central notification queue. Any controller can push into the queue and digest the messages.
I have built a service like:
angular.module('app').factory('notificationSvc', ['translateSvc', notification]);
function notification(translate) {
var notificationQ = [];
var service = {
add: add,
getAll: getAll
};
return service;
function add(message, type) {
notificationQ.push({
message: message,
type: type
});
}
function getAll() {
return notificationQ;
}
}
(One of the problems with this is that the notificationQ can be modified unsafely by calling svc.getAll()[3].message = "I have changed a message"; or something similar. I originally wanted a "push only" service with immutable messages, but this problem is outside of the scope of this question.)
If I digest this queue in a controller like:
$scope.notifications = svc.getAll();
$scope.current= 0; // currently visible in the panel
And use it like:
<div ng-repeat="notification in notifications" ng-show="$index == current">
<p>{{notification.message}}</p>
</div>
I can bind to it, see it changing and all is well. I can cycle through past notifications by changing the variable current.
The question:
When the queue gets a new element I want the $scope.index variable to change to notifications.length - 1. How do I do that?
I have seen examples using $rootScope.$broadcast('notificationsChanged'); and $scope.$on('notificationsChanged', function() { $scope.index = $scope.notifications.length - 1; });, but I did not really like the pattern.
I have a controller that knows about the service, has a direct reference to it, and yet we use $rootScope to communicate? Everything else sees the $rootScope, and all the events from different services will clutter up there.
Can't I just put the event on the service instead? Something like this.$broadcast('notificationsChanged') in the service and svc.$on('notificationsChanged', function() { ... }); in the controller.
Or would it be cleaner to watch the data directly? If yes, how? I don't like this as I was not planning on exposing the full array directly (I was planning on get(index) methods) it just sort of happened along the lines where I had no idea what I was doing and was happy that at least something works.
You could just manage events yourself. For example (untested):
function EventManager() {
var subscribers = [];
var service = {
subscribe: subscribe;
unsubscribe: unsubscribe;
publish: publish
}
return service;
function subscribe(f) {
subscribers.push(f);
return function() { unsubscribe(f); };
}
function unsubscribe(f) {
var index = subscribers.indexOf(f);
if (index > -1)
subscribers.splice(index, 1);
}
function publish(e) {
for (var i = 0; i < subscribers.length; i++) {
subscribers[i](e);
}
}
}
function notification(translate) {
var notificationQ = [];
var addEvent = new EventManager();
var service = {
add: add,
getAll: getAll,
onAdded: addEvent.subscribe;
};
return service;
function add(message, type) {
var notification = {
message: message,
type: type
};
notificationQ.push(notification);
addEvent.publish(notification);
}
function getAll() {
return notificationQ;
}
}
Then, from your controller:
...
var unsubscribe = notificationSvc.onAdded(function(n) { /* update */ });
Caveat: using this method the service will maintain a reference to the subscriber function that is passed to it using subscribe, so you have to manage the subscription using $scope.$on('$destroy', unsubscribe)
The notification approach would definitely work. Depending on your implementation it would be the right solution.
Another approach would be to watch the notifications array in your controller, like this:
$scope.$watchCollection('notifications', function(newValue, oldValue) {
$scope.index = newValue.length - 1;
});
This should work, because your controller receives a direct reference to the notifications array and therefore can watch it directly for changes.
As runTarm pointed out in the comments, you could also directly $watch the length of the array. If you're only interested in length changes this would be a more memory saving approach (since you don't need to watch the whole collection):
$scope.$watch('notifications.length', function (newLength) {
$scope.index = newLength - 1;
});

Resources