168

I want to inject a service into app.config, so that data can be retrieved before the controller is called. I tried it like this:

Service:

app.service('dbService', function() {
    return {
        getData: function($q, $http) {
            var defer = $q.defer();
            $http.get('db.php/score/getData').success(function(data) {
                defer.resolve(data);            
            });
            return defer.promise;
        }
    };
});

Config:

app.config(function ($routeProvider, dbService) {
    $routeProvider
        .when('/',
        {
            templateUrl: "partials/editor.html",
            controller: "AppCtrl",
            resolve: {
                data: dbService.getData(),
            }
        })
});

But I get this error:

Error: Unknown provider: dbService from EditorApp

How to correct setup and inject this service?

Rubens Mariuzzo
  • 28,358
  • 27
  • 121
  • 148
dndr
  • 2,319
  • 5
  • 18
  • 28
  • 3
    despite what you have seen already, there *is* a way to achieve what you intended, and AngularJS has spent a lot of time enabling this type of functionality. Review my answer on how to achieve this. – Brian Vanderbusch Feb 08 '14 at 19:12

10 Answers10

140

Set up your service as a custom AngularJS Provider

Despite what the Accepted answer says, you actually CAN do what you were intending to do, but you need to set it up as a configurable provider, so that it's available as a service during the configuration phase.. First, change your Service to a provider as shown below. The key difference here is that after setting the value of defer, you set the defer.promise property to the promise object returned by $http.get:

Provider Service: (provider: service recipe)

app.provider('dbService', function dbServiceProvider() {

  //the provider recipe for services require you specify a $get function
  this.$get= ['dbhost',function dbServiceFactory(dbhost){
     // return the factory as a provider
     // that is available during the configuration phase
     return new DbService(dbhost);  
  }]

});

function DbService(dbhost){
    var status;

    this.setUrl = function(url){
        dbhost = url;
    }

    this.getData = function($http) {
        return $http.get(dbhost+'db.php/score/getData')
            .success(function(data){
                 // handle any special stuff here, I would suggest the following:
                 status = 'ok';
                 status.data = data;
             })
             .error(function(message){
                 status = 'error';
                 status.message = message;
             })
             .then(function(){
                 // now we return an object with data or information about error 
                 // for special handling inside your application configuration
                 return status;
             })
    }    
}

Now, you have a configurable custom Provider, you just need to inject it. Key difference here being the missing "Provider on your injectable".

config:

app.config(function ($routeProvider) { 
    $routeProvider
        .when('/', {
            templateUrl: "partials/editor.html",
            controller: "AppCtrl",
            resolve: {
                dbData: function(DbService, $http) {
                     /*
                     *dbServiceProvider returns a dbService instance to your app whenever
                     * needed, and this instance is setup internally with a promise, 
                     * so you don't need to worry about $q and all that
                     */
                    return DbService('http://dbhost.com').getData();
                }
            }
        })
});

use resolved data in your appCtrl

app.controller('appCtrl',function(dbData, DbService){
     $scope.dbData = dbData;

     // You can also create and use another instance of the dbService here...
     // to do whatever you programmed it to do, by adding functions inside the 
     // constructor DbService(), the following assumes you added 
     // a rmUser(userObj) function in the factory
     $scope.removeDbUser = function(user){
         DbService.rmUser(user);
     }

})

Possible Alternatives

The following alternative is a similar approach, but allows definition to occur within the .config, encapsulating the service to within the specific module in the context of your app. Choose the method that right for you. Also see below for notes on a 3rd alternative and helpful links to help you get the hang of all these things

app.config(function($routeProvider, $provide) {
    $provide.service('dbService',function(){})
    //set up your service inside the module's config.

    $routeProvider
        .when('/', {
            templateUrl: "partials/editor.html",
            controller: "AppCtrl",
            resolve: {
                data: 
            }
        })
});

A few helpful Resources

  • John Lindquist has an excellent 5 minute explanation and demonstration of this at egghead.io, and it's one of the free lessons! I basically modified his demonstration by making it $http specific in the context of this request
  • View the AngularJS Developer guide on Providers
  • There is also an excellent explanation about factory/service/provider at clevertech.biz.

The provider gives you a bit more configuration over the .service method, which makes it better as an application level provider, but you could also encapsulate this within the config object itself by injecting $provide into config like so:

Seb D.
  • 5,046
  • 1
  • 28
  • 36
