Angular Service to update all scopes using it - angularjs

I've been reading that Services are great for sharing data. But I am having a hard time making all data be in sync...
For example, let's say you have
an ItemService with an array of items.
a ItemListController, which shows a list of items ($scope.items = ItemService.items)
a AddItemController, a form to add items to the array (using the service).
When I add a new item, it doesn't automatically shows in the list controller. My workaround so far is to use cache:false in the router so that it refreshes every time I look at it...
I can't use $scope.$apply() because the adding happens on one controller while I want to see it on another...

You need to use $rootScope.$broadcast('item.added'); in the controller that initiates changes (AddItemController), after it successfully updates the array in the service.
Then in the ItemListController you use
$rootScope.$on('item.added', function(event){
$scope.items = ItemService.items;
});
Check this question

#Sarpdoruk There is two-way data binding between views and controllers, but not between controllers and services. By using this line $scope.items = ItemService.items; you copy 'items' from service to items in controller but you copy by value, not by reference. You can change $scope.items in your view or controller and they will get updated between the view and controller but these changes will have no effect on the service. Same thing if you update items in the service - controller will not know that something has been changed - so you need to $broadcast or $emit that something has changed from service and listen for the changes in the controller. You can also $broadcast between controllers, directives and even services (but since services have no access to $scope, you have to use $rootScope and some people frown upon that). The bottom line is, if you really have to use a $rootScope in the service because otherwise you will have to overcomplicate parts of your app, then do it and never bother with "best practice evangelist" The first rule of the app is that it has to work, only after it works you should worry about clean code. I hope it makes it clearer now.

Related

where in an angular app is a good place to store "shared" data

This is somewhat a follow up on my "Is it bad practice for an angular directive to request data" Q.
My Q Is where would be the appropriate place to keep application data?
for example - information about the current user such as his name and his roles in the app?
differrent areas (on the screen) and components will depend on this data (e.g - side bar will want to know if the curentUser.isAnAdmin and a helloUser Directive would like to know the currentUser.name
Does this mean that the currentUser should be placed on the $rootScope?
and what should be the trigger for retrieving the initial data for the currentUser and for refreshing this information?
I was thinking of having several ngControllers responsible for setting up this data on the scope of the same html node as that of the ngApp, but found out that it is not possible to have multiple ngControllers on a single HTML Item.
I am now thinking of having multiple services with methods that get a scope object and assign the data they are responsible to onto that scope.
It would allow me to separate code for currentUser from code for someOtherSharedAppData into two different services and call both of them from the applications's main controller thus assiging the data to the scope associated with the top-most element in the app - does that make sense?
In fact you asked two questions here:
Where to store and manipulate data?
When and how should I use the $rootScope (compared to $scope)?
1)
I will refer to this article:
Whenever data and methods need to be reusable I would write a service like this for example.
angular.module('modelDemo').service("myModel", [function() {
this.list = [what, ever, items, you, have];
this.property= null;
this.setProperty = function(value) {
this.property= value;
};
}]);
Note, that I'm not passing the $scope as you considered. Instead I would inject the service in my controller and bind the $scope variables like this:
$scope.list = myModel.list;
If you need, you can even bind to the full model
$scope.myModel = myModel;
myModel.setPropery(value)
Got the idea? This way all model changes will be directly available to the corresponging view
{{myModel.property}}
ng-repeat="item in myModel.list"
ng-click="myModel.setProperty(item)"
Conclusion: Yes, you should have different services for your user model and your someOtherSharedAppData models.
2)
I will refer to this SO Question.
In short: If you have data that should be available in many views, it is OK to bind your (service) model to $rootScope variables. As you can see in the mentioned discussion there are also other opinions but I think the conclusion is: It depends on the structure and needs of your application.

Is it bad practice for an angular directive to request data

