I have a UI Router defined something like this (trimmed for simplicity):
$stateProvider
.state('someState', {
resolve: {
model: ['modelService', 'info', function (modelService, info) {
return modelService.get(info.id).$promise;
}]
},
controller: 'SomeController'
});
This someState
state is using a factory / service that is dependent on that model
resolve. It's defined something like this, and AngularJS throws an Unknown provider: modelProvider <- model <- someService error here:
angular
.module('someModule')
.factory('someService', someService);
someService.$inject = ['model'];
function someService(model) { ... }
However, using the same model
resolve inside of this state's controller works fine:
SomeController.$inject = ['model'];
function SomeController(model) { ... }
So I'm understanding that UI Router is delaying the DI of the SomeController
until the resolve is happening, which allows AngularJS to not throw an error. However, how come the same delay is not happening when putting that resolve as a dependency on someService
? Do resolves only work on controllers? And if that is the case, how can I use a resolve inside a factory / service?
Yes, resolves only work on controllers.
Remember that factories and services return singleton objects, i.e. the first time a factory is injected into a controller, it runs any instantiation code you provide and creates an object, and then any subsequent times that factory is instantiated, that same object is returned.
In other words:
SomeFactory.now
will be the current time the first time the factory is injected into a controller, but it not update on subsequent usage.As such, the concept of resolve for a factory doesn't really make sense. If you want to have a service that does something dynamically (which is obviously very common), you need to put the logic inside functions on the singleton.
For example, in the code sample you gave, your factory depended on a model. One approach would be to inject the model into the controller using the resolve method you've already got set up, then expose a method on the singleton that accepts a model and does what you need to do, like so:
Alternatively, if you don't need the resolved value in the controller at all, don't put it directly in a resolve, instead put the resolve logic into a method on the service's singleton and call that method inside the resolve, passing the result to the controller.
It's hard to be more detailed with abstract conversation, so if you need further pointers then provide a specific use-case.
Just to add to Ed Hinchliffe's answer, there is another thing you could try. As other people have mentioned, factories and services are singletons. Nevertheless, there is one key difference between services and factories that we can use for this, services provide an instance of the service funtction (
new ServiceFunction()
), while a factory provides the value that is returned by invoking the function reference passed to the module. This informations is further explained on this stackoverflow thread:service-vs-provider-vs-factory
So, basically that means that we can create a function on the factory, add properties to its prototype, and then create an instance of it on the controller passing the arguments we like. Here is a very basic example:
Let's suppose that we have our angular module on a global
app
variable. First we create the Ui-Router state with a resolve attribute:To represent an asynchronous action we are using the
$q
and$timeout
service provided by the angular core to return a promise on the reslove object, which gets resolved after three seconds.Now we need to create our factory using the method explained before:
Here we created the
Greeter
factory as a constructor. We can now instantiate this factory passing the arguments we like. In this example we need to provide the constructor with arepeat
value. If we would like to inject an asynchronous value to the factory, taking advantage from theresolve
property of an ui-state, we can do this on the controller:The
$scope.greetingsArray
will be filled withrepeatValue
of "Hi" strings.I hope I made myself clear, hope it helps.
You should review the angular docs on dependency injection:
So each type of angular component has its own list of acceptable components to inject. Since services are singletons, it really wouldn't make any sense to inject a value as part of a resolve. If you had two separate places on your page that used a service with different resolves, the outcome would be indeterminate. It would make no more sense than injecting
$scope
into your service. It makes sense for controllers because the controller is responsible for the same area of the page that is being resolved.If your
someService
needs to use the data in model, it should have a function that takes the data as a parameter and your controller should pass it.You cannot use the resolved values in services or factories, only in the controller that belongs to the same state as the resolved values. Services and factories are singletons and controllers are newly instantiated for (in this case) a state or anywhere else where ng-controller is used.
The instantiation happens with the $controller service that is capable of injecting objects that belong only to that controller. Services and factories do not have this capability.