I am not seeing the difference (or pros/cons) to directly injecting my dependencies vs. using $injector in angularjs. Why would I do one or the other? Here is a sample of the two.
angular
.module("myApp")
.factory("myService", [
"$injector", function($injector) {
var $http = $injector.get("$http");
//used $injector to get $http, now get some data, etc.
}
]);
angular
.module("myApp")
.factory("myService", [
"$http", function($http) {
//just use $http to get some data, etc.
}
]);
In most cases, the second method is simple, readable and it just works, especially if you use a precompiler that writes the string values for you, e.g. ng-annotate:
angular.module("myApp")
.factory("myService", /*#ngInject*/ function($http, $locale, $window) {
});
I see no reason why you should avoid it.
The first method, using $injector should be reserved for specific cases, e.g. when there are too many injections (and you don't want to refactor right now) or in some specific cases in tests.
It also makes unit testing much easier to use the first example. You simply mock the injections and your dependencies are handled for you. There are ways to get around it for the second example, but you're just making your life more difficult.
Both solutions work, but difference is in the code readability and extensibility.
Using the second solution you see all module's dependecies by factory definition.
angular
.module("myApp")
.factory("myService", [
"$myService", function($myService) {
}
]);
If you injecting a whole $injector, your dependencies will be fixed. You won't be able to inject other service with consistent API.
angular
.module("myApp")
.factory("myService", [
"$injector", function($injector) {
var $myService = $injector.get("myService");
}
]);
To sum up, both solutions work, but use the first posiblity.
Related
Sometimes I see dependency injection in Angular done like:
angular.module('controllers')
.controller('BooksListCtrl', ['$scope', 'Books', function($scope, Books){
Books.get(function(data){
$scope.books = data;
});
}]);
And sometimes it looks like the following without the array, and just passing dependencies directly into the function:
angular.module('controllers')
.controller('BooksListCtrl', function($scope, Books){
Books.get(function(data){
$scope.books = data;
});
});
Is one the right way? Does it depend on whether you are doing dependency injection on a controller vs directive vs etc?
Sometimes I see dependency injection in Angular done like:
angular.module('controllers')
.controller('BooksListCtrl', ['$scope', 'Books', function($scope, Books){
Books.get(function(data){
$scope.books = data;
});
}]);
And sometimes it looks like the following without the array, and just
passing dependencies directly into the function:
angular.module('controllers')
.controller('BooksListCtrl', function($scope, Books){
Books.get(function(data){
$scope.books = data;
});
});
which one is the right way ?
Both
Does it depend on whether you are doing dependency injection on a controller vs directive vs etc?
No
so how are they different ?
Well first form gives you the freedom to handle the dependencies with your own custom name. For example
app.controller('BooksListCtrl', ['$scope', 'Books', function($scope, myOwnBookName){
myOwnBookName.get(function(data){
$scope.books = data;
});
}]);
while second one does not..but both are correct.
Also, you need to be a little cautious while using the first form because you might mistakenly skip a dependency and/or link it with the wrong one.
For example doing something like:
app.controller('BooksListCtrl',['$scope','$window','$rootScope', function(foo, bar){
...
}]);
would be extremely damaging as foo will now point to $scope, bar will point to $window while $rootScope would be undefined. Just keep the order intact and follow proper naming convention.
When you just pass dependency in function, it can not be obfuscated. While you pass an array with function replicating the same dependencies, you can obfuscate the code without breaking the flow
Angular most probably uses the toString method to read out the dependencies in a function passed. When you obfuscate, angular won't be able to read out the argument as dependencies. Now when you pass an array with function as last element using rest of element as arguement in the same order, angular uses array elements to identify the dependencies as they are values and won't be affected by obfuscation.
So as you have wrote in the comment, Yes! it does the same. like :
['$scope', '$location', function (s, l){}] ;
In this angular tries to read array element to inject dependencies not the argument of function.
Prefer the first version you mentioned over the second:
angular.module('controllers')
.controller('BooksListCtrl', ['$scope', 'Books', function($scope, Books){
Books.get(function(data){
$scope.books = data;
});
}]);
This version protects your code from being mangled during minification (even if you're not currently minifying your code you most likely will in the future). The second version you mentioned is perfectly legal, BUT when minified your dependencies such as $scope and Books may very well become a and b and your services obviously will never be injected.
There's also a second way to annotate your dependency injection:
angular.module('controllers')
.controller('BooksListCtrl', BooksListCtrl);
BooksListCtrl.$inject = ['$scope', 'Books'];
function BooksListCtrl($scope, Books) {
Books.get(function(data){
$scope.books = data;
});
}
This makes your dependency injection very clear, and again protects your code from minification mangling.
I have a sidebar that contains a feed from various social medias, along with a service in AngularJS that queries my API for the data. Below is the controller, along with the service (in that order). Why isn't it being executed on page load, and how should I rewrite my code to make it load the data when the page is rendered to the client?
angular.module('HomeCtrl', ['MediaServ']).controller('HomeController', [
'$scope',
'MediaServ',
'$rootScope',
function($scope, $rootScope, MediaServ){
if ($rootScope){
$scope = $rootScope;
}
function handleRes(response){
console.log('hello!');
}
angular.element(document).ready(function () {
$scope.SocialMedia = function(){
MediaServ.feed()
.then(handleRes, handleRes);
}
});
}]);
angular.module('MediaServ', []).service('MediaServ', [
'$http',
function($http){
this.feed = function(){
return $http.get('/api/social/feed');
}
}]);
You can only use things (be it services, factories, filters, etc) from another module if you have first injected that module into your current one. As the code above is written your modules don't know about each other, and so you can't inject MediaServ into HomeController.
To fix it, inject the MediaServ module into the HomeCtrl module like this:
angular.module('HomeCtrl', ['MediaServ'])...
I will also suggest not shortening names (minifiers should shorten things, developers should not) and not using the same name for services and apps. The last one in particular can cause a lot of confusion in a large project. Personally I prefer to name modules things like "media.services" and services "MediaService" but that is personal taste, just keep a clear naming convention so that you always know what is what.
You shouldn't need to wrap your code in angular.element(document).ready(function () { });
The controller will execute on page load automatically, provided it is reference in the page.
So, UncleDave and Erik Honns' responses both led me to realize what was wrong (on top of having a typo in handleRes). Here is my new code:
angular.module('HomeCtrl', ['MediaServ']).controller('HomeController', [
'$scope',
'$rootScope',
'MediaServ',
function($scope, $rootScope, MediaServ){
if ($rootScope){
$scope = $rootScope;
}
function handleRes(response){
if (response.data.tweets){
$scope.SocialMedia = response.data.tweets;
}
}
MediaServ.feed()
.then(handleRes, handleRes);
}]);
It is now working. Thank you everybody for your help. I won't pick a best answer, and instead will let others vote. Feel free to flag this question to be deleted, as the errors were more on my side (kind of being lazy about reading through my code, but I thought that this was one of those Angular things you just kind of have to learn).
Thanks everyone for your help!
So this is really weird, maybe it has a simple answer I'm missing. The following code gives an unknown provider error:
var foo = angular.module('foo', [ 'ngRoute', 'ngAnimate', 'ngCookies' ]);
foo.factory('fooApi', function ($scope, $http) {
var url = '/api/';
var factory = {};
factory.action = function (fields) {
fields.userid = $scope.userid;
fields.token = $scope.token;
console.log(JSON.stringify(fields));
return $http.post(url, { data: fields });
};
return factory;
})
.controller('loginController', function ($scope, fooApi) {
// do stuff
});
It's all loading together in the same file, and I'd think the factory being first would resolve and the injector would be able to find it when referenced below. But it gives an unknown provider error.
However, if I comment out the controller and wait for the page to load and then do the exact same controller declaration in the Chrome JS console it works fine.
Anyone run into this before and know how to deal with it? I haven't been able to find this same exact issue anywhere.
Like #tasseKATT said, you can not inject $scope into a service, particularly a factory. Maybe your confusion is because $scope can be injected in controllers, so you tried to injected into a factory.
An interesting thing is that the $scope that you see being injected into controllers is not a service - like the rest of the injectable stuff -, but is a Scope object.
The main purpose of $scope is a king of glue between views and controllers, it doesn't make much sense to pass a $scope into a service.
The services only have access to the $rootScope service.
If you need to pass the $scope of a specific controller to a service always you can pass it like parameter of a function in the service. This approach is not recommended because starting to break the SoC and the single responsibility principle, but maybe could fit you.
Good luck :-)
I'm aware that for the purposes of minification and obfuscation we should always use the $injector (by way of controllerName.$inject = ['$service', '$service2']) to specify the actual service names that are required.
If I write a custom service that relies on other services, however, can/should I do the same thing? The only examples I can find for using the .$inject method are called on Controllers.
If I am doing
myModule.factory('myService', function($rootScope, anotherService) {
return {
foo: 'bar'
});
Should I append this?
myService.$inject = ['$rootScope', 'anotherService'];
Or perhaps it's applied to the module as a whole then?
myModule.$inject = ['$rootScope', 'anotherService'];
...But maybe in that case, the module is already keeping track of its services, and hence minification is not a concern?
Check the dependency injection guide, section Inline Annotation.
The following is also a valid syntax, and it is safe for minification:
myModule.factory('myService', ['$rootScope', 'anotherService',
function($rootScope, anotherService) {
....
}]);
I have this interest in automate/simplify angular project with a compiler tool, which might work on everything else, but angular inject and namespacing is awkward enough to escape compiler knowledge. What is the best/professional method for doing this?
thanks, just one last thing,
app.controller('ctrl',['$rootScope',function($rootScope){
...
}]);
works when minified, but how do I minify
app.config(['$routeProvider', function($routeProvider){
}]);
and does it work when I minify successive actions?
app.controller(...).directive(...).run(...)
In Angular, you need to annotate functions for the injector to know which dependencies you want to inject in your function. There are basically three ways to inject dependencies in your function which are being described on official angular website. The three ways are:
1.Use the inline array annotation
yourModule.controller('yourController', ['$scope', function($scope) {}]);
2.Use the $inject property annotation
var yourController = function($scope) {};
yourController.$inject = ['$scope'];
yourModule.controller('yourController', yourController);
3.Implictly from the function parameter names
yourModule.controller('yourController', function($scope) {});
Now when you minify your project, your dependencies names will get renamed.
In first case your code will be like
yourModule.controller('yourController', ['$scope', function(e) {}]);
In third case your code will be like
yourModule.controller('yourController', function(e) {});
It will break your app because angular has no way to recognize your dependency name. So it is advised never to use implicit dependency injection in your project. From the above two inline array annotation is the most popular way amongst programmers.
I would recommend using https://github.com/olov/ng-annotate. It will allow you to write your code like follows.
angular.module("MyMod").controller("MyCtrl", function($scope, $timeout) {
});
and then ngAnnotate turns it into the following which is safe for minification.
angular.module("MyMod").controller("MyCtrl", ["$scope", "$timeout", function($scope, $timeout) {
}]);
The minifyer leaves strings untouched, that's why we use the array notation.
Chaining methods wont change the way the minifyer keeps strings intact.
var app=module(myapp);
app.config(['$routeProvider', function($routeProvider){
$routeProvider.dosomestuffs()
}]);
will be minified in something like
var a=module(myapp);
a.config(['$routeProvider', function(b){
b.dosomestuffs()
}]);
but angular will still find its way around thanks to the '$routeProvider' string.
If you always use annotations there should not be problems minifying angular scripts.
app.controller(['$scope', function(mrScope) {
mrScope.yourProperty = 'it does not matter the dependency variable names if you use annotations';
}]);
As long as you use the array notation for the dependencies you are injecting, no minification trouble is expected. The minification tool you are using should handle any of your examples without trouble (on my project we're using uglify to accomplish that).
In fact, for oddly named injections (named with dots and chars that result in invalid function names like ABC.CDE), the array notation is the best way to inject them.
I had the same problem when minifying, and like you, it only failed for the $routeProvider config elements.. The answer for me was to use the $inject method like Himanshu says for my configs, even though the syntax you show for your first example works for my controllers. So I'll post my .config() code here since I don't see it specifically listed in the answers above:
var app = angular.module('myApp');
var RouteProviderConfig = function ($routeProvider) {
$routeProvider
.when(...)
.otherwise(...);
};
RouteProviderConfig.$inject = ['$routeProvider'];
app.config(RouteProviderConfig);
This fixed my error for configs, but my controllers work the way your first example is written:
app.controller('ctrl',['$rootScope',function($rootScope){
...
}]);
The Angular Documentation seems to suggest that both ways should work, so I think it's possible there is a bug with configs, or $routeProvider, or something else entirely...