Consider for example a currentUser directive.
I could let the controller use a service to get the data about the current user, provider it to the directive and let the directive render some "hello {user.name}" template.
Alternatively I could have the directive have a dependency on some currentUserService and in the directive's controller ask for currentUserService.getCurrentUser.
Is one of the two significantly better then the other for any reason?
I tend to go with the first option but not sure if the using the second one would not have a benefit of having all the current-user-logic less spread around...
Thanks
As long as you're requesting the data from a service, I believe having a dependency toward it in a directive is fine.
The main aspect of the controller is having access to $scope, not much more.
There are two scenarios and it really depends on the purpose of your directive:
the directive is only used to display user-data (in a complex way)
the directive displays data and manipulates it (according to user-input)
SCENARIO 1
Since the only purpose of the directive it to render the data somehow, the directive should not be responsible for retrieving the data.
So you decouple the logic how to access the data and how to display the data. This way you can also use the directive for users other than the currently logged in user.
If there should be some special things visible, if the user is logged in, the directive should use ng-if or ng-show for that (and maybe a parameter to disable that view-part).
SCENARIO 2
In this case the purpose of the directive is to provide a gui for some business-logic (service functionality). Therefore the service should be injected into the directive.
Remark:
PERFORMACE
If you get the data via method-call from your service, this method will only be called once in every digest-cycle if you load the data and inject it into the directive-controller. Otherwise it may be called once for each occurance of the directive.
INTEGRITY
Remember, that if your service-method requests data via http and you are using the directive for example 3 times in a view and the directive calls the service-method itself, this will result in 3 identical requests which may have non-identical results (i.e. someone other changes the data while the requests are processed).
It is always better to use service to reside business logic.You should use service to get data and inject that service to directive.Do not use controller to communicate between directive.Service is meant for that purpose, initiate once.

Storing $scopes in a factory

I'm adding features to a Web application that I did not write myself. I feel that a swipe feature cannot be added.
I saw somewhere "$scope cannot be accessed in a factory", but I somehow managed to store all scopes created by an ng-repeat in a factory. Since JavaScript passes by value I'm probably not "storing the scope" but still if I set the scopes ng-class within the factory I can clearly see the difference. I thought it was a good idea since scope traversing is a pain. Did I strike gold or am I wasting speed and space on storing the massive data which is scope?
HTML
main.html
<div ng-controller="someCtrlWhichWasDeclaredALongTimeAgo">
.
.
.
<div class="items-container"
ui-sortable="sortableOptions"
ng-model="selectedSession.items">
<div ng-repeat="item in selectedSession.items"
ng-controller="itemEditorCtrl"
ng-class="{'item-last':$last}">
<div ng-if="item._value !== undefined"
class="item inline-comment">
<span class="handle"></span>
</div>
<div ng-if="item._value === undefined"
class="item"
ng-include="'/views/item-editor.html'"></div>
</div>
</div>
<div class="stopper"></div>
</div>
</div>
item-editor.html
<div item-editor
swipe-function="swipeMe"
ng-swipe-left="swipeLeft(item)"
ng-swipe-right="swipeRight(item)">
...
</div>
This will render a list with my magic code. If an item in list is clicked it will expand to full screen. You should be able to swipe between the items from there.
I don't think I have to post more than the HTML to explain the problem. I did manage to make the swipe happen with $rootScope.$broadcast but what if I have hundred items in my list? Will it not grow slower with more and more items receiving message and processing them? I'd rather just load all of the scopes into one factory on selection of this session and then get rid of it when a session is deselected. It might need some time to start but it doesn't take 5 minutes to swipe.
What you're doing is totally fine. You can pass scope objects around your application at will. Just keep in mind what they represent. The $scope object is your interface with the view. Most of the time your services are going to have little to do with the view and should ideally be used to share data between controllers and directives. That said, there are several examples where it makes total sense for a service to indirectly affect a view. For example, we wrote a spinner service that allows spinner directives to register themselves with the service.
The spinner directive injects the spinnerService into it, calls the spinnerService.register function and passes in its isolate scope so the service can keep track of it and use it to toggle the spinner on and off. Then our devs can inject the service wherever they need it and hide/show loading spinners or groups of spinners when they need to.
What they mean by "$scope cannot be accessed in a factory" is that you cannot inject $scope inside a factory. $scope is tied to a DOM element (the element the controller or isolate directive was invoked on), so injecting a fresh $scope into a factory doesn't make much sense and is in fact impossible. You can't do app.factory('myFactory', function ($scope) { ... }); but you can definitely call your factory/service from a controller or directive and pass the scope object from there into a method you provide on your service.
There is nothing wrong with tracking scopes within services as long as you know what you're doing. People will tell you that you can't pass your scope into a service because it's an anti-pattern, but then they'll suggest putting something like this in your directive/controller and then passing it to the service:
var api = {
hideSpinner: function () {
$scope.show = false;
},
showSpinner: function () {
$scope.show = true;
}
};
Sure, you're not directly passing your $scope object to the service, but if the argument is that your scope object could get stuck in memory and never garbage collected, this solution doesn't help either. The API object you build and pass around takes its JavaScript scope chain with it, keeping in memory all the variables within the scope that it was defined, including the $scope of your controller/directive. It has to, or else those API methods wouldn't work. Your $scope is a sort of API for that component and it's fine to use it that way.
A better argument for doing something like the above example is that perhaps your $scope object contains information or functions that you don't need/want to share with the service. Making a little API object with just the things you want to expose to the service makes perfect sense there. Then your service only has access to the things you want it to have access to and can still affect the controller/directive $scope, but only in the ways outlined by the API object you passed in.
Please stay away from storing $scope objects in any type of global store. This includes $rootScope and Angular Services.
Since services in Angular are singleton, if any service is tracking $scope object (in any service variable) then the $scope may never get release, and there are chances of memory leaks.
Also the concept of scope is tightly tied to view and should not be used outside that context.
Maybe you can share some code around your implementation so we can suggest better solution.
"$scope cannot be accessed in a factory"
You're almost correct. You can't inject the $scope service into another service. There's nothing stopping you from creating a service that stores data, and putting $scope in as that data.
THIS IS A BAD IDEA. Services are used to share data between components. $scope is used to communicate with the view. Think of this as bananas and toasters. Both have purposes, no one will fault you for using one or the other, but using a service to store $scope is like stuffing a banana in a toaster.
Each component using $scope should only be concerned with its own scope - and no other (there are very rare exceptions but you shouldn't use them unless you know what you're doing).
Instead, put data in services and then put the service's data onto the scope:
.controller('ExampleController', function($scope, myService) {
$scope.names = myService.names;
});
Even better, you can look into using controllerAs syntax

