I have a Service:
angular.module('cfd')
.service('StudentService', [ '$http',
function ($http) {
// get some data via the $http
var path = 'data/people/students.json';
var students = $http.get(path).then(function (resp) {
return resp.data;
});
//save method create a new student if not already exists
//else update the existing object
this.save = function (student) {
if (student.id == null) {
//if this is new student, add it in students array
$scope.students.push(student);
} else {
//for existing student, find this student using id
//and update it.
for (i in students) {
if (students[i].id == student.id) {
students[i] = student;
}
}
}
};
But when I call save()
, I don't have access to the $scope
, and get ReferenceError: $scope is not defined
. So the logical step (for me), is to provide save() with the $scope
, and thus I must also provide/inject it to the service
. So if I do that like so:
.service('StudentService', [ '$http', '$scope',
function ($http, $scope) {
I get the following error:
Error: [$injector:unpr] Unknown provider: $scopeProvider <- $scope <- StudentService
The link in the error (wow that is neat!) lets me know it is injector related, and might have to do with order of declaration of the js files. I have tried reordering them in the index.html
, but I think it is something more simple, such as the way I am injecting them.
Using Angular-UI and Angular-UI-Router
Instead of trying to modify the
$scope
within the service, you can implement a$watch
within your controller to watch a property on your service for changes and then update a property on the$scope
. Here is an example you might try in a controller:One thing to note is that within your service, in order for the
students
property to be visible, it needs to be on the Service object orthis
like so:Well (a long one) ... if you insist to have
$scope
access inside a service, you can:Create a getter/setter service
Inject it and store the controller scope in it
Now, get the scope inside another service
You could make your service completely unaware of the scope, but in your controller allow the scope to be updated asynchronously.
The problem you're having is because you're unaware that http calls are made asynchronously, which means you don't get a value immediately as you might. For instance,
There's a simple way to get around this and it's to supply a callback function.
The form:
This removed some of your business logic for brevity and I haven't actually tested the code, but something like this would work. The main concept is passing a callback from the controller to the service which gets called later in the future. If you're familiar with NodeJS this is the same concept.
Services are singletons, and it is not logical for a scope to be injected in service (which is case indeed, you cannot inject scope in service). You can pass scope as a parameter, but that is also a bad design choice, because you would have scope being edited in multiple places, making it hard for debugging. Code for dealing with scope variables should go in controller, and service calls go to the service.
The
$scope
that you see being injected into controllers is not some service (like the rest of the injectable stuff), but is a Scope object. Many scope objects can be created (usually prototypically inheriting from a parent scope). The root of all scopes is the$rootScope
and you can create a new child-scope using the$new()
method of any scope (including the$rootScope
).The purpose of a Scope is to "glue together" the presentation and the business logic of your app. It does not make much sense to pass a
$scope
into a service.Services are singleton objects used (among other things) to share data (e.g. among several controllers) and generally encapsulate reusable pieces of code (since they can be injected and offer their "services" in any part of your app that needs them: controllers, directives, filters, other services etc).
I am sure, various approaches would work for you. One is this:
Since the
StudentService
is in charge of dealing with student data, you can have theStudentService
keep an array of students and let it "share" it with whoever might be interested (e.g. your$scope
). This makes even more sense, if there are other views/controllers/filters/services that need to have access to that info (if there aren't any right now, don't be surprised if they start popping up soon).Every time a new student is added (using the service's
save()
method), the service's own array of students will be updated and every other object sharing that array will get automatically updated as well.Based on the approach described above, your code could look like this:
One thing you should be careful about when using this approach is to never re-assign the service's array, because then any other components (e.g. scopes) will be still referencing the original array and your app will break.
E.g. to clear the array in
StudentService
:See, also, this short demo.
LITTLE UPDATE:
A few words to avoid the confusion that may arise while talking about using a service, but not creating it with the
service()
function.Quoting the docs on
$provide
:Basically, what it says is that every Angular service is registered using
$provide.provider()
, but there are "shortcut" methods for simpler services (two of which areservice()
andfactory()
).It all "boils down" to a service, so it doesn't make much difference which method you use (as long as the requirements for your service can be covered by that method).
BTW,
provider
vsservice
vsfactory
is one of the most confusing concepts for Angular new-comers, but fortunately there are plenty of resources (here on SO) to make things easier. (Just search around.)(I hope that clears it up - let me know if it doesn't.)
Got into the same predicament. I ended up with the following. So here I am not injecting the scope object into the factory, but setting the $scope in the controller itself using the concept of promise returned by $http service.