I am beginning my development in angular and I don't know much. What I'm trying to do is that I am trying to pass a fairly large collection of data from one controller to another. This is how I managed to do it.
angular.module("myApp").controller("controllerName", function($rootScope, $scope, *...other stuff...*)
{ /* code */ }
Later there is one specific method which is accessed from outside, and I copy the collection like this:
$rootScope.selectedItems = angular.copy($scope.selected.items);
(This is an array of 5k strings)
Which is then catched in another controller. Other developers said it is unsafe to pass this through $rootScope but after the data is passed and copied into local controller, I use this to get rid of the collection in rootScope
delete $rootScope.selectedItems;
Is this a safe way to do this? It works perfectly, and nothing seems dangerous to me
As a general rule, don't use $rootScope to pass data. By using it you make your module dependent on unspecified functionality which may not be a dependency of your module. It's a structural issue which will create problems later.
Instead, use a service:
angular.module("myApp").service("myAppService", function () {
this.selectedItems = [];
});
angular.module("myApp").controller("controllerName1", function(myAppService, $scope) {
$scope.selectedItems = myAppService.selectedItems;
});
angular.module("myApp").controller("controllerName2", function(myAppService, $scope) {
$scope.selectedItems = myAppService.selectedItems;
});
It's also recommended that all your logic goes into services (and factories/providers where appropriate). Controllers should be used only to expose service functionality, unless a necessary special case can be proven. This makes the logic in services easier to unit test.
There are many service are available you should go with broadcast
Here is example for $broadcast service
https://toddmotto.com/all-about-angulars-emit-broadcast-on-publish-subscribing/
Related
Is it advisable to store data in $rootScope.
I have a cordova app which uses sensor data which is coming every 100ms. For me to use that data in multiple controller I am using $rootScope.sensorData variable which is being refreshed every 100ms. Is it alright to use it this way? Is there a better way to do it?
Thank you
You can store it in factory. In AngularJS factory is singleton, so only instance is created.
myApp.factory('SensorSrv', function SensorSrv() {
var sensorData;
return {
setData: setData,
getData: getData
};
function setData(data) {
sensorData = data;
}
function getData() {
return sensorData;
}
});
You can also user local-storage if you want to persist the data.
I think this is not good idea to use $rootScope in entire code logic , There are lot of reasons behind that ... Instead of that you can create code login in services it is more flexible ... and also you can see this link
Best practice for using $rootscope in an Angularjs application?
From the Official Docs:
$rootScope exists, but it can be used for evil
Scopes in Angular form a hierarchy, prototypally inheriting from a root scope at the top of the tree. Usually this can be ignored, since most views have a controller, and therefore a scope, of their own.
Occasionally there are pieces of data that you want to make global to the whole app. For these, you can inject $rootScope and set values on it like any other scope. Since the scopes inherit from the root scope, these values will be available to the expressions attached to directives like ng-show just like values on your local $scope.
Of course, global state sucks and you should use $rootScope sparingly, like you would (hopefully) use with global variables in any language. In particular, don't use it for code, only data. If you're tempted to put a function on $rootScope, it's almost always better to put it in a service that can be injected where it's needed, and more easily tested.
Conversely, don't create a service whose only purpose in life is to store and return bits of data.
--AngularJS Miscellaneous FAQ
I recommend using app.value app.value('test', 20);
Because by using $rootScope you are exposing that value to all the services which might be a security threat. By using value you can make sure where do you want to use that variable according to your requirement.
About shared state between controllers. I have a hard time finding the right way to do this from all the possible solutions recommended on SO. I made this sketch to illustrate the basic idea I had about this so far using a factory.
There is the factory myFactory, that holds a shared variable sharedVar.
The controllers Ctrl1, Ctrl2, Ctrl3 want to access always the updated version. They also can call an updateViaHttp.
Is that the right purpose of a factory? (in general to share state,
specific to the other options like service and provider)
If so, how to watch changes of the sharedVar in a proper way? (by
reference of objects, $watch, events (broadcast, on), ...)
Is there a general pattern that works well for objects, arrays and
primitives.
You've got the basic idea right, assuming by 'factory' you mean 'service' -- it's kind of confusing, I know, because services are declared using factory functions. That said, it's an important distinction to make so that you'll have an easier time finding documentation, etc.
Watch changes either just by using object references and being careful about watch depths in Angular (my preferred method) or by explicitly registering $watch statements (still be careful about watch depths). Generally I'm of the opinion that you shouldn't overuse broadcasts as it can make your code a little messy. It also kind of defeats the point of the service in this case, which is to be the source of shared state.
My general pattern for creating services is to bind everything I want to use to an object (both data and functions) and then return that object in the factory function. Sometimes you have to introduce some extra nesting so that the Javascript prototypical inheritance doesn't mess with you (see the watch depths thing again) but that's the general idea.
An example service for your set up:
angular.factory('shareAndUpdate', ['dependencyInjection', function(dependency) {
var srvc = {};
srvc.sharedVar = 'something';
srvc.updateViaHttp = function(){ something };
return srvc;
}]);
factories vs services vs providers - only differences are related to how the Dependency Injector provides instances of them to you. Services are specifically designed for providing singletons, but are just a wrapper over factories that add the singleton specific functionality. Nothing stopping you from returning a singleton from a factory.
using services to share state - you need a singleton and a service makes defining and injecting singletons easy.
SomeService:
var foo = {
bar = 'a';
};
function getFoo() {
return foo;
}
SomeController[SomeService injected here] :
$scope.foo = SomeService.getFoo();
$scope.$watch('foo.bar', ...);
$scope.setFooBar = function(val) {
$scope.foo.bar = val;
};
2
The general pattern here is to never do a
$scope.foo = { bar: 'Some other reference' }; because then all your other things depending on SomeService will not get the new reference when you overwrite it - the "infamous" always use a "dot" in $scope stuff issue.
You are probably looking for something like a pubsub service:
https://www.npmjs.com/package/angular-pubsub
However, in my experience, through proper design, you can minimize the necessity to share data in between non-nested controllers.
Sometimes it is unavoidable, though, for stuff like login credentials, permissions, stuff that is all-app-encompassing. In such occasions you can use a service to indeed share/get the state in between controllers, or you can go for the fully-fledged pubsub mechanism.
A factory is just another way of specifying a service. A factory, when called, gives an instance of a service. This service you can use for everything that you want, one of those things being sharing state in between your controllers.
You can watch a shared variable in many ways, the easiest being inheriting scopes, but, as you mentioned, sometimes your controllers don't necessarily inherit their scopes. Then you can use a pubsub service or just broadcast events on a shared scope for both controllers (like $rootScope, which is the parent of all controllers' scopes for your app).
If you were to use an existing pubsubservice, it would still be up to your implementing controllers to actually do the subscribe and watching on a specific variable and updating their corresponding scopes accordingly. However, that can be avoided if you design your app in such a way that your controllers inherit the variable from a shared scope. Then they will automatically update their stuff using the normal angular mechanism. That, sadly, can not always be achieved and then what you are left with is having to implement a pubsub service.
So, this isn't the typical question of HOW to do it. I know it can be done with a service or a factory but I was wondering if someone could share what the advantages/disadvantages would be of just creating a basic service, injecting it into each controller, and then extending the service with functions from each controller. Something similar to the following example..
app.service('HelperService', function() {
return {};
});
app.controller('Controller1', function($scope, HelperService) {
$scope.somefunc = function() {
//do stuff here
};
HelperService.somefunc = $scope.somefunc;
});
app.controller('Controller2', function($scope, HelperService) {
HelperService.somefunc();
});
This works, and works well. I feel a bit stupid for asking this but it just seems like I'm missing something here as to why this isn't used or recommended?
It may work, but its a bad idea.
Controller2 HelperService.somefunc() won't exist until Controller1 has been instantiated. So theres an implicit dependency of Controller2 on Controller1
the code on HelperService isn't in one place where it can be understood together
if you are doing some sort of data manipulation in that function, it really should be operating on data encapsulated by the HelperService.
The service is a singleton and it will be instantiated once calling new on the function itself -- the function you pass in is essentially a constructor. This will create the empty object you are returning for use everywhere, but if you want to return an object in such a way it makes more sense to use .factory, but this is not a big deal.
At any rate, you can consider your code to conceptually do this:
var HelperService = function () {}
var helperService = new HelperService;
function Controller1() {
helperService.someFunc = function () {}
}
function Controller2() {
helperService.someFunc();
}
I would consider this a dangerous thing to do for a couple of reasons:
Controller1 must be instantiated before Controller2 or else somefunc won't be available to Controller2. Ideally the controllers would have no knowledge of each other.
You are coupling Controller/ViewModel (since you're using scope) with service level logic, but these should be decoupled. HelperService shouldn't know about the controllers either. Instead, you should be injecting a service that has an API that the controllers expect to use. This doesn't always have to be HelperService, it just has to look like HelperService to the controllers and its API shouldn't change.
Without knowing specifics about what you're trying to do, it's hard to advise. In general you may rethink what you want to do, but you can extend functionality of services with other services. Consider services to be in their own layer.
The code below represents a situation where the same code pattern repeats in every controller which handles data from the server. After a long research and irc talk at #angularjs I still cannot figure how to abstract that code, inline comments explain the situations:
myApp.controller("TodoCtrl", function($scope, Restangular,
CalendarService, $filter){
var all_todos = [];
$scope.todos = [];
Restangular.all("vtodo/").getList().then(function(data){
all_todos = data;
$scope.todos = $filter("calendaractive")(all_todos);
});
//I can see myself repeating this line in every
//controller dealing with data which somehow relates
//and is possibly filtered by CalendarService:
$scope.activeData = CalendarService.activeData;
//also this line, which triggers refiltering when
//CalendarService is repeating within other controllers
$scope.$watch("activeData", function(){
$scope.todos = $filter("calendaractive")(all_todos);
}, true);
});
//example. another controller, different data, same relation with calendar?
myApp.controller("DiaryCtrl", function($scope, Restangular,
CalendarService, $filter){
//this all_object and object seems repetitive,
//isn't there another way to do it? so I can keep it DRY?
var all_todos = [];
$scope.todos = [];
Restangular.all("diary/").getList().then(function(data){
all_diaries = data;
$scope.diaries = $filter("calendaractive")(all_diaries);
});
$scope.activeData = CalendarService.activeData;
$scope.$watch("activeData", function(){
$scope.todos = $filter("calendaractive")(all_diaries);
}, true);
});
DRY should be followed purposefully, not zealously. Your code is fine, the controllers are doing what they are supposed to be doing: connecting different pieces of the app. That said, you can easily combine repeated code in a factory method that returns a function reference.
For example,
myApp.factory('calendarScopeDecorator', function(CalendarService, Restangular, $filter) {
return function($scope, section) {
$scope.todos = [];
$scope.activeData = CalendarService.activeData;
Restangular.all(section+"/").getList().then(function(data){
$scope.all_data = data;
$scope.filtered_data = $filter("calendaractive")(data);
});
$scope.$watch("activeData", function(){
$scope.todos = $filter("calendaractive")($scope.all_data);
}, true);
}
});
And then incorporate this into your controller:
myApp.controller("DiaryCtrl", function($scope, calendarScopeDecorator){
calendarScopeDecorator($scope, 'diary');
});
I wouldn't do this kind of thing with a watcher and local reference like in this controller. Instead, I would use $on() / $emit() to establish a pub-sub pattern from the service out to the controllers that care about its updates. This is an under-used pattern IMO that provides a more "DRY" mechanism. It's also extremely efficient - often more so than a watcher, because it doesn't need to run a digest to know something has changed. In network-based services you almost always know this for certain, and you don't need to go from knowing it for certain to implying it in other locations. This would let you avoid the cost of Angular's deep inspection of objects:
$rootScope.$on('calendarDiariesUpdated', function() {
// Update your $scope.todos here.
}, true);
In your service:
// When you have a situation where you know the data has been updated:
$rootScope.$emit('calendarDiariesUpdated');
Note that emit/on are more efficient than using broadcast, which will go through all nested scopes. You can also pass data from the service to listening controllers this way.
This is a really important technique that does a few things:
You no longer need to take a local reference to activeData, since you aren't actually using it (it's DRY).
This is more efficient in most/many cases than a watcher. Angular doesn't need to work out that you need to be told of an update - you know you do. This is also kind of a DRY principle - why use a framework tool to do something you don't actually need? It's an extra step to put the data somewhere and then wait for Angular to digest it and say "whoah, you need to know about this."
You may even be able to reduce your injections. There's no need to take CalendarService because that service can pass a reference to the array right in its notification. That's nice because you don't need to refactor this later if you change the storage model within the service (one of the things DRY advocates also advocate is abstracting these things).
You do need to take $rootScope so you can register the watcher, but there's nothing in pub-sub concepts that violate DRY. It's very common and accepted (and most important: it performs very well). This isn't the same thing as a raw global variable, which is what scares people off from using $rootScope in the first place (and often rightly so).
If you want to be "Super DRY" you can re-factor the call to $filter into a single method that does the filtering, and call it both from your REST promise resolution and from the calendar-update notification. That actually adds a few lines of code... but doesn't REPEAT any. :) That's probably a good idea in principle since that particular line is something you're likely to maintain (it takes that static "calendaractive" parameter...)
It looks like the code in the 2 controllers is identical except for the path to which the API call is made: "vtodo/" or "diary/".
One way to achieve something closer to DRY-ness is to pass the API path as an option to the controller as an attribute. So, assuming we call the controller ApiController, this can be used as
<div ng-controller="ApiController" api-controller-path="vtodo/">
<!-- Todo template -->
</div>
and
<div ng-controller="ApiController" api-controller-path="diary/">
<!-- Diary template -->
</div>
Which is then accessible in the controller by the injected $attrs parameter:
myApp.controller("ApiController", function($scope, $attrs, Restangular, CalendarService, $filter) {
// "vtodo/" or "diary/"
var apiPath = $attrs.apiControllerPath;
As a caution I would beware of over-architecting, not everything needs to be factored out, and there is an argument that you are just following a design pattern rather than copy+pasting code. However, I have used the above method of passing options to a controller for a similar situation myself.
If you are worried about making multiple calls to the same resource CalendarService, I'd recommend finding a way to cache the result, such as defining a variable in that service or using Angular's $cacheFactory.
Otherwise, I don't see anything wrong with your patterns.
Is it correct to pass the "current" $scope to an AngularJS service?
I'm in the situation where I've a $service knowing it's consumed by only one controller, and I'd like to have a reference to the controller's scope in the $service methods themselves.
Is this philosophically correct?
Or I'd better to broadcast events to the $rootScope and then make my controller listen to them?
To let the controller know when something async happens, use Angular promises.
To provoke the $apply, you don't need the scope, you can call $rootScope.$apply, as there is no difference calling it in a specific scope or in the root.
Regarding the variable reading, it would be better if you received parameters. But you could also read it from a scope as an object parameter, but I would go with parameter, that would make your service interface much more clearer.
I would say if your functionality is specific to one controller only than you don't need a service.
The controllers tasks is to manipulate the specific model whereas a service should deal with global tasks. I would rather stick to this paradigm instead of mixing things up.
This is what the docs say
Service
Angular services are singletons that carry out specific tasks common to web apps
Controller
In Angular, a controller is a JavaScript function(type/class) that is used to augment instances of angular Scope, excluding the root scope.
PS: Apart from that if you need to digest you can also inject the $rootScope within your service.
Yes. You can pass the $scope into the service when you initialize it. In the service constructor you can assign the scope to something like this._scope and then reference the scope within the service!
angular.module('blah').controller('BlahCtrl', function($scope, BlahService) {
$scope.someVar = 4;
$scope.blahService = new blahService($scope);
});
angular.module('blah').factory('blahService', function() {
//constructor
function blahService(scope) {
this._scope = scope;
this._someFunction()
}
//wherever you'd reference the scope
blahService.prototype._someFunction = function() {
this._scope['someVar'] = 5;
}
return blahService;
});
I personally believe that passing the whole $scope to a service is a bad idea, because it creates a kinda circular reference: the controller depends on the service and the service depends on the scope of the controller.
On top of being confusing in terms of relations, things like this one end up getting in the way of the garbage collector.
My preferred approach is to put a domain object in the controller scope and pass that to the service. This way the service works regardless whether it's used inside a controller or maybe inside another service in the future.
For example, if the service is supposed to push and pop elements from an array errors, my code will be:
var errors = [];
$scope.errors = errors;
$scope.myService = new MyService(errors);
The service interacts then with the controller by operating on errors.
Of course I've got to be cautious about never wiping out the whole array reference, but at the end of the day that's a general JS concern.
I'd never want to use broadcasting, $apply and/or similar things, because imho good OO-practices will always trump whatever Angular-magics.