AngularJS - $rootScope to share data with other controllers

I have a abstract state whose controller get's data from a $resource async. and put the result in $rootScope.data.
.state('stateA', {
abstract: true,
url...
templateUrl...
controller: function($rootScope, Resource){
// Empty data so far
$rootScope.data = [];
Resource.query().$promise.then(function(data){
$rootScope.data = data;
})
}
})
Inheriting states are supposed to use that data. At the moment when the controllers from these states are resolved the $rootScope has not received yet the data.
When the Resource.query() is resolved in the abstract state controller, it updates the $rootScope.
Data attached to $rootScope async does not $digest? Why controllers that inherit from the abstract state do not see the changes after $rootScope.data is populated? Do I need to $broadcast and $on?
no you don't need to do any of those things, you just need to make sure your data gets properly updated and your watchers are properly set. on your child states you should do something like.
scope.$watch(function(){return $rootScope.data},function(){
})
this will let your controller know when the data array has been reassign. you can also use watch collection, it depends what type of modifications are you planning to do make to your array. assigning it it will destroy the array and create a new one, with a new address, but adding and removing elements it affects it in a different way. but bottom line you need watchers in this case because you are recreating the array.
other thing is, i really not recommend using rootScope for this purposes this is what services where made for. it might seem an overkill to use a service for this, but most of the time chances are you are also going to need to implement some data manipulation logic. and you'll end up creating a service anyways if you are doing it right
The proper approach is almost always to create a service or factory for shared data between controllers.

How to output data from a directive to a controller, and should I?

Use case
For use in a form, I created a directive that tracks changes in an array. It allows changes to be reverted and deletions and additions to be stored separately. It allows for an array (one to many mapping in the database) to be updated incrementally (rather than requiring the server to either diff, or rewrite the entire list).
Problem?
My question is about the way I expose the functionality to the controller's scope. I currently use an two-way databound attribute on the directive's scope. This works, and it seems reliable (of course you can easily break it by reassigning the scope's value, but intentionally you can break anything).
Code
You can see this plunk to see this in action. It allows methods on the directive's controller to be called from the view and the view's controller. (I am using the directive controller intentionally because that's what I do in my actual code for the directive to directive communication, but this could also just be placed in the linking function.)
Question
Is this way of doing it bad design? Am I completely throwing AngularJS out of the window now and hacking in my own code. Are there any better ways to expose functions from a directive (keep in mind that there'll be multiple of these in a single form).
It's very easy to pass in my-attribute="someFunction()" to have the directive be a consumer of the view controller. I can't find a better way to do the opposite and have the view controller consume from the directive.
Alternative?
I've been thinking about using a service here, in which the service will provide an object that is instanciated in the view, passed to the directive, and have the directive blurp out it's results to that object. Then in turn have the view controller consume the information from that service's object. Would this be a better approach?
There's nothing wrong with your approach. In fact built-in angular directives such as ng-form use this approach to store the controller in the scope (see the name property of ng-form) http://docs.angularjs.org/api/ng.directive:ngForm
For more re-usability though I would put the api methods on the controller and then put the controller itself in the api:
this.getChanges = function () {};
this.resetChanges = function(){};
$scope.api = this;
In directives, the main purpose of the controller is to serve as an api for other directives (if you didn't need an api for other directives you could just do everything in the link function). Doing it this way ensures the api is available both on the scope as well as to any directive that 'requires' the oneToMany directive.

Resources