In my app I need to inject "dateFilter" in the config block. I know I can't do it like this:
.config(function(dateFilter){})
Since dateFilter is not a provider or a constant, it's not available during config.
However, after some research, I made it work by using the following in the config:
angular.injector(["ng"]).get('dateFilter')('2014-01-01','yyyy/MM/dd');
Doesn't this mean that I can get anything during config? Then what's the point making only providers and constants injectable during config? Is it bad to do something like angular.injector(["ng"]).get('dateFilter') during config?
angular.injector shouldn't be used in production, unless the circumstances are really exotic (i.e. almost never). It creates a new injector instance and introduces some overhead. Conventional Angular DI is good for its testability, while angular.injector turns a part of the application into untestable piece of code. Always reuse current injector inside the app, if possible (i.e. almost always).
Usually 'how to use service instance in config block' type of questions indicates an XY problem. The fact that Angular uses config to configure service providers that thereafter will create service instances (chicken-egg dilemma) suggests that the application should be refactored to respect Angular life cycle.
However, built-in filters are stateless helper functions, and their use in config phase is relatively harmless. dateFilter service is defined by $filterProvider, and $filterProvider should be injected to get to dateFilterProvider. The problem is that dateFilter depends on $locale service, which wasn't instantiated yet. $locale is constant (in broad sense) that doesn't depend on other services, so it has to be instantiated too.
angular.module('...', [
'ngLocale' // if used, should be loaded in this module
])
.config(($filterProvider, $localeProvider, $provide, $injector) => {
var $locale = $injector.invoke($localeProvider.$get);
var dateFilterProvider = $injector.get('dateFilterProvider')
var dateFilter = $injector.invoke(dateFilterProvider.$get, { $locale: $locale });
$provide.constant('dateHelper', dateFilter);
})
This is a hack should be taken into account in tests (dateHelper service should superficially tested) but is relatively trouble-free and idiomatic.
you cant inject services in config only provides but you can do it in app.run here's the calling order:
app.config() //only provides can be injected
app.run() //services can be injected
directive's compile functions (if they are found in the dom)
app.controller()
directive's link functions (again, if found)
Related
I'm certainly missing some fundamental point about the injector, but I fail to understand why exactly this
angular.module('app').config(function ($provide) {
...
});
and this
angular.module('app').config(function ($injector) {
$injector.invoke(function ($provide) { ... });
});
work as intended, while this
app.run(function($provide) {
...
});
will throw
Error: [$injector:unpr] Unknown provider: $provideProvider <- $provide
As follows from the above, config has some special relationship with providers, while run deals with instances, yet I'm unsure about the thing that makes config blocks so special.
As a consequence of that, is there no way to get to $provide outside config blocks, e.g. with angular.injector() (though it seems that it gets provider instances also)?
The question, besides mere curiosity, also has some practical considerations. In 1.4 all of $provide functions are exposed to module, but that's not true for 1.3.
The purpose of the config() function is to allow you to perform some global configuration that will affect the entire application - that includes services, directives, controllers, etc. Because of that, the config() block must run before anything else. But, you still need a way to perform the aforementioned configuration and make it available to the rest of the app. And the way to do that is by using providers.
What makes providers "special" is that they have two initialization parts, and one of them is directly related to the config() block. Take a look at the following code:
app.provider('myService', function() {
var self = {};
this.setSomeGlobalProperty = function(value) {
self.someGlobalProperty = value;
};
this.$get = function(someDependency) {
this.doSomething = function() {
console.log(self.someGlobalProperty);
};
};
});
app.config(function(myServiceProvider) {
myServiceProvider.setSomeGlobalProperty('foobar');
});
app.controller('MyCtrl', function(myService) {
myService.doSomething();
});
When you inject a provider into the config() function, you can access anything but the $get function (technically you can access the $get function, but calling it won't work). That way you can do whatever configuration you might need to do. That's the first initialization part. It's worth mentioning that even though our service is called myService, you need to use the suffix Provider here.
But when you inject the same provider into any other place, Angular calls the $get() function and injects whatever it returns. That's the second initialization part. In this case, the provider behaves just like an ordinary service.
Now about $provide and $injector. Since they are "configuration services", it makes sense to me that you can't access them outside the config() block. If you could, then you would be able to, say, create a factory after it had been used by another service.
Finally, I haven't played with v1.4 yet, so I have no idea why that behavior apparently has changed. If anyone knows why, please let me know and I'll update my answer.
After some Angular injector study I was able to give an exhaustive answer to my own question.
Essentially, $injector in config blocks and provider constructor functions and $injector everywhere else are two different services with the same name, which are defined on internal provider/instance cache explicitly, together with $provide (this one is being defined in provider cache, hence it can be injected in config only).
While generally not recommended because of probable race conditions, it is possible to expose internal services to instance cache and make config-specific $provide and $injector available for injection after config phase has ended:
app.config(function ($provide, $injector) {
$provide.value('$providerInjector', $injector);
$provide.value('$provide', $provide);
});
The possible applications are configuring service providers any time (if possible)
app.run(function ($providerInjector) {
var $compileProvider = $providerInjector.get('$compileProvider');
...
});
and defining new components at run-time
app.run(function ($provide) {
$provide.controller(...);
...
});
I have a service that has an internal list of directive names (lets call it listService). In the spirit of loosely coupled applications I want other modules to be able to add their own directives to that list instead of statically defining it in the service module.
I understand that I can create a provider in order to configure a service like this:
app.provider("listService", ServiceProvider);
app.config(["listServiceProvider", function(listServiceProvider) {
listServiceProvider.add("myDirective");
}]);
At the same time, I don't want all the modules that define directives to depend on the service. So what I would like to do is "if this service is used in this project: configure it; else: ignore".
Unfortunately, the above code produces a module error if listServiceProvider is not available. I also tried to use a decorator with the same result.
So how can I optionally configure a service in order to have a loosely coupled application?
Like I mentioned in the comment, it is important to have a broader context for why you need to register directives (or their names).
In absence of a broader understanding, if, generally speaking, you need to conditionally check for existence of a service (or service provider, in config), you can use the $injector:
.config(function($injector){
var listServiceProvider;
if ($injector.has("listServiceProvider")) {
listServiceProvider = $injector.get("listServiceProvider");
}
if (listServiceProvider){
listServiceProvider.add("myDirective");
}
});
If I understand correctly; you want a service that tracks which of your directives have been added into the Angular app, and for the directives and the service to be decoupled from each other so they can be included on demand.
I don't think the module pattern will do this for you, since the services and directives are injected at load time. Optional dependency injection is not possible.
However, you could fire an event from your directive and pick it up in your service, removing the need for dependency injection altogether.
myDirective
.run(['$rootScope', 'LIST_SERVICE_EVENT',
function($rootScope, LIST_SERVICE_EVENT) {
$rootScope.$emit(LIST_SERVICE_EVENT, 'myDirective');
}]);
listService
.run(['listService', '$rootScope', 'LIST_SERVICE_EVENT',
function(listService, $rootScope, LIST_SERVICE_EVENT) {
$rootScope.$on(LIST_SERVICE_EVENT, function(ev, name) {
listService.add(name);
});
}]);
Fiddle: http://jsfiddle.net/bdpxhLg3/4/.
I was wondering when would you use $injector.get('someService') vs injecting that service directly.
Basically, what is the difference of the two code below?
angular
.module('myApp')
.controller('MyController', MyController);
/** Difference of this **/
MyController.$inject = ['$rootScope', '$route']; // and more services
MyController($rootScope, $route)
{
/* ... */
}
/** And this **/
MyController.$inject = ['$injector'];
MyController($injector)
{
var $rootScope = $injector.get('$rootScope');
var $route = $injector.get('$route');
/* and so on */
}
If you are not sure what your controller needs, or that you will update it in the future, and you have it refactored, then using $injector it is a lot easier to add/remove dependencies.
The first approach uses dependency injection while the second uses service locator pattern. Dependency injection has the following advantages:
It makes a component's dependencies explicit. For example, you can look at the constructor function of a controller and immediately know what dependencies it needs. With service locator, you usually have no idea because the controller might invoke the service locator any time.
It makes unit test easier. For example you can use the $controller service to instantiate a controller and feed it with mock objects via the locals argument. That's certainly easier than having to define a bunch of AngularJS services or factories so that $injector can resolve them. It becomes worse when coupled with #1: you might not be aware of all dependencies a component needs in order to supply all the necessary mocks.
Service locator does offer some flexibility thought. For example, your code might use a service only if it exists, like this:
if ($injector.has('serviceA')) $injector.get('serviceA').doSomething()
else someSomethingElse()
With dependency injection, if serviceA doesn't exist by the time your component is constructed, you'll get an error.
As to ease of refactoring, I don't think it's difficult to add/remove parameters to the constructor function. As pointed out in a comment to your question, tools like ngAnnotate will help make the declaration DRYer.
Therefore, I would just stick with dependency injection and only use service locator when absolutely necessary.
I'm using an angularJS and requireJS seed which you can download here: LINK
In that seed only the controllers that are called download the relevant controller which is then triggered. I've been trying to call a factory from my controller (with no luck) plus I would like the services/factories only to download the relevant factory if it has been called.
I've attempted to require a function within the factory method (much like the controller) but it is not working.
This is where I left off: Plunkr link
user971824.
I've put together something I call couchPotato that lazy-registers just about anything in angular using requirejs and the resolution features of $routeProvider (or any other router that does lazy promise-based resolution.
I've created a plunker based on yours demonstrating how you could do it with couchPotato. If you take a look at it, I think you'll see that it's a bit simpler because you don't actually create modules for all of the things you register lazily.
couchPotato grew out of some other example apps I found on the web. What I wanted was a tight way to do the lazy registration and a provider/service combo seemed ideal. I also wanted to maintain the ability for one component to depend on another, like in your example, you want the controller to depend on the factory... couchPotato lets you specify those dependencies in requirejs syntax.
So your controller, in my rendition, looks like this:
define(['app', 'myFactory'], function(app) {
app.couchPotato.registerController([
'mycontroller',
[
'myFactory',
function(myFactory) {
var message = myFactory.getCustomers();
alert(message);
}
]
]);
});
In this example, I made the controller, the factory and the value all lazy, but you could pick and choose and have some registered the "old fashioned way" at configuration time and others registered with couchPotato when they're needed for a given route.
http://plnkr.co/edit/Z3v1mszQiiq024po8Ocp?p=preview
A couple of things to note:
1) I put in a default route in order to trigger the lazy loading of your controller, your service (factory) and the version value.
2) I modified your service to append the version just to show how one component can depend on another (the service depends on the version value, the controller depends on the service).
So, within the require configuration, you don't actually specify any of this... it's all done lazily within your route.
$routeProvider.when('/',
$couchPotatoProvider.resolveDependenciesProperty({
templateUrl:'home.html',
controller: 'mycontroller',
dependencies: [
'mycontroller'
]
})
);
Since mycontroller depends on myFactory and myFactory depends on version, by the time your route is displayed they are all available and hooked up. I put some dummy text in the home.html partial just for kicks, but the controller is assigned by the $routeProvider so you don't actually need to specify it in the template.
couchPotato lives at https://github.com/afterglowtech/angular-couchPotato if you'd like to see a couple of other samples. I shim'ed it in dependent on angular because I designed it to be usable in cases where an entire application doesn't necessarily use requirejs... thus if you are loading angular with requirejs, you need to make couchPotato dependent on angular using the shim/deps technique.
LMK if you have any questions/comments... hope this helps!
Angularjs has this nice feature of auto discovery of the providers based on a function arguments.
For example, if I want to use $http in some function i would call it like that:
$inject.invoke(function ($http) {
});
Angularjs will "know" what are my dependencies. It will figure it out by reading my function's body and based on argument names it will know.
However there is a problem when you would like to minify your code. Minifier will change arguments names. That's why we should use this notation:
$inject.invoke(['$http', function ($http) {}]);
or this notation:
function Foo ($http) {}
Foo.$inject = ['$http'];
$inject.invoke(Foo);
We should always in the end minify our code. So we should avoid using this magic (first example) notation.
And now my problem:
I'm trying to minify my js code and angularjs cannot resolve a provider name.
I can't find a place where i haven't specified .$inject = [...]. Now it just says: "Unknown provider a" and i don't know what function is it referring to.
Is it possible to turn off angularjs auto discover (auto-injector) of providers? I would test and repair my code before minifying.
So, I'm wondering how to disable this "magic" angularjs deduction.
Since I always minify my code I want angularjs to yell at me when I will accidentally use this superheroic evil.
How to turn it off?
Just edit the source. Find 'function annotate', and replace the fn == 'function' block with something like this:
if (typeof fn == 'function') {
console.log("Bad magic injection in "+fn.toString().replace(STRIP_COMMENTS, ''));
}
update:
If anyone needs this because of trying to minify, maybe here is another possible solution
ngmin. It is an AngularJS application minifier project.
Not sure if this help.
According to Igor Minar,
You should make something like this
factory('Phone', function($resource){ ... }))
to
factory('Phone', ['$resource', function($resource){ ... })])
Here is the official doc from Dev guide.
$inject Annotation
To allow the minifers to rename the function parameters and still be
able to inject right services the function needs to be annotate with
the $inject property. The $inject property is an array of service
names to inject.
var MyController = function(renamed$scope, renamedGreeter) {
...
}
MyController.$inject = ['$scope', 'greeter'];
Care must be taken that the $inject annotation is kept in sync with
the actual arguments in the function declaration.
This method of annotation is useful for controller declarations since
it assigns the annotation information with the function
From 1.3.0-beta.6 onwards angularjs supports ng-strict-di option which can be used along with ng-app directive to disable auto-injection.
From Documentation
if this attribute is present on the app element, the injector will be
created in "strict-di" mode. This means that the application will fail
to invoke functions which do not use explicit function annotation (and
are thus unsuitable for minification), as described in the Dependency
Injection guide, and useful debugging info will assist in tracking
down the root of these bugs