91

The main question - is it possible? I tried with no luck..

main app.js

...
var app = angular.module('myApp', ['services']);
app.config(['customProvider', function (customProvider) {

}]);
...

provider itself

var services = angular.module('services', []);
services.provider('custom', function ($http) {
});

And I've got such error:

Uncaught Error: Unknown provider: $http from services 

Any ideas?

Thanks!

Kosmetika
  • 20,774
  • 37
  • 108
  • 172

4 Answers4

160

The bottom line is:

  • You CANNOT inject a service into the provider configuration section.
  • You CAN inject a service into the section which initializes the provider's service.

Details:

Angular framework has a 2 phase initialization process:

PHASE 1: Config

During the config phase all of the providers are initialized and all of the config sections are executed. The config sections may contain code which configures the provider objects and therefore they can be injected with provider objects. However, since the providers are the factories for the service objects and at this stage the providers are not fully initialized/configured -> you cannot ask the provider to create a service for you at this stage -> at the configuration stage you cannot use/inject services. When this phase is completed all of the providers are ready (no more provider configuration can be done after the configuration phase is completed).

PHASE 2: Run

During run phase all the run sections are executed. At this stage the providers are ready and can create services -> during run phase you can use/inject services.

Examples:

1. Injecting the $http service to the provider initialization function WILL NOT work

//ERRONEOUS
angular.module('myModule').provider('myProvider', function($http) {
    // SECTION 1: code to initialize/configure the PROVIDER goes here (executed during `config` phase)
    ...

    this.$get = function() {
        // code to initialize/configure the SERVICE goes here (executed during `run` stage)

        return myService;
    };
});

Since we are trying to inject the $http service into a function which is executed during the config phase we will get an error:

Uncaught Error: Unknown provider: $http from services 

What this error is actually saying is that the $httpProvider which is used to create the $http service is not ready yet (since we are still in the config phase).

2. Injecting the $http service to the service initialization function WILL work:

//OK
angular.module('myModule').provider('myProvider', function() {
    // SECTION 1: code to initialize/configure the PROVIDER goes here (executed during `config` phase)
    ...

    this.$get = function($http) {
        // code to initialize/configure the SERVICE goes here (executed during `run` stage)

        return myService;
    };
});

Since we are now injecting the service into the service initialization function, which is executed during run phase this code will work.

georgeawg
  • 48,608
  • 13
  • 72
  • 95
