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.
Related
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/
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.
Let's say I have a controller like:
angular.app("myapp",[]).controller("MyCtrl", function (MyService){
var vm = this;
vm.value1 = something();
vm.array1 = somethingElse;
//notice that I pass the whole controller as object to the service.
MyService.getData(vm);
//do something with data...
doWithNewData(vm.elementCreatedByMyService);
}). etc...
Now, to pass the vm itself seems to have sense, because I don't need to pass several values and several controllers call this service.
It seems overkill because, of course, vm has much more than the elements used in the service.
Also, this favors reuse (As I discovered in production).
My questions then: Is this an antipattern or is a valid use of a "controller as" object?
Now, if this is an antipattern, what should do instead?
Thanks in advance...
There's nothing really criminal here to call it antipattern. But there's nothing really good also because it requires the service to be aware of vm, any changes to existing vm properties will effect controller's scope (not necessary in a desirable way).
Now, if this is an antipattern, what should do instead?
Pass local variable to the service and incorporate it into controller's scope when needed?
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.