I'm confused with how Angular knows which service to inject. For example, suppose we create a logger service like that
appModule.factory('logger', function(){
return { /* Logger module here */ };
});
Them, if in a certain controller I want to use this service, I know that I would use it like:
appModule.controller('MyController', function($scope, logger) { });
And this confuses me, because I'm not getting how Angular knows that $scope referes to the controller scope and logger refers to the logger service. It verifies the name os the variables? So that it tries to match the name of the variable with some service available?
If we were to add a third parameter anotherParam it would check for the service called anotherParam?
The magic is done by $injector.annotate:
annotate(fn);
Returns an array of service names which the function is requesting for injection. This API is used by the injector to determine which services need to be injected into the function when the function is invoked. There are three ways in which the function can be annotated with the needed dependencies.
The first way is to extract the names of the arguments from the function signature:
Argument names
The simplest form is to extract the dependencies from the arguments of the function. This is done by converting the function into a string using toString() method and extracting the argument names.
Inference
In JavaScript calling toString() on a function returns the function definition. The definition can then be parsed and the function arguments can be extracted. NOTE: This does not work with minification, and obfuscation tools since these tools change the argument names.
From the source code , how $injector extracts arguments from a function:
if (fn.length) {
fnText = fn.toString().replace(STRIP_COMMENTS, '');
argDecl = fnText.match(FN_ARGS);
forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg){
arg.replace(FN_ARG, function(all, underscore, name){
$inject.push(name);
});
});
}
You can find it here: https://github.com/angular/angular.js/blob/v1.2.13/src/auto/injector.js#L77
Yes the injection is based on the name of the parameter, AngularJS uses what's generally referred to as introspection so it checks what the name of the parameters are in the function definition and then looks for services it has seen previously defined.
To explicitly keep these as strings so injection still works if you do minification (where the variable names are lost there's an alternative syntax)
appModule.controller("MyCtrl", ["$scope", function($scope) {}]);
^ in this way giving a list of the strings they will not be minified and angular can still determine how to appropriately do the injection. Note that if you use this second method the order of the strings must match the order of parameters you expect to be injected and can be a gotcha if you accidentally add a string but no parameter or vice versa. I believe there is a Grunt task that will take care of this for you (ng-min possibly not sure though)
Short answer is yes.
To make it more clear, you can write
appModule.controller('MyController', ["$scope", "logger", function($scope, logger) { }]);
Then the magic angular use is, if you have a js function
var f = function(a, b, c, d) {};
console.log(f.toString());
// Output
// function (a, b, c, d) { [...] }
Angular still can get these info even you don't mention them manually.
See more http://docs.angularjs.org/guide/di.
Related
I have been coding in Angular for a little bit of time now and I have created Modules, Controller and Services so far but I recently just came across a piece of code which I am just not able to understand. It is someone else's code and I think author has tried to define a service but not like I have created. Normally when I create a service it looks like below:
app.factory('portabilityService', [
'$resource', '$state', '$rootScope', function ($resource, $state, $rootScope) {
return { //definition };
}
]);
To me it is simple specify service name as the first argument inside Factory followed by all the dependencies and there you go.
Now, I came across some other code which looks like following:
(function () {
/*
* Manages which samples have been selected
*/
'use strict';
angular.module('maintenance.portability.module')
.factory('selectedSamplesSvc', service);
service.$inject = [];
function service()
{
//other function definitions
};
})();
Now, this raises a lot of questions:
First of all, is this really a service?
If yes, what is service.$inject = []?
Where should I add the dependencies?
What is the purpose of the constructor service() here?
Is it just a different way to create a service or there is a specific reason why we should define a service in this particular way?
I'll try to address your questions in order:
First, yes, this is definitely a way to declare a service. It's not entirely far off from what you are normally used to using.
The normal syntax for declarations in angular is:
typeOfDeclaration('stringNameOfDeclaration', functionDeclaration);
function functionDeclaration(){..};
In JavaScript, a function is an object, so an anonymous function can be used in place of the functionDeclaration, for example:
typeOfDeclaration('stringNameOfDeclaration', function(){..});
This inline declaration is usually easier to write when you are only making a single declaration, and is the defacto standard for most tutorial examples.
Note that these rules apply to all angular components (providers, filters, directives, controllers, etc.)
By declaring the function as a separate object rather than inline, the declaration and the function are more easily managed. You could, in theory, put the function itself into a different file (though this is not common). Also, if you are declaring multiple functions, this syntax allows you to group the declarations together and the functions together, which is mostly a matter of preference and coding style.
The optional array of dependencies in your example are a way to manage Dependency Injection in cases where the code will be minified. In this case, another alternate DI method is used, service.$inject = [];. This array can be used instead of the inline array, i.e. service.$inject = ['$resource', '$state', '$rootScope']; Again, this gives you a way to group your declarations in one place, your dependencies in another.
All the answers boil down to this: They're equivalent with equivalent definitions of the function passed to .factory.
Yes, it's a service (based on the prior statement)
$inject is an alternative to your [ names..., function(dependencies...) {...}] syntax
The same way just now your function definition is separate.
It's the function you pass anonymously
It's preference for the most part but explicit $inject is beneficial for minimizing
For additional considerations on the matter (in part) and overall good reading give this style guide a look.
I have seen controller being declared in two ways as below. But what diff does this make?
appmodule.controller('Actrl',['$scope',function($scope) {}]);
appmodule.controller('Actrl',function($scope) {});
But, most of the times, the 1st doesn't work. Why?
Both the syntax are same but the first one is preferred (there is a typo, see below description) if you are minifying your code.
Angular resolves the dependency based on the name so when you write appmodule.controller('Actrl',function($scope) {}); syntax, Angular injects the dependency of $scope by reading the argument name $scope. But when your code is minified for production level use then your code will become like:
appmodule.controller('Actrl', function(a) {});
Now, the Angular will not be able to resolve the dependency with the name a. That is why the first approach is used i.e. appmodule.controller('Actrl',['$scope', function($scope) {}]);. Now when your code is minimized for production, your code will be like this:
appmodule.controller('Actrl',['$scope', function(a) {}]);
Now, Angular can match the index based position that a is $scope.
There is a typo in your code where the list should not be closed before the function declaration.
Read the Dependency Annotation for more information on this under the topic Inline Array Annotation.
Angular invokes certain functions (like service factories and
controllers) via the injector. You need to annotate these functions so
that the injector knows what services to inject into the function.
There are three ways of annotating your code with service name
information:
Using the inline array annotation (preferred)
Using the $inject property annotation
Implicitly from the function parameter names (has caveats)
Edit:
Another more detailed description over the two different style: a-note-on-minification:
Since Angular infers the controller's dependencies from the names of
arguments to the controller's constructor function, if you were to
minify the JavaScript code for PhoneListCtrl controller, all of its
function arguments would be minified as well, and the dependency
injector would not be able to identify services correctly.
We can overcome this problem by annotating the function with the names
of the dependencies, provided as strings, which will not get minified.
There are two ways to provide these injection annotations:
[EDIT]
For your first case, this isn't the right syntax. The right one would be to encapsulate in the same array your dependency injection and your controller like the following:
appmodule.controller('Actrl',['$scope', function($scope) {}]);
The difference between both of your definitions is that in the first case you're explicitly specifying your injection dependencies. This will avoid to rename variables name during minification which would break your code. Hence the name in quotes [i.e. those strings] will be used in the minified versions.
Both approach are doing the same thing but the second one is just a syntactic sugar of the first one.
These are just two ways that AngularJS does Dependancy Injection. But this version,
appmodule.controller('Actrl',['$scope',function($scope) {}]);
in particular has been written to handle code minification. It is recommended use this version whenever possible.
To get the difference clear, you must first understand how AngualarJS does dependancy injection. For more details you can refer to:
Understanding Dependency Injection
The "Magic" behind AngularJS Dependency Injection
AngularJS Dependency Injection - Demystified
But to cut the long story short, AngularJS loops through each items by their names in the parameter list, looks up against a list of known names of objects that can be injected and then injects the objects if there is a match.
Let's have a look at an example:
appmodule.controller('myController',function($scope, $log) {
$log.info($scope);
});
Here, since $scope and $log (the order that you specify them in the parameter list doesn't matter here) are known objects to AngularJS, it injects them into myController. But if you were to do:
appmodule.controller('myController',function(someVar) {
// ...
});
AngularJS doesn't know about the parameter someVar and it throws a dependancy error.
Now let's come back to your example. Let me modify your 2nd version a bit:
appmodule.controller('Actrl',function($scope, $log) {
$log.info($scope);
});
If we use a minifier, let's see how this piece of code gets minified. I am using an online minifier for this purpose . After minification, it becomes:
appmodule.controller("Actrl",function(o,l){l.info(o)});
This is because, minifiers usually shorten the variable names to smallest size to save space. Notice how our $scope got renamed to o and $log to l.
Now, if we run this code, AngularJS doesn't know about o and l and it is going to cry about missing dependencies as we understood earlier.
AngularJS deals with this problem using the 1st version of Dependency Injection in your example. If it was:
appmodule.controller('Actrl',['$scope','$log', function($scope, $log) {
$log.info($scope);
}]);
After minification it becomes:
appmodule.controller('Actrl',['$scope','$log',function(o,l){l.info(o)}]);
Here, even though $scope and $log parameters were renamed to o and l respectively, the minifier didn't touch the strings '$scope' and '$log' and their order in the array.
Now when AngularJS injector sees this version using array, it substitutes each item in the parameter list in the function with the corresponding objects in the array (provided the objects are known to AngularJS).
So in our example, even after minification, AngularJS knows that it needs to substitute o with $scope and l with $log. Thus the code runs without any Dependancy Injection errors.
But one important thing is to note here is that, when we use this version the order of the items specified in the array and the parameter list of the function of really matters. That is, if you were to do:
appmodule.controller('Actrl',['$scope','$log', function($log, $scope) {
$log.info($scope);
}]);
, it is going to blow everything up!
I can read in AngularJS doc that when using a syntax where dependencies are specified in strings before being used in function. (e.g. .controller('InvoiceController', ['currencyConverter', function(currencyConverter) { [...]), this is called Array Syntax.
Angular uses this array syntax to define the dependencies so that the DI also works after minifying the code, which will most probably rename the argument name of the controller constructor function to something shorter like a.
I'm searching for a clearer, or at least more specialized term to describe this approach, as I think that it sounds weird and means nothing when discuting with co-workers.
This approach is called 'Inline Array Annotation', it is a type of 'Dependency Annotation'.
You are basically annotating the controller so that the injector knows what services to inject into the function.
There are three ways of annotating your code with service name information:
Using the inline array annotation (this is the preferred approach).
Using the $inject property annotation.
Implicitly from the function parameter names (not recommended).
Taken from here.
Inline Array Annotation
What you have in your example. You specify the dependencies in an
in-line array.
myModule.controller('MyController', ['$scope', 'someService', function($scope, someService ... ]
$inject Property Annotation
Here you can use $inject to inject your dependencies.
var MyController = function($scope, someService) ...
MyController.$inject = ['$scope', 'someService'];
myApp.controller('MyController', MyController);
Implicit Annotation
Here you don't specify the dependencies in an array. This causes
problems if you minify your code.
someModule.controller('MyController', function($scope, someService)
The Angular docs specify three terms for injection annotation:
Inference
$inject Annotation
Inline (this is the array syntax to which your question refers)
Perhaps that's the term you're looking for.
https://docs.angularjs.org/api/auto/service/$injector
This parameter of controller is in an array format, hence the array syntax term. I think what you actually need to communicate is that controller takes as parameter a dependency list in an array format.
Basicly what happens is that using Array Syntax instead of just parameters in the function, you make sure that the dependencies are injected exactly as defined in the array syntax. If you would not use the array syntax, there is a chance that the minification of your code renames the dependencies of your controller to a, b, c and your app would not know what you want to inject with a, b, c. While when you use the Array Syntax, your app knows that a stands for the first entry in your array, b for the second, c for the third etc. In other words, It doesn't matter how the dependencies inside the: function() bit are called, as long as the names in the array match the names of the dependencies you want to inject.
I am doing it this way.
Method 1:
var app = angular.module('MyModule', ['ngDialog']);
app.controller('MyController', function ($scope,ngDialog) {
///
});
but i see a lot like the below kind in articles
Method 2:
app.controller('MyController', ['$scope', function ($scope) {
///
}]);
Please help me understand why is $scope mentioned twice in method 2.
Is method 1, a good practice. If not, in what cases will it fail.
Method 2 is used to prevent minification bugs. In Production when you usually minify your JS/CSS files, the variables' names change to reduce bytes and make the files lighter.
AngularJS relies on DI, which means that it knows which service/provider to inject to your controllers/services according to the name. If it sees $httpProvider, it knows to inject $httpProvider. Once minifaction takes place, the naming will change from:
app.controller('MyController', function (a,b) { //a, b is the change
///
});
Or something of that sort.
Using Method 2, you specify the names as strings, and using those strings AngularJS knows what to inject despite the fact that the minification changed the variables' names.
You can read more about this here.
The recommended way of declaring Controllers is using the array notation:
someModule.controller('MyController', ['$scope', 'dep1', 'dep2', function($scope, dep1, dep2) {
...
$scope.aMethod = function() {
...
}
...
}]);
Dependency Annotation
There are three ways of annotating your code with service name information:
Using the inline array annotation (preferred)
Using the $inject property annotation
Implicitly from the function parameter names (has caveats)
Among These three,Using the inline array annotation is preferred approach.And
the last is simplest way to get hold of the dependencies is to assume
that the function parameter names are the names of the dependencies.
Advantage of implicitly from the function parameter names approach is that there's no array of names to keep in sync with the function parameters. You can also freely reorder dependencies.And disadvantage is If you plan to minify your code, your service names will get renamed and break your app.
Source: this
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