Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AngularJS UI Router using resolved dependency in factory / service

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?

like image 608
Amberite Avatar asked Jan 11 '15 19:01

Amberite


3 Answers

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.

like image 154
Ed_ Avatar answered Nov 06 '22 19:11

Ed_


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.

like image 33
Walter Brand Avatar answered Nov 06 '22 20:11

Walter Brand


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.

like image 32
Jason Goemaat Avatar answered Nov 06 '22 18:11

Jason Goemaat