I'm trying to define services dynamically in angularjs, as the docs says, $provide and $injector are services, thus they should be injectable in module.run..
I need to make the dynamic services available from app bootstrap, that's why i'm trying to define them in module.run
angular.module('remote.interface',[])
.run(['$provide', '$injector', function(provide, injector){
// provide dynamically
}]);
but this ends up in an Error : [$injector:unpr] Unknown provider: $provideProvider <- $provide, and same eror for $injector if i try to remove $provide injection.
Where's the bug?
[EDIT]
after some research i tried something like this:
var module = angular.module('remote.interface',[])
.run([function(){
var provide = module.provider(),
injector = angular.injector();
provide.value('my.val',{i:'am a value'});
injector.get('my.val'); // this throws [$injector:unpr] Unknown provider: my.valProvider <- my.val
}]);
even if i remove the injector.get call, if i try to inject my.val, for example, in another-module's controller, angular throws the same error.
Have a look at the documentation for module and have a read at the comments in the example setup, particularly these comments.
config
You can only inject Providers (not instances) into config blocks
run
You can only inject instances (not Providers) into run blocks
Here is an example setup on JSFiddle injecting $provide and $injector correctly.
https://docs.angularjs.org/guide/module
angular.module('myModule', []).
config(function(injectables) { // provider-injector
// This is an example of config block.
// You can have as many of these as you want.
// You can only inject Providers (not instances)
// into config blocks.
}).
run(function(injectables) { // instance-injector
// This is an example of a run block.
// You can have as many of these as you want.
// You can only inject instances (not Providers)
// into run blocks
});
Related
I am trying to set the $compileProvider.debugInfoEnabled(false); except if a query param of ?debug=true is passed into the url. However $location does not work in the app.config function.
myApp.config(['$compileProvider', '$location',
function ($compileProvider, $location) {
if (!$location.search().debug || $location.search().debug === false) {
$compileProvider.debugInfoEnabled(false);
}
}
]);
I get an error 'Error: [$injector:unpr] Unknown provider: $location'
Any idea how this can be done?
Form the documentation:
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.
$location is a service, and has no provider. The only way to get to the underlying browser's location object is directly. You can use $window to help with unit testing, if you need that:
if (!$window.location.search.debug || $window.location.search.debug === false) {
$compileProvider.debugInfoEnabled(false);
}
You can only inject providers and constants in to the configuration block.Instead of using $location in config try to use it in .run() or use $locationProvider instead.
Today I found, that $injector injected to config or provider is different from $injector injected to service, factory or controller.
And get() function from this $injectors works differently.
$injector from config or provider, can't get() any service! $injector.get('myService') throws Error: [$injector:unpr] Unknown provider: myService, but $injector.has('myService') return true. That's very very strange.
$injector from service or controller works normally.
Here is a code sample for better understanding:
angular.module('app', [])
.provider('myProvider', function ($injector) {
this.$get = ['$injector', function (serviceInjector) {
return {
providerInjector: $injector,
serviceInjector: serviceInjector
};
}];
})
.service('myService', function () {})
.controller('myCtrl', function ($scope, myProvider) {
var providerInjector = myProvider.providerInjector;
var serviceInjector = myProvider.serviceInjector;
console.log(providerInjector === serviceInjector); // -> false
console.log(serviceInjector.has('myService')); // `serviceInjector` has `myService`
console.log(getMyService(serviceInjector)); // `serviceInjector` can get `myService`
console.log(providerInjector.has('myService')); // `providerInjector` has `myService` too!
console.log(getMyService(providerInjector)); // but `providerInjector` can't get `myService`! =(
function getMyService(injector) {
try {
injector.get('myService');
return "OK";
} catch (e) {
return e.toString();
}
}
});
Here is a plunker to play
Can anybody explain why there is two different injectors?
And how can I use $injector from provider/config to inject service(after service was initialized, of course)?
P.S. I use angular 1.3.13
I found this issue on github: https://github.com/angular/angular.js/issues/5559
In the config function, $injector is the provider injector, where in the run function, $injector is the instance injector.
One's the $injector at the config stage (only providers and constants accessible), and one's the $injector at the run stage. The confusion may be that you're thinking the $injector modifies itself to include the new stuff as it crosses the line from config to run, but that's not true. They're two separate (although related) objects, with their own caches of instances.
A more in-depth reason for this dichotomy will probably come from a deep learning of the $injector internals, but it seems like it's been DRY-ed pretty hardcore, and the two types of injectors share almost all the same behavior, except in how they deal with "cache misses" in their instance caches.
We are going to overhaul the injector in v2, so this will get fixed there (getting rid of the config phase is one of the objectives of the injector v2).
Seems like there is really two different injectors, and angular developers will not fix that behavior(in versions <2.0). And nobody added a note about that aspect to $injector docs for some reason.
I was unable to find a way how to really get instance injector inside a configuration block without hacky tricks. So, I write a cute provider to solve that kind of problems.
.provider('instanceInjector', function () {
var instanceInjector;
function get() {
return instanceInjector;
}
function exists() {
return !!instanceInjector;
}
angular.extend(this, {
get: get,
exists: exists
});
this.$get = function ($injector) {
instanceInjector = $injector;
return {
get: get,
exists: exists
};
}
})
// We need to inject service somewhere.
// Otherwise $get function will be never executed
.run(['instanceInjector', function(instanceInjector){}])
Ok. After reading your comments, here is my answer.
I edited code in plunk to make it work, when invoking the providerInjector.get() the code should be as follows:
$scope.getMyServiceFromProviderInjector = function () {
try {
myProvider.providerInjector.get('myServiceProvider');//here is change in provider name
return "OK";
} catch (e) {
return e.toString();
}
};
According to angular docs the following is quoted for config and run 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.
Run blocks - get executed after the injector is created and are used to kickstart the application. Only instances and constants can be
injected into run blocks. This is to prevent further system
configuration during application run time.
This simply means, you cannot get instances of services inside config blocks.
I wrote this a while back, which explains the lifecycle of both injectors of AngularJS, i.e providerInjector and instanceInjector.
http://agiliq.com/blog/2017/04/angularjs-injectors-internals/
I am using angular-route-segment in my angular app and an trying to configure the segments from a json feed.
I have having problems with this, because I can't figure out how to inject $http into the app.config function. This fails with Unknown provider: $http
myApp.config(["$http", "$routeSegmentProvider", function ($http, $routeSegmentProvider) {
/* setup navigation here calling $routeSegmentProvider.when a number of times */
}
So instead of injecting $http into config, I also tried injecting $routeSegmentProvider into myApp.run
myApp.run(["$http", "$routeSegment", function($http, $routeSegment) {
/* can use $http here to get data, but $routeSegment is not the same here */
/* $routeSegment does not have the when setup method */
}]);
I also tried
myApp.run(["$http", "$routeSegmentProvider", function($http, $routeSegmentProvider)
but I get Unknown provider: $routeSegmentProviderProvider <- $routeSegmentProvider
Providers can only be injected in the "config" phase and not the "run" phase. Conversely, a service such as $http will not have been initialized yet in the "config" phase and will only be available in the "run" phase.
A little trick to work around this is to define a variable in the parent function scope so that both the "config" and "run" blocks can have access to it:
var routeSegmentProvider = null;
myApp.config(["$routeSegmentProvider", function ($routeSegmentProvider) {
routeSegmentProvider = $routeSegmentProvider;
}]);
myApp.run(["$http", function($http) {
// access $http and routeSegmentProvider here
}]);
I'm not sure if you will encounter problems trying to call $routeSegmentProvider.setup() within the run phase as I haven't tried. In the past I was able to use the same technique to register http response interceptors that depend on some custom services with the $httpProvider.
I am trying to create a simple example where I get the promise/defer object from AngularJS's service solution:
var $q;
function init() {
var $injector = window.angular.injector();
console.log($injector);
$injector.invoke(["$q", function (_$q) {
console.log($q);
$q = _$q;
}]);
}
init();
But it results in:
Error: Unknown provider: $qProvider <- $q
[Break On This Error]
throw Error("Unknown provider: " + path.join(' <- '));
What could I have missed?
You have to add which module the provider resides in like this:
var $injector = window.angular.injector(['ng']);
Then it will work!
Edit: Regarding the 'ng' module, the docs specifically says that it must be explicitly added. From the angular injector docs:
modules – {Array.<string|Function>} – A list of module functions or their aliases.
See angular.module. The ng module must be explicitly added.
Use https://github.com/kriskowal/q if you're outside a angular.js module lifecycle, I've go down that road and it will not work well, $q is not meant to be used outside of a bootstraped module (require a $rootScope).
This is something that I could not figure out from digging into the AngularJS code, maybe you can help solve the mystery.
To show it, I added a service to AngularJS seed project:
function MyServiceProvider() {
console.log('its my service');
this.providerMethod = providerMethod;
function providerMethod() {
console.log('its my service.providerMethod');
}
this.$get = $get;
function $get() {
var innerInjectable = {
name: 'stam'
};
return innerInjectable;
}
}
var serviceModule = angular.module('myApp.services', []).
value('version', '0.1').
provider('myservice',MyServiceProvider);
You can see that this provider exposes $get and a certain 'providerMethod'.
Now, for the injection usage:
If we call config, we can inject the whole class and get access to the 'outer' provider method:
serviceModule.config(function(myserviceProvider) {
console.log('myServiceProvider:',myserviceProvider);
myserviceProvider.providerMethod();
});
But when we inject this to a controller (note the Provider-less name), only the $get return value is exposed:
function MyCtrl1(myservice) {
console.log('MyCtrl1.myservice =',myservice,myservice.name);
}
MyCtrl1.$inject = ['myservice'];
Console output follows as it should:
its my service
myServiceProvider:
Constructor {providerMethod: function, $get: function}
its my service.providerMethod
MyCtrl1.myservice = Object {name: "stam"} stam
Can any one explain the difference? The reason?
many thanks for any thought
Lior
PS: I've seen this technique in angular-ui new ui-router (excellent project!). I need access to the outer provider class to do injection in jasmine and other places - to no avail
A provider is responsible for creating instances. In your example, you created a provider explicitly, but the truth is that every service has a provider, even if it's created automatically for it. [module].service() and [module].factory() are just shortcuts for [module].provider().
[module].config() is run during provider registrations and configurations so you get a change to access providers and do stuff with them. It's a place for configuration of things, hence the name.
From the documentation (http://docs.angularjs.org/guide/module):
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.
Controllers, in the other hand, are instantiated AFTER services have been configured, so you're not supposed to mess with providers anymore. Everything has already been configured. You're ready to get their products now. In this phase, the injector just can't inject providers anymore, just instances (services) created by them.
If you register a service myService...
myModule.service('myService', function() {
// this is your service constructor
});
then you can access its provider, myServiceProvider, in a config function...
myModule.config(function(myServiceProvider) {
// to stuff with your provider here
});
but by the time controllers are being instantiated, you're supposed to ask for services, not their providers, so this will not work...
myModule.controller(function(myServiceProvider) {
...
});
whereas this will be fine...
myModule.controller(function(myService) {
...
});
If you're finding yourself needing to do configuration in a controller, you should stop and rethink the place of responsibilities.
From the Angular mailing list I got an amazing thread that explains service vs factory vs provider and their injection usage. I decided to put it in its own question here
the specific answer is: it is so by design, to allow configuration of the provider at config time.