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(...);
...
});
Related
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)
Tearing my hair out on this one.
I have the following module/controller I wish to test
angular
.module('monitor.tableLord.controller', ['monitor.wunderTable'])
.controller('TableLordController', TableLordController);
TableLordController.$inject = ['$scope', 'historyState'];
function TableLordController($scope, historyState) {
....some code....
}
The module monitor.wunderTable contains a directive that should be loaded before the controller, but the controller I want to test does not actually depend on monitor.wunderTable. monitor.wunderTable does however have a LOT of other dependencies....
My testfile:
describe('TableLordController', function() {
var $scope, historyState;
beforeEach(function() {
angular.module('monitor.wunderTable', []);
module('monitor.tableLord.controller');
});
beforeEach(angular.mock.inject(function($rootScope, $controller) {
$scope = $rootScope.$new();
$controller('TableLordController', {$scope: $scope, historyState: {}});
}));
it('loads', function() {
$scope.$digest();
});
});
For some reason (I didn't think this should be possible), my mock version of monitor.wunderTable is interfering with the tests i have for this module. Every test of the controller defined in this module now fails with: "Argument 'WunderTableController' is not a function, got undefined".
I case it is relevant, here is my the definition of monitor.wunderTable:
angular
.module('monitor.wunderTable', [
'ui.grid',
'ui.grid.infiniteScroll',
'ui.grid.autoResize',
'monitor.wunderTable.service'
])
.controller('WunderTableController', WunderTableController)
.directive('wunderTable', wunderTable);
function wunderTable(){...}
WunderTableController.$inject = [];
function WunderTableController(...){...}
Edit: Posts suggesting that I remove the module dependency (as it is not strictly needed) will not be accepted as correct answer (and possibly downwoted).
Your confusion comes from misunderstanding how modules work in Angular. Modules are stored inside angular. Once they are overriden, they are overriden for the current test run, not for the current spec. Module mocking isn't supported by ngMock (it would require some substantial changes in Angular core) and beforeEach won't help anything.
Unless you want to run test suites in separate runs, the solution is to backup the module before mocking it. In some cases angular.extend and angular.copy are unable to handle complex objects properly. Object.assign, jQuery.extend or node-extend may be better candidates. In the case of extends deep copy can also used if necessary.
So in one test suite it is
var moduleBackup = angular.module('monitor.wunderTable');
angular.module('monitor.wunderTable', []);
describe('TableLordController', function() {
...
And in another
Object.assign(angular.module('monitor.wunderTable'), moduleBackup);
describe('WunderTableController', function() {
...
The good thing about TDD is that it clearly indicates the flaws in app design and teaches the developer to write test-friendly code in immediate and ruthless manner. Module dependency implies that the components inside the module depend on the components from another one. If they doesn't or they are coupled too tightly, this can be considered a potential flaw and the subject for refactoring.
The fact that the solution is hack-ish and tends to break makes it unfit for testing.
TL;DR: Yes, you have to remove the module dependency.
First of all, this is an honest question. And I'm looking for honest and justified answers on why I shouldn't be doing this...
angular
.module('X', ['Y'])
.config(function (myFactoryProvider, myServiceProvider) {
myFactoryProvider.$get().myFn();
myServiceProvider.$get().myFn();
});
angular
.module('Y', [])
.factory('myFactory', ['$location', function ($location) {
return {
myFn: function () {
console.log('factory');
console.log($location.absUrl());
}
}
}])
.service('myService', ['$location', function ($location) {
this.myFn = function () {
console.log('service');
console.log($location.absUrl());
}
}]);
Here's a JSFiddle: http://jsfiddle.net/1vetnu6o/
This is working as you can see above and it solves a few problems for me. But I shouldn't probably be doing this and I want to understand why. I really need good reasons to not do this. Despite the fact that I really want to.
tl;dr;
Here's the context of the problem...
I have this internal framework used by multiple products where there's this one service (which happens to be a factory) that basically contains a group of related helper methods. In this case device related like isMobileDevice, isAndroid, getDeviceType (with returns mobile, tablet or desktop), and few others...
This service must be injected into the config() phase of the application using the framework because we need access to the getDeviceType function. The thing is, we need to get the deviceType to load proper templates with $routeProvider. It's in the config() phase that we are building the correct template paths to be used for all the routes. Some of them depend on the deviceType, while others have a generic template independent of the device.
Since this is a service, we cannot inject it directly into the config() phase but we can call that method using the technique I mentioned earlier in the post.
How I'm currently solving this? The helper service is actually a provider and all the methods are exposed both in the provider section as well as in the factory function. Not ideal, but it works. I consider this a work-around, but I'd rather have a work-around in the application and not the framework, thus the technique first mentioned.
Thoughts?
I didn't knew but actually you can invoke on config phase.
The problem is that myService will be instantiated twice :/
angular
.module('X', [])
.config(function(myServiceProvider) {
myServiceProvider.$get().myFn();
})
.run(function(myService) {
myService.myFn();
})
.service('myService', ['$location', function($location) {
console.log('myService!');
this.myFn = function() {
console.log($location.absUrl());
}
}]);
output:
"myService!"
"location"
"myService!"
"location"
This is dangerous if you call $get on config of a service that has a big nested dependency tree :/
I think the correct approach, if you need a utility service (with no dependency), is to use a constant (in this way you can inject it everywhere). Otherwise if you need dependencies use a service and stick to the run() block.
The config() block it's the place to instruct your services how they should work with the help of their providers.
The run() block it's the perfect place to do some logic when your app starts (aka main method).
I have followed this post How inject $stateProvider in angular application? and I have managed to figure out how to use '$stateprovider' but now I have issue with the '$urlRouterProvider'.
Does that mean I can't inject '$urlRouterProvider' into controller either and it should be only injected into config?
I highly appreciate any help any help on this issue.
There is a very narrowed snippet of the $urlRouterProvider code:
$UrlRouterProvider.$inject = ['$locationProvider', '$urlMatcherFactoryProvider'];
function $UrlRouterProvider( $locationProvider, $urlMatcherFactory) {
// I. config
// these are CONFIGURATION methods
// we can access in .config() phase
....
this.rule = function (rule) {
...
}
...
this.when = function (what, handler) {
...
}
...
// II. application run
// this is the service/factory/configured provider
// injected in the .run() phase via IoC
this.$get = $get;
$get.$inject = ['$location', '$rootScope', '$injector', '$browser'];
function $get( $location, $rootScope, $injector, $browser) {
...
return {
sync: function() {
...
},
listen: function() {
...
}
And this is the answer. In the config we do have access to configuration methods of the provider $urlRouterProvider. While later, in phase of application run, our services/factories are via IoC provided with the result of the configured $get()
For more details, see:
Configuring Providers
You may be wondering why anyone would bother to set up a full-fledged provider with the provide method if factory, value, etc. are so much easier. The answer is that providers allow a lot of configuration. We've already mentioned that when you create a service via the provider (or any of the shortcuts Angular gives you), you create a new provider that defines how that service is constructed. What I didn't mention is that these providers can be injected into config sections of your application so you can interact with them!
First, Angular runs your application in two-phases--the config and run phases. The config phase, as we've seen, is where you can set up any providers as necessary. This is also where directives, controllers, filters, and the like get set up. The run phase, as you might guess, is where Angular actually compiles your DOM and starts up your app.
In case, you need to access $stateProvider or $routeProvider configuration even later... there is a way... Check this plunker. Can hardly say that this is the angular way... but it is working. Check it here
AngularJS - UI-router - How to configure dynamic views
http://plnkr.co/edit/I4AGHa3xgfv8WUMotxNL?p=preview
I was wondering what the best moment is while initializing the app to retrieve data from:
1 - a REST service
2 - $routeParams
to define application wide constant.
config phase only accepts providers and during the config / run phase $routeParams properties are undefined.
This seems like somewhat in the right direction:
Can you pass parameters to an AngularJS controller on creation?
Also: how to define a constant within a controller, is that possible?
app.controller('MainCtrl', function($scope) {
//define app.constant here
}
--edit: typo
During run phase all the providers should be initialized and working correctly, you can use the $http service to retrieve what ever parameters are needed for you.
I am pretty sure the $routeParams are initialized at run phase as well.
Defining constants in a controller isn't a good practice (in my opinion), if they are unique to that controller then they are just variables, and if you want real application wide constants, use a service, that's what they are for :)
I know of one easy way to pass parameters to controllers on creation, which is using the angular ui router project: https://github.com/angular-ui/ui-router
In the resolve function you can do http calls if necessary, inject constants etc, it's very handy and personally I never build an angular project without it.
I am pretty sure there are more ways, but usually the best practice to pass data between controllers is using a service.
On a side note, if you have a piece of data that is common to more than 1 controller, the easiest way is to put that data on a service and do a watch on that service return value, for example, say I have isLoggedIn, which can change at any moment and a lot of controllers will want to be notified about it, use a UserService and watch for it's value:
UserService.isLoggedIn = function() {
return _isLoggedIn;
}
And in your Controller:
$scope.$watch(function() {
return UserService.isLoggedIn();
}, doSomeAction);
I hope this helps!
This http://www.jvandemo.com/how-to-resolve-angularjs-resources-with-ui-router/ seems like a nice guide, basically, you add the resolve to the state:
.state("customers", {
url : "/customers",
templateUrl: 'customers.html',
resolve: {
//any value you want, this function should return a promise,
//only when that promise is resolved, it will instantiate the controller
//Make sure however you add some signal that something is happening because
//while fetching it can seem like the page is not responding
customers: ['$http', 'anyOtherServiceYouMightNeed', function($http, otherService){
//return a promise
return $http.get('api/customers');
}],
//and for constant
constants: ['ConfigService', function(config) {
return config.appConstants;
}]
},
//customersCtrl will have customers resolved already
controller : 'customersCtrl'
});
app.controller('customersCtrl', ['$scope', 'customers', 'constants',
function($scope, customers, consts) {
//customers will be ready and resolved when the controller is instantiated
//you can do this with anything you might need inside a controller
}