Dana Shalev
  • 1,894
  • 1
  • 13
  • 13
  • Can you give an example? (fiddle) – Dana Shalev Jul 06 '13 at 13:31
  • actually it works but not in the way i would like - my need is to make POST request while configuring angular application and then start to run it. I thought it's possible with providers but it's not :( methods that can use ``$http`` or any other service can be executed only in ``run`` section unfortunately.. – Kosmetika Jul 06 '13 at 13:56
  • 64
    Good answer, but while it explains how it's not possible to inject services during configuration, it doesn't explain how to make an HTTP POST/GET during configuration. This is important for applications which are configured using values provided by an API. – Sean O'Dell Oct 03 '13 at 15:55
  • That's not possible. I don't think the config phase is asynchronous. You could probably make everything happen during the run phase? – Sean Clark Hess Dec 16 '13 at 16:13
  • @Sean Maybe in that case it makes sense to use a sync `XMLHttpRequest` to work around the issue? – Juho Vepsäläinen Apr 29 '14 at 12:09
  • 3
    @bebraw & Kosmetika - The only thing I can think you would need to request during the config phase is some kind of settings object. Maybe it contains the API endpoint, user information, the user's locale and language settings, etc. If that's the case, I'd recommend including that information in the javascript source somehow. You can use server-side rendering on index.html to put a few settings in so that they are available before your app initializes. Everything else, I'd try to figure out how to do it post-init – Sean Clark Hess Apr 29 '14 at 16:04
  • 2
    @Sean: How to make an HTTP POST/GET is a different question than the OP's (Is it possible to use $http inside the configuration phase?), and probably merits a separate post altogether; due to the synchronous nature of Angular's configuration phase, a good way to provide server-side data to your configuration code is to render it as a javascript object in your HTML page during server-side rendering (e.g. ``). This can be done using a templating engine such as Smarty for PHP, Jinja2 for Python, Nunchucks for NodeJS, etc. – Trevor Sep 22 '14 at 18:42
  • 4
    @threed: Inserting config data directly into the HTML or js on the server works only if your client code comes from the same server. With CORS, it's now possible (and very desirable) to have the client code being served from a different server, and data being served from separate(s) servers. In those cases, we do need to retrieve config data using HTTP. – Bernard Sep 24 '14 at 04:16
  • 4
    While this is an answer, it is not the answer to the question that was asked. – Eric Rini Jan 27 '15 at 15:22
  • I did my services/providers on the same way to have an extra method setApiUrl so i can globaly configure my service url. Does anyone know how to test this? (injecting mocks inside $get) – Bumbolt Aug 25 '16 at 06:51
  • I actually implemented something in the past resolving this issue in a non-recommended way. You can create a directive to wrap entire page, and then when you initialize this directive, you can call $http, either directly or from a provider implementation. This is definitely a wrong answer, but it will allow you put anything you want upon initialization, as long as you are not that picky about the order of the bootstrap process. – windmaomao Aug 11 '17 at 21:17
65

This might give you a little leverage:

var initInjector = angular.injector(['ng']);
var $http = initInjector.get('$http');

But be careful, the success/error callbacks might keep you in a race-condition between the app start and the server response.

Or A.
  • 1,220
  • 1
  • 15
  • 28
Cody
  • 9,785
  • 4
  • 61
  • 46
  • 6
    The "accepted answer" failed for my provider... I spent 2 days of frustration trying to make that work with no hope. Your approach worked immediately. – Dave Alperovich Jan 14 '15 at 22:07
  • Can you clarify if the instance created here is the "real" service singleton or just an instance of the service that is discarded when Angular does its real injector magic. – Eric Rini Feb 19 '15 at 13:59
  • Eric, I cannot confirm that at this time. However, what I usually do (if applicable) is `angular.injector(['mymodule'])` -- but I'm not sure if you can use this approach for the `$http` service. I wanna say I have though. Not sure if this helps or not :-/ – Cody Feb 25 '15 at 22:14
  • 2
    This should be the accepted answer. I struggled for a good while trying to get this to work, and this approach solved my problem immediately. I think this may be a very common issue. Thanks @Cody – iamdash Apr 18 '15 at 06:08
  • 5
    I confirm that the accepted solution isn't working for using $http in the provider. But @Cody 's answer does make the trick – Dino Jun 24 '15 at 14:49
1

This is an old question, seems we have some chicken egg thing going on if we want to rely on the core capability of the library.

Instead of solving the problem in a fundamental way, what I did is by-pass. Create a directive that wraps the whole body. Ex.

<body ng-app="app">
  <div mc-body>
    Hello World
  </div>
</body>

Now mc-body needs to be initialized before rendering (once), ex.

link: function(scope, element, attrs) {
  Auth.login().then() ...
}

Auth is a service or provider, ex.

.provider('Auth', function() {
  ... keep your auth configurations
  return {
    $get: function($http) {
      return {
        login: function() {
          ... do something about the http
        }
      }
    }
  }
})

Seems to me that I do have control on the order of the bootstrap, it is after the regular bootstrap resolves all provider configuration and then try to initialize mc-body directive.

And this directive seems to me can be ahead of routing, because routing is also injected via a directive ex. <ui-route />. But I can be wrong on this. Needs some more investigation.

windmaomao
  • 7,120
  • 2
  • 32
  • 36
-2

In response to your question, "Any Ideas?", I would have respond with "yes". But wait, there's more!

I suggest just using JQuery in the config. For example:

var app = angular.module('myApp', ['services']);
app.config(['$anyProvider', function ($anyProvider) {
    $.ajax({
        url: 'www.something.com/api/lolol',
        success: function (result) {
            $anyProvider.doSomething(result);
        }
    });
}]);
Suamere
  • 5,691
  • 2
  • 44
  • 58