Brian Vanderbusch
  • 3,313
  • 5
  • 31
  • 43
  • 2
    Thank you, i was looking for such an example; detailed answer and great links ! – cnlevy Jun 26 '14 at 20:25
  • 1
    no problem! This is my favorite answer on SO. I answered this when the currently accepted answer had already been answered, and had 18 votes. Good for a few badges! – Brian Vanderbusch Jul 09 '14 at 01:44
  • It would really be useful if your codepen samples worked. For instance, $provide.service('dbService',function(){ doesn't have $http injected but uses it in its body. As it stands, I could not get your technique 2 to work. It's very frustrating that it is so hard to load config data from a remove file in an Angular program at startup. – Bernard Sep 24 '14 at 08:02
  • @Alkaline I have learned a thing or two since this post. The answer is correct in theory but has 1 or 2 things (1 you pointed out) that should be fixed. Thanks for the comment. I'll review and update the answer. Deleted the codepen for the moment... never had a chance to finish it. – Brian Vanderbusch Sep 24 '14 at 14:08
  • The clevertech link is now: http://blog.clevertech.biz/post/angularjs-factory-service-provider – ArashM Dec 07 '14 at 17:06
  • 5
    I think the information you provided here is incorrect. You can use a provider but during config phase you're not working with the result of the `$get` call. Instead you want to add methods on the provider instance and just return `this` when you call `$get`. In fact in your example you could just use a service... In a provider you also can't inject services like `$http`. And btw this `//return the factory as a provider, that is available during the configuration phase` is misleading/incorrect information – Dieterg Mar 17 '15 at 11:46
  • exactly what I was looking for – Tarun Feb 27 '16 at 14:39
  • Supply dependencies in `ng-strict-di` mode via `.config(['$routeProvider', function($routeProvider) {...` or module injection error occurrs [ref](https://docs.angularjs.org/error/$injector/strictdi?p0=function($templateCache)) – droid192 Dec 24 '16 at 10:31
130

Alex provided the correct reason for not being able to do what you're trying to do, so +1. But you are encountering this issue because you're not quite using resolves how they're designed.

resolve takes either the string of a service or a function returning a value to be injected. Since you're doing the latter, you need to pass in an actual function:

resolve: {
  data: function (dbService) {
    return dbService.getData();
  }
}

When the framework goes to resolve data, it will inject the dbService into the function so you can freely use it. You don't need to inject into the config block at all to accomplish this.

Bon appetit!

Josh David Miller
  • 120,525
  • 16
  • 127
  • 95
  • 2
    Thanks! However, if I do this I get: Error: 'undefined' is not an object (evaluating '$q.defer') in the the service. – dndr Apr 10 '13 at 23:23
  • 1
    The injection occurs in the top-level function passed to `.service`, so move `$q` and `$http` there. – Josh David Miller Apr 10 '13 at 23:29
  • @JoshDavidMiller, you said `resolve` "can take either the string of a service or a function". You provided a great demo on using a function, which works great for me. But what about the 'string of a service' option? How would I reduce `resolve: {pageData: ['myData', function(myData) {return myData.overview; }] }` into `resolve: {pageData: myData.overview}`, for sheer clarity/readability? (`overview` is a promise returned by `$http`, inside the `myData` service, fwiw.) Thanks. – XML Aug 05 '13 at 17:22
  • 1
    @XMLilley The string in a resolve is actually the name of a service and not a particular function on the service. Using your example, you could do `pageData: 'myData'`, but you would then have to call `pageData.overview` from your controller. The string method is probably only useful if the service factory returned a promise instead of an API. So the way you're currently doing it is probably the best way. – Josh David Miller Aug 05 '13 at 21:20
  • The assumption this is based on is incorrect, and even your code does not have the dependencies needed to process this service. – Brian Vanderbusch Feb 08 '14 at 20:00
  • There isn't anything wrong with the way he's using resolves either, the only difference is, his use of resolve internally handles success before resolving the value, whereas your method leaves that up to the caller. I actually prefer the OP's method here, as it allows you to reject the promise and do some alternative handling prior to handing it off to the rest of the application. I did the same thing in my answer. Neither approach is incorrect, they just have different steps when called. – Brian Vanderbusch Aug 12 '14 at 10:17
  • 2
    @BrianVanderbusch I must to admit to some confusion over where exactly you feel we disagree. The actual *problem* the OP encountered was that he injected a service into a config block, which *cannot be done*. The solution is to inject the service into the *resolve*. While your answer provided a lot of detail about service configuration, I do not see how it related in any way to the error the OP encountered, and your solution to the OP's problem was *exactly the same*: you injected the service in the resolve function and *not* the config function. Can you elaborate on where we disagree here? – Josh David Miller Aug 18 '14 at 01:10
  • 1
    @JoshDavidMiller Using the method I demonstrated, it's possible to configure a service prior to state activation, such that errors can be thrown/handled during the config phase, potentially altering how you would instantiate other configuration values prior to application bootstrapping. For example, determining the role of a user in order to have the application compile the right features. – Brian Vanderbusch Aug 18 '14 at 15:15
  • This solution is perfect for when you *don't* want to run logic during the config/bootstrap phase, but just shim in an existing service. Thanks for sharing! – Scott Sword Mar 18 '15 at 20:26
21

Short answer: you can't. AngularJS won't allow you to inject services into the config because it can't be sure they have been loaded correctly.

See this question and answer: AngularJS dependency injection of value inside of module.config

A module is a collection of configuration and run blocks which get applied to the application during the bootstrap process. In its simplest form the module consist of collection of two kinds of blocks:

Configuration blocks - get executed during the provider registrations and configuration phase. Only providers and constants can be injected into configuration blocks. This is to prevent accidental instantiation of services before they have been fully configured.

Community
  • 1
  • 1
Alex Osborn
  • 9,831
  • 3
  • 33
  • 44
5

I don't think you're supposed to be able to do this, but I have successfully injected a service into a config block. (AngularJS v1.0.7)

angular.module('dogmaService', [])
    .factory('dogmaCacheBuster', [
        function() {
            return function(path) {
                return path + '?_=' + Date.now();
            };
        }
    ]);

angular.module('touch', [
        'dogmaForm',
        'dogmaValidate',
        'dogmaPresentation',
        'dogmaController',
        'dogmaService',
    ])
    .config([
        '$routeProvider',
        'dogmaCacheBusterProvider',
        function($routeProvider, cacheBuster) {
            var bust = cacheBuster.$get[0]();

            $routeProvider
                .when('/', {
                    templateUrl: bust('touch/customer'),
                    controller: 'CustomerCtrl'
                })
                .when('/screen2', {
                    templateUrl: bust('touch/screen2'),
                    controller: 'Screen2Ctrl'
                })
                .otherwise({
                    redirectTo: bust('/')
                });
        }
    ]);

angular.module('dogmaController', [])
    .controller('CustomerCtrl', [
        '$scope',
        '$http',
        '$location',
        'dogmaCacheBuster',
        function($scope, $http, $location, cacheBuster) {

            $scope.submit = function() {
                $.ajax({
                    url: cacheBuster('/customers'),  //server script to process data
                    type: 'POST',
                    //Ajax events
                    // Form data
                    data: formData,
                    //Options to tell JQuery not to process data or worry about content-type
                    cache: false,
                    contentType: false,
                    processData: false,
                    success: function() {
                        $location
                            .path('/screen2');

                        $scope.$$phase || $scope.$apply();
                    }
                });
            };
        }
    ]);
kim3er
  • 6,306
  • 4
  • 41
  • 69
  • service method name is *dogmaCacheBuster* but in `.config` you have written *cacheBuster* ( which is not defined anywhere in the answer) and *dogmaCacheBusterProvider* ( which is not used further). Will you clarify over this? – xkeshav Jun 14 '17 at 04:19
  • @pro.mean I think I was demonstrating the technique I used, to insert a service into a config block, but it has been a while. `cacheBuster` is defined, as a parameter of the config function. With regards to `dogmaCacheBusterProvider`, it's something clever that Angular does with naming conventions, that I have long since forgotten. This might get you closer, https://stackoverflow.com/a/20881705/110010. – kim3er Jun 14 '17 at 09:00
  • on this another reference. I come to know that append *Provider* whatever we define in `.provider()` recipe. what if I define something with `.factory('ServiceName')` or `.service('ServiceName')` recipe and want to use one of its method in `.config` block , set the parameter as *ServiceNameProvider* but it stops my application. – xkeshav Jun 15 '17 at 04:27
5

** Explicitly request services from other modules using angular.injector **

Just to elaborate on kim3er's answer, you can provide services, factories, etc without changing them to providers, as long as they are included in other modules...

However, I'm not sure if the *Provider (which is made internally by angular after it processes a service, or factory) will always be available (it may depend on what else loaded first), as angular lazily loads modules.

Note that if you want to re-inject the values that they should be treated as constants.

Here's a more explicit, and probably more reliable way to do it + a working plunker

var base = angular.module('myAppBaseModule', [])
base.factory('Foo', function() { 
  console.log("Foo");
  var Foo = function(name) { this.name = name; };
  Foo.prototype.hello = function() {
    return "Hello from factory instance " + this.name;
  }
  return Foo;
})
base.service('serviceFoo', function() {
  this.hello = function() {
    return "Service says hello";
  }
  return this;
});

var app = angular.module('appModule', []);
app.config(function($provide) {
  var base = angular.injector(['myAppBaseModule']);
  $provide.constant('Foo', base.get('Foo'));
  $provide.constant('serviceFoo', base.get('serviceFoo'));
});
app.controller('appCtrl', function($scope, Foo, serviceFoo) {
  $scope.appHello = (new Foo("app")).hello();
  $scope.serviceHello = serviceFoo.hello();
});
user230910
  • 2,353
  • 2
  • 28
  • 50
00500005
  • 3,727
  • 3
  • 31
  • 38
5

You can use $inject service to inject a service in you config

app.config(function($provide){

    $provide.decorator("$exceptionHandler", function($delegate, $injector){
        return function(exception, cause){
            var $rootScope = $injector.get("$rootScope");
            $rootScope.addError({message:"Exception", reason:exception});
            $delegate(exception, cause);
        };
    });

});

Source: http://odetocode.com/blogs/scott/archive/2014/04/21/better-error-handling-in-angularjs.aspx

Koray Güclü
  • 2,857
  • 1
  • 34
  • 30
2

Using $injector to call service methods in config

I had a similar issue and resolved it by using the $injector service as shown above. I tried injecting the service directly but ended up with a circular dependency on $http. The service displays a modal with the error and I am using ui-bootstrap modal which also has a dependency on $https.

    $httpProvider.interceptors.push(function($injector) {
    return {
        "responseError": function(response) {

            console.log("Error Response status: " + response.status);

            if (response.status === 0) {
                var myService= $injector.get("myService");
                myService.showError("An unexpected error occurred. Please refresh the page.")
            }
        }
    }
2

A solution very easy to do it

Note : it's only for an asynchrone call, because service isn't initialized on config execution.

You can use run() method. Example :

  1. Your service is called "MyService"
  2. You want to use it for an asynchrone execution on a provider "MyProvider"

Your code :

(function () { //To isolate code TO NEVER HAVE A GLOBAL VARIABLE!

    //Store your service into an internal variable
    //It's an internal variable because you have wrapped this code with a (function () { --- })();
    var theServiceToInject = null;

    //Declare your application
    var myApp = angular.module("MyApplication", []);

    //Set configuration
    myApp.config(['MyProvider', function (MyProvider) {
        MyProvider.callMyMethod(function () {
            theServiceToInject.methodOnService();
        });
    }]);

    //When application is initialized inject your service
    myApp.run(['MyService', function (MyService) {
        theServiceToInject = MyService;
    }]);
});
Chklang
  • 857
  • 1
  • 8
  • 17
1

Well, I struggled a little with this one, but I actually did it.

I don't know if the answers are outdated because of some change in angular, but you can do it this way:

This is your service:

.factory('beerRetrievalService', function ($http, $q, $log) {
  return {
    getRandomBeer: function() {
      var deferred = $q.defer();
      var beer = {};

      $http.post('beer-detail', {})
      .then(function(response) {
        beer.beerDetail = response.data;
      },
      function(err) {
        $log.error('Error getting random beer', err);
        deferred.reject({});
      });

      return deferred.promise;
    }
  };
 });

And this is the config

.when('/beer-detail', {
  templateUrl : '/beer-detail',
  controller  : 'productDetailController',

  resolve: {
    beer: function(beerRetrievalService) {
      return beerRetrievalService.getRandomBeer();
    }
  }
})
Luis Sieira
  • 29,926
  • 3
  • 31
  • 53
0

Easiest way: $injector = angular.element(document.body).injector()

Then use that to run invoke() or get()

Pencilcheck
  • 2,664
  • 3
  • 25
  • 14
  • What a hack! Unfortunately, it won't work with most unit tests where the application is not tied to the DOM. – rixo Oct 06 '15 at 15:02