AngularJS UI Router using resolved dependency in f

2019-03-20 15:55发布

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?

4条回答
爷的心禁止访问
2楼-- · 2019-03-20 16:21

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.

查看更多
叛逆
3楼-- · 2019-03-20 16:33

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.

查看更多
Emotional °昔
4楼-- · 2019-03-20 16:35

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.

查看更多
兄弟一词,经得起流年.
5楼-- · 2019-03-20 16:42

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.

查看更多
登录 后发表回答