How does implicit/inline/$inject dependency injection work in AngularJS? - javascript

I'm new to AngularJS and I would like to understand more about the dependencies that are being injected by default. While reading through code I've noticed that sometimes dependencies are explicitly declared beforehand, and sometimes they aren't. For example:
someModule.controller('MyController', ['$scope', 'someService', function($scope, someService) {
// ...
}]);
Gives the same results as:
someModule.controller('MyController', function($scope, someService) {
// ...
});
How does this work? Is Angular assuming that the modules being injected are named the same as the variables in the parameters?
Also, strangely enough, if you do specify the dependencies that are going to be injected, you must specify all of them and in the right order, otherwise nothing will work. For example, this is broken code:
someModule.controller('MyController', ['someService', '$scope', function($scope, someService) {
// Won't give us any errors, but also won't load the dependencies properly
}]);
Can someone clarify to me how is this whole process working? Thank you very much!!

Yes, dependency injection in Angular works via the names of the components you (and Angular - for the internal ones) registered.
Below is an example showing how a service is registered and injected into a controller using several different annotations. Please note that dependency injection always works the same in Angular, i.e. it doesn't matter if you are injecting something into a controller, a directive or a service.
app.service('myService', function () {
// registering a component - in this case a service
// the name is 'myService' and is used to inject this
// service into other components
});
Two use (inject) this component in other components, there are three different annotations I am aware of:
1. Implicit Annotation
You can either specify a constructor function which takes as parameters all the dependencies. And yes, the names need to be the same as when these components were registered:
app.controller('MyController', function ($http, myService) {
// ..
});
2. Inline Array Annotation
Or you can use a notation using an array, where the last parameter is the constructor function with all the injectables (variable names do not matter in this case). The other values in the array need to be strings that match the names of the injectables. Angular can this way detect the order of the injectables and do so appropriately.
app.controller('MyController', ['$http', 'myService', function ($h, m) {
/* Now here you can use all properties of $http by name of $h & myService by m */
// Example
$h.x="Putting some value"; // $h will be $http for angular app
}]);
3. $inject Property Annotation
A third option is to specify the $inject-property on the constructor function:
function MyController($http, myService) {
// ..
}
MyController.$inject = ['$http', 'myService'];
app.controller('MyController', MyController);
The reason why the last two options are available, at least as far as I know, is due to issues which occured when minifying the JavaScript files which led to the names of the parameters being renamed. Angular then wasn't able to detect what to inject anymore. In the second two cases the injectables are defined as strings, which are not touched during minification.
I would recommend to use version 2 or 3, as version 1 won't work with minification/obfuscation. I prefer version 3 as from my point of view it is the most explicit.
You can find some more detailed information in the internet, e.g. on the Angular Developer Guide.

Just to provide a different sort of answer, as to the how inline/implicit dependencies work in AngularJS. Angular does a toString on the provided function and parses the parameter names from the string which is produced. Example:
function foo(bar) {}
foo.toString() === "function foo(bar) {}"
References:
source code
AngularJS Dependency Injection - Demystified

Related

Which is the preferred way of defining an AngularJS controller?

As an example, we have this index.html code:
<!DOCTYPE html>
<html ng-app="sample">
<head>
...
</head>
<body>
<div ng-controller="myController">
...
<script src="js/modules/app.js"></script>
</div>
</body>
</html>
and in app.js we have a module and a controller:
var app = angular.module('sample', []);
// controller here
So my question is that, I have seen controllers defined in two types, as a controller, and as a plain function:
app.controller('myController', function(args){
...
});
or
var myController = function(args){
...
};
Which one should be used and why? I have mostly seen the first one used in Angular-based code, but even in tutorials I have come across the second. I personally don't use the second, as I have read it 'pollutes the global namespace'.
Another question I have is that I have seen this kind of usage for a controller:
app.controller('myController', ['$scope', '$http', function($scope, $http) {
...
}]);
Why do we need the array? Can't we make do with just the arguments?
According to the Angular the array annotation based dependency injection or definition is the preferred way:
someModule.controller('MyController', ['$scope', 'greeter', function($scope, greeter) {
// ...
}]);
See Inline Array Annotation
This is the preferred way to annotate application components. This is
how the examples in the documentation are written.
While at the other hand, the simplest way to get hold of the dependencies is to assume that the function parameter names are the names of the dependencies (which is not preferred for like production app).
someModule.controller('MyController', function($scope, greeter) {
// ...
});
The Angular can infer the names of the services to inject by examining the function declaration and extracting the parameter names. In the above example, $scope and greeter are two services which need to be injected into the function.
However this method will not work with JavaScript minifiers/obfuscators because of how they rename parameters.
The resulting code after minification will be like this:
someModule.controller('MyController', function(a, b) {
// ...
});
So now, the Angular does not know what is the dependency a & b while if you use the array annotation based, the output will be:
someModule.controller('MyController',['$scope','greeter', function(a,b) {
// ...
}]);
So, now Angular can map a with $scope and b with greeter and will be able to resolve the dependency.
app.controller('myController', ['$scope', '$http', function($scope, $http) {
This is done to prevent JS minifiers from breaking your code, because angular relies on names for dependency resolutions.
As for the two styles of controller
app.controller('myController', function(args){
...
});
vs
var myController = function(args){
...
};
app.controller('myController', myController);
It's a matter of personal taste. There's no functional difference.
The first question is mostly about style, since both methods are correct.
There might be arguments about both ways of defining controllers and other Angular modules. But as it is with every language in Software Development: Find a coding style and stick to it. Inconsistencies are the real problem.
Here's a good style guide to stick to: https://github.com/johnpapa/angular-styleguide
The second question has to do with minification. Please read this article: http://thegreenpizza.github.io/2013/05/25/building-minification-safe-angular.js-applications/
Syntax for Controllers
The most preferred way for Angular 1.x version is to use Controller As syntax. Please see code below:
(function () {
'use strict';
angular.module('app.controllers')
.controller('HeadController', HeadController);
HeadController.$inject = ['someService'];
function HeadController(someService) {
/* jshint validthis: true */
var vm = this;
vm.logout = action;
function action() {
someService.doSomeAction();
}
}
})();
In your html it will be used like this:
<div ng-controller="HeadController as vm">
<a href ng-click="vm.logout();" id="item-btn-logout"><i class="icon-off">
</div>
I prefer this syntax. This will let you not to use scope in your views.
Take a look at John Papa AngularJS guide - it is good!
If you are still thinking that it's too complicated and you ain't gonna need it - refer to this article that explains how to avoid Scope Soup in Angular.
Why do we need the array?
Array with plain text is used during injection of dependencies. This will let you minify your code easily and not to lose dependency names during initialization process. If you are not going to do this and minify your code - you are risking that your code won't work.

Should I use the array notation in Angular?

I learnt to write angular dependencies needed using the array notation, that way:
var app = angular.module('MyApp', []);
app.controller('MyCtrl', ['$scope', function($scope) {
$scope.stuff = 'stuff';
}]);
The Angular doc follows this notation, but I see more and more tutorials not using the array notation and just directly passing the controller the function($scope).
Is there any differences between the two ways to do? Or maybe one was implemented in the version two?
You should be using the Array notation
say Tomorrow if you wish to minify your data using a uglify say, it minifies your big variable names but doesn't touch your strings so your statement
from
Case 1 with array notation
original
app.controller('MyCtrl', ['$scope', function($scope) {
minified
x.controller('MyCtrl', ['$scope', function(a) {
Here controller knows exactly knows that variable a is $scope
Case 2 without array notation (whereas if you choose not to use it)
original
app.controller('MyCtrl', function($scope) {
minified
x.controller('MyCtrl', function(a){
Now your controller doesn't know what to do with a variable its not $scope for sure
The array notation is important if you plan to minify your code, which you should be doing in production anyway. Stick to using it.
If you're planning to mini your app, yes you MUST use the array notation.
This is because the variables are renamed, so the injector no longer knows what dependencies you're intending to inject.
For example if $scope was renamed a on minification it wouldn't work.
Obviously this means you have to write and maintain more code. Luckily, you can automate this in your build process.
On my project I use grunt and angular-templates.
https://www.npmjs.com/package/grunt-angular-templates
Yes there is a difference. I would recommend continuing to use array notation since not using it will break your dependency references if the application is minified. For more details, read https://docs.angularjs.org/tutorial/step_05 .
Basically you can rename the variables without affecting angulars ability to inject. As others have said, specifically for minification.
var app = angular.module('MyApp', []);
app.controller('MyCtrl', ['$scope', function(s) {
s.stuff = 'stuff';
}]);
Array notation is used for injecting the dependencies. Difference between this two is if u do not include array notation means u do not need any dependencies.
Best practice is to use [] notation so that u can include dependencies any time in you application.
In a nutshell there are two ways how you can use dependency injection in angular: implicit or explicit.
Implicit DI is .controller('MyCtrl', function(scope, dep1, dep2){
It does not give instructions to $injector on what to include, it bvasically doing something like a reflection to figure it out. If you minify your code it will turn into
`.controller('MyCtrl', function(a, b, c){ `
That is why we are using explicit DI. There are several ways to do so:
use array : .controller('MyCtrl', ['$scope','dep1','dep2', function($scope, dep1, dep2){
use $inject:
.controller('MyCtrl', myCtrl)
myCtrl.$inject = ['$scope', 'dep1', 'dep2']
function myCtrl($scope, dep1, dep2) {}
In both ways, even if minification renamed function parameters to something, $injector will know what it is expected to inject, as it has original names in the string literals ( which are not affected by minification)
You can also use comment annotations that will tell compiler/transpiler how to handle angular DI , ie it will turn implicit DI into explicit for you, see ng-annotate
I personally prefer second way with .$inject
DI helps you while minifying the code, Also other answer does explained how both the DI injection techniques work. Basically none of DI injection technique is bad.
But I'll prefer to do not worry about this thing because other will take care of this DI thing. Use ng-annotate directive, that will give you facility to use dependency inside a function directly
If you wrote code like this
angular.module("MyMod").controller("MyCtrl", function($scope, $timeout) {
//awesome code here
});
When you send this code to minifier it does add the array annotation of dependency on the angular component while
angular.module("MyMod").controller("MyCtrl", ["$scope", "$timeout",
function($scope, $timeout) {
//code here
}
]);
From Docs
ng-annotate works by using static analysis to identify common code
patterns. There are patterns it does not and never will understand and
for those you can use an explicit ngInject annotation instead, see
section further down.

Angular Service Injection

I am working on a trivial task (that I got working) which adds an an .active class to dom elements in a tab nav. Simple. Got it working by following https://stackoverflow.com/a/12306136/2068709.
Reading over the answer provided and comparing their controller to example controllers on angular's website I came across something interesting that I don't quite understand.
On the AngularJS website, $scope and $location (and other services) are injected into the controller along with an anonymous function which defines the controller. But on the answer, the services are not injected, rather they are passed via the anonymous function. Why does this work? Shouldn't the service always be injected?
In terms of an example: Why does this
angular.module('controllers', [])
.controller('NavigationController', ['$scope', '$location', function($scope, $location) {
$scope.isActive = function(route) {
return route === $location.path();
};
}])
and this
angular.module('controllers', [])
.controller('NavigationController', function($scope, $location) {
$scope.isActive = function(route) {
return route === $location.path();
};
})
both work?
This may be trivial but its throwing my brain for a loop that I can't figure out.
The two examples are equivalent - they just make use of different syntax. The first example uses what they call "inline array annotation" (see here). The purpose of this alternate syntax is just to allow a convenient way to make the injected variable names different than the name of the dependency.
So for example, if you wanted the variable names to be "s" and "l", then you could write:
angular.module('controllers', [])
.controller('NavigationController', ['$scope', '$location', function(s, l) {
s.isActive = function(route) {
return route === l.path();
};
}]);
Actually they are injected in both cases, the difference between those two cases is in the first scenario you define and name the dependency this could be useful if you minify your js code and that way you are declaring explicitly the dependency for examply it could be:
angular.module('controllers', [])
.controller('NavigationController', ['$scope', '$location', function($s, $l) {
$s.isActive = function(route) {
return route === $l.path();
};
}])
that way angular will know which dependency to inject on which parameter without looking at the naming for each parameter.
the other case you need to be explicit and declare which dependency you'll inject by setting up the name.
I hope that helps.
This is how angular handles code minification. by keeping strings intact it can keep mapping vars when they are renamed.
if you take a look at the code of the controller https://github.com/angular/angular.js/blob/master/src/ng/controller.js#L48
you'll see that the constructor can accept both function and array.

AngularJS - Parameter binding in controllers

I've got the following code sample and apparently I once again lack some background information.
When I use the greet directive, there is a binding happening on the controller level. For some reason it binds the element and the attribute values of the element to those corresponding variables ($attr, $element).
Is there a list with all the existing bindings which exist for the controllers?
I've done some research, but couldn't come up with anything in this direction.
directives.js
angular.module('TodoApp.directives', []).
directive('greet', function () {
return {
template: '<h2>Greetings from {{from}} to {{to}}<h2>',
controller: function($scope, $element, $attrs) {
$scope.from = $attrs.from;
$scope.to = $attrs.greet;
}
};
});
list.html
....
<div greet="Test1" from="Test2"></div>
...
Directive controllers have some special bindings. You can find them in the docs of the $compile service (here), search for "controller". Repeating here for completeness:
The controller is injectable (and supports bracket notation) with the following locals:
$scope - Current scope associated with the element
$element - Current element
$attrs - Current attributes object for the element
$transclude - A transclude linking function pre-bound to the correct transclusion scope. The scope can be overridden by an optional first argument. function([scope], cloneLinkingFn).
This is called Dependency Injection. Rather than having a fixed parameter list, Angular uses a DI container to inject the parameter when you need it. For example, if you need the $http service, just add it as a parameter - the order of the parameters does not matter.
The type of parameters you can inject are constants, values, factories, services, and providers.
Some are available to you 'out-of-the-box' from the Angular library:
Look here for Angular Providers and Services
Others, you get from third-party modules, or modules that you develop yourself.

AngularJS Dependency Injection where to specify?

I am an angularJS noob here and, based on my understanding, there are two places where I can inject dependency.
angular.module('myApp', [A: HERE IS ONE PLACE TO DO IT])
.controller('HomeController', function(B: $hereIsAnotherPlace){
});
Am I right about this? If so, what are the differences?
In your example, A is where you can specify modules rather than dependency injection (DI). Below, addresses this variation of your code:
.controller('HomeController', [A , function(B) {}]);
The second one (B) is required, the first (A) is optional (but has benefits described below).
Here's an example of only using just the second (B) from the Angular docs:
function MyController($scope, greeter) {...}'
But Javascript minifiers and obfuscators can rename parameters and break that approach because angular expects, for instance, $scope to be named precisely $scope (and minifiers like to rename parameters to something as small as possible in order to shrink the file as small as possible).
One way, amongst others, around that is inline annotation:
someModule.factory('greeter', ['$window', function(renamed$window) {...}]);
(again from the angular docs). This gets around the issue as the minifers/.. won't change a string literal. And angular knows to inject the service with that string name into the matched parameter within the function. So that parameter name can be changed to anything by the minifier and all is well as the only thing that matters is the service's position within the list of strings/parameters (first string matches to first parameter, etc.).
For lots more on dependency injection: http://docs.angularjs.org/guide/di
Where you specify "HERE IS ONE PLACE TO DO IT" is actually where you can inject different modules into another module, here is an example.
var helperModule = angular.module('helperModule', []);
var pageModule = angular.module('pageModule', ['helperModule']);
pageModule now has access to all the services & directives.. etc. linked to helperModule
And where you specify this
function(B: $hereIsAnotherPlace){ ...
Is where you inject services, although that javascript is invalid.
Here are 2 ways that you can inject services.
.controller( 'myController', function( $myService ) { ... });
Or for minified code you would use.
.controller( 'myController', ['$myService', function( $myService ) { ... }]);
in the latter example you can change the name of $myService in the arguments to anything you like.
Quick Example
.controller( 'myController', ['$myService', function( $thisIsEqualTo$myService ) { ... }]);
So the last 2 example's are identical, when you use the Array to specify the injections, the arguments can be named whatever as they are passed in the order that you require them in the Array.
A is only used when creating your module, and should only be used once in your app.
angular.module('myApp', [A: HERE IS ONE PLACE TO DO IT]);
B is for injecting into the controller...
angular.module('myApp')
.controller('HomeController', function(B: $hereIsAnotherPlace){
});
No injection of A again otherwise it will create a new module rather than using your already created one.

Categories

Resources