Closed MrOutput closed 8 years ago
I am not sure what part of the docs doesn't add up (although admittedly the multitude of ways to create Angular services (hereafter "services") has been a common source of confusion).
There are numerous resources on that subject, but in brief (quoting from the docs):
Angular services are substitutable objects that are wired together using dependency injection (DI). You can use services to organize and share code across your app.
So, services are "things" that you can inject into other parts/components, such as directives, controllers, filters, other services etc. Angular's Inversion Of Control container is in charge of creating the service instances (one per injector), supplying the necessary "ingredients" (dependent values) as arguments and handing them to whoever requests them (through Dependency Injection).
The "recipe" for how a service will de created is defined using one of the available methods: .provider()
, .service()
, .factory()
, .value()
, .constant()
Basically, .provider()
is the most flexible but verbose, so Agular provides the other methods as syntactic sugar (more or less) for some of the most common usecases.
The service factory function generates the single object or function that represents the service to the rest of the application.
That is exactly what it does. Using .factory()
, you can specify a (factory) function, that will be invoked by the injector (when it needs an instance of the service), and it is expected to return the service instance (i.e. the "thing" that will be passed to whoever requests the corresponding service).
value: Register a value service with the $injector, such as a string, a number, an array, an object or a function.
For you usecase, you might want to just use the .value()
shortcut. E.g. .value('Person', Person)
says: "Each time someone requests for the service identified by the string 'Person'
, give them the Person
(which happens to be a constructor function, but Agular doesn't know and doesn't care) and let them use it as they see fit (e.g. call it with new
and a bunch of arguments).
Regarding the following:
[...] factories should return service / class instances. However, this code contradicts. No instances were created and returned by the factory.
Note that instance
doesn't necessarily mean "something that was the result of calling the new
operator on a constructor function / class". The new
operator is one of the many possible ways of creating an instance of something. The example code returns a new function, which is an instance of Function
(which is an instance of Object
).
But the main point is that whatever the factory function returns, is the "instance of the service" in the sense that it is the "concrete thing" that will be passed to anyone that requests it (through DI). And because services as (per injector) singletons, the exact same "thing" will be passed everytime.
Afaict, everything works (and adds up) properly. BUT we always welcome suggestions (or better yet pull requests) with docs improvements, so by all means do come forward if you have ideas on how we can do a better job explaining these concepts.
I am going to close this, since it is not actionable in its current form, but feel free continue the discussion below.
@gkalpak I have long felt that the key point, of confusion around the service
/ factory
issue, which the documentation completely fails to address, is the difference in their use cases, or rather the lack thereof.
.service
and .factory
essentially have the exact same capabilities but cater to different programming styles. If the documentation would simply remark that .service
should be used when you want to write in a classical style, and .factory
should be used otherwise, things would be a lot clearer.
Additionally, I have seen some highly skilled angular developers write code which reflects a misunderstanding of arbitrarity of this distinction. For example, I have seen this:
(function () {
Function MyService ($http) {
this.$http = $http;
}
MyService.prototype.getById = function (id) {
return this.$http('/whatever?id=' + id);
};
angular.module('app', [])
.factory('MyService', ['$http', function($http) {
return new MyService($http);
}]);
}());
Why didn't they write this? I believe it was because the documentation failed to adequitely explain the similarity and redundancy of the concepts.
(function () {
Function MyService ($http) {
this.$http = $http;
}
MyService.prototype.getById = function (id) {
return this.$http('/whatever?id=' + id);
};
angular.module('app', [])
.service('MyService', ['$http', MyService]);
}());
Maybe they know the difference and they intentionally use it this way. Note that @johnpapa's styleguide suggests using .factory
even where .service
would be enough, for the sake of consistency/fewer concepts. (Some agree, some don't - there is no right or wrong.)
.service
and.factory
essentially have the exact same capabilities
This is not 100% true, unless you use a constructor function as a factory, havig a return value (which is neither a good idea, nor necessary). .service
has a more limited scope but requires less boilerplate. .factory
is a little more flexible, but is more verbose.
Like I said, if you have an idea of how the docs could be improved, please submit a PR and we'll be more than happy to discuss :wink:
the docs say that .service
takes a constructor function and will be invoked by the new
keyword, which I supplied and have attempted. But I can't use it like a constructor, it thinks the parameters are dependencies for the service. Instead, to get around this, I have to return the REAL constructor function in the service function / wrapper. That is where the docs are not adding up, or at least are confusing for me.
I am well aware of the ideologies behind factories and that they have some method that returns an instance of something depending on the parameters passed to them.
(function () {
angular
.module("App")
.service("Form", FormService);
function FormService() {// SERVICE FUNCTION WRAPPER
function Form() {// REAL CONSTRUCTOR FUNCTION
this.roles = [];
this.name = "";
}
Form.prototype.addRole = addRole;
function addRole() {
this.roles.push("");
}
return Form;// RETURN REAL CONSTRUCTOR
}
})();
In your factory example, you are returning a function which is indeed an instance of Function. But you are returning a new function just to push messages to the same message queue. Plus, the docs say that a factory returns an instance of a service, which isn't what was done in that example. A Function was returned. Why not just expose a single method to push onto a queue to begin with?
The whole point of .service()
is that you are supplying a constructor function that Angular's $injector
will instantiate (with new
) when it needs to create an instance of the service. It is not a way to "store" constructor functions that will be passed to the user to instantiate. And yes, arguments of constructor functions passed to .service()
(and used to create service instances) are resolved through DI - that is the whole point of having a IoC container.
Again, for your usecase (where you don't need anything from DI), you could (and should) use .value()
:
(function () {
angular.
module('App').
value('Form', Form);
function Form() {
this.roles = [];
this.name = '';
}
Form.prototype.addRole = function addRole() {
this.roles.push('');
}
})();
Using .value()
could work but the docs say that it should take a service instance object as the second parameter. A constructor function is not a service instance object or is it to angular? The reason for the large confusion in the community is the numerous ways of using these methods and the lack of a concrete definition.
the docs say that it should take a service instance object as the second parameter
This means that it should take the "thing" (whatever that is), that will be returned from the $injector
whenever someone requests the service (in this case 'Form').
A constructor function is not a service instance object or is it to angular?
In JavaScript there are no "services", so when we are talking about "services" in this context, we are referring to Angular services" (a.k.a. "substitutable objects that are wired together using dependency injection (DI)"). So, a constructor function (which is essentially just a function, which is essentially an Object) can be used as a service instance (i.e. as the "thing" that will be returned when someone requests for the service instance).
I think much of the confusion comes from the fact that in JavaScript, functions are first class citizens and are objects themselves (everything is an object :grin:).
This is (more or less) how I like to think about it:
// Assuming:
$provide.
service('FOO', FooService).
factory('BAR', barFactory).
value('BAZ', bazValue).
provider('QUX', quxProvider);
...I imagine the following "dialogs" taking place inside the app:
someone: Hey, $injector, I need my 'FOO'.
$injector: Sure thing.
$injector: (To itself: They need 'FOO'. I don't have a 'FOO' yet, let's create one. How do I create 'FOO'? Aha, I just var foo = new FooService(/* any arguments are resolved through DI */)
.)
$injector: There is your 'FOO', sir. Have a nice day!
$injector: (Passing foo
to someone.)
sometwo: Hey, Mr. $injector, could I get some 'BAR' here?
$injector: Sure thing.
$injector: (To itself: They need 'BAR'. I don't have a 'BAR' yet, let's create one. How do I create 'BAR'? Aha, I just var bar = barFactory(/* any arguments are resolved through DI */)
.)
$injector: There is your 'BAR', sir. Have a nice day!
$injector: (Passing bar
to sometwo.)
somethree: Yo, $injector dude, gimme some 'BAZ'?
$injector: Sure thing.
$injector: (To itself: They need 'BAZ'. I don't have a 'BAZ' yet, let's create one. How do I create 'BAZ'? Aha, I just var baz = bazValue
.)
$injector: There is your 'BAZ', sir. Have a nice day!
$injector: (Passing baz
to somethree.)
somefour: Could I get some QUX
, please?
$injector: Sure thing.
$injector: (To itself: They need 'QUX'. I don't have a 'QUX' yet, let's create one. How do I create 'QUX'? Aha, I just var qux = /* some stuff that are outside the scope of this comment */
.)
$injector: There is your 'QUX', sir. Have a nice day!
$injector: (Passing qux
to somefour.)
somefive: So, $injector, my old friend, here's the thing: I need some 'FOO', some 'BAR', some 'BAZ' and some 'QUX'.
$injector: Sure thing.
$injector: (To itself: They need 'FOO', 'BAR', 'BAZ' and 'QUX'. Hey, I already have all of them - how convenient.)
$injector: There is your 'FOO', your 'BAR', your 'BAZ' and your 'QUX', sir. Have a nice day!
$injector: (Passing foo
, bar
, baz
and qux
to somefive.)
(Yes, Angular components do speak to each other like this :grin:)
I am not sure if the angular team had mixed ideas about services and factories. But the code and ideas mentioned in the services section don't add up, not even on angular's site. If service registration takes a constructor function as the second argument, and its suppose to be invoked as a constructor would, with the
new
keyword, then why can't I treat it as a true constructor passing in parameters?Service
Controller
Factories
As for factories, your documentation states that, as well as other books, that factories should return service / class instances. However, this code contradicts. No instances were created and returned by the factory.