可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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?
回答1:
Do resolves only work on controllers?
Yes, resolves only work on controllers.
And if that is the case, how can I use a resolve inside a factory / service?
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:
angular.module('someModule')
.factory( 'SomeFactory' , function () {
// this code only runs once
object = {}
object.now = Date.now();
return object
);
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:
angular.module('someModule')
.factory( 'SomeFactory', function () {
return {
doSomethingWithModel: function (model) {
$http.post('wherever', model);
}
});
.controller('SomeController', function (SomeFactory, model) {
SomeFactory.doSomethingWithModel(model);
});
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.
回答2:
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:
app.config(function ($stateProvider) {
$stateProvider
.state('example', {
url: '/example',
templateUrl: 'app/example/example.html',
controller: 'ExampleCtrl',
resolve: {
repeatValue: ['$q', '$timeout', function($q, $timeout){
var deferred = $q.defer();
$timeout(function(){
deferred.resolve( parseInt(Math.random() * 100) );
}, 3000);
return deferred.promise;
}]
}
});
});
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:
app.factory('Greeter', function () {
// Function
var Greeter = function(repeat){
this.repeat = repeat;
};
// Prototype
Greeter.prototype.repeatedHi = function() {
var array = [];
console.log(this);
for (var i = 0; i < this.repeat; i++){
array.push('Hi');
}
return array;
};
// Public API here
return Greeter;
});
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 a repeat
value. If we would like to inject an asynchronous value to the factory, taking advantage from the resolve
property of an ui-state, we can do this on the controller:
app.controller('ExampleCtrl', function ($scope, Greeter, repeatValue) {
$scope.repeatValue = repeatValue;
var greeter = new Greeter(repeatValue);
$scope.greetingsArray = greeter.repeatedHi();
});
The $scope.greetingsArray
will be filled with repeatValue
of "Hi" strings.
I hope I made myself clear, hope it helps.
回答3:
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.
回答4:
You should review the angular docs on dependency injection:
- Components such as services, directives, filters, and animations are defined by an injectable factory method or constructor function. These components can be injected with "service" and "value" components as dependencies.
- Controllers are defined by a constructor function, which can be injected with any of the "service" and "value" components as dependencies, but they can also be provided with special dependencies. See Controllers below for a list of these special dependencies.
- The run method accepts a function, which can be injected with "service", "value" and "constant" components as dependencies. Note that you cannot inject "providers" into run blocks.
- The config method accepts a function, which can be injected with "provider" and "constant" components as dependencies. Note that you cannot inject "service" or "value" components into configuration.
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.