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.
Related
Well I have seen couple of questions in StackOverFlow related to this but couldn't find proper answer.
Say in my app.js file I have defined a constants object which basically has controllers names. As I use "ui-router" I want to attach Controllers to views on state change.
app.js
var app = angular.module('myApp', ['ui.router']);
app.constant('CONTROLLER', {
ONE: 'ControllerOne',
TWO: 'ControllerTwo'
});
Now I want to use those constants to define Controller names in other file, say controller.js. Illustrating couple of ways which I tried but dint work for me.
controller.js
var app = angular.module('myApp', ['CONTROLLER']);
app.controller(CONTROLLER.ONE, ['$scope', 'myFactory', function ($scope, myFactory) {
$scope.result = myFactory.someAPI();
}]);
ERROR -> Uncaught ReferenceError: CONTROLLER is not defined
Even I tried injecting that constant module in Controller which gave same error.
var app = angular.module('myApp', ['CONTROLLER']);
app.controller(CONTROLLER.ONE, ['$scope', 'myFactory', 'CONTROLLER', function ($scope, myFactory, CONTROLLER) {
$scope.result = myFactory.someAPI();
}]);
I know that second way is wrong. As I define controller names in my main app.js file and use those constants to attach controllers to views during state change.
I want reuse those constants to define controllers names too.
I may be doing something wrong. Any suggestions ?
Your code shows that you have correctly injected the CONTROLLER constant into the controller constructor function.
var app = angular.module('myApp', ['CONTROLLER']);
app.controller(CONTROLLER.ONE, ['$scope', 'myFactory', 'CONTROLLER', function ($scope, myFactory, CONTROLLER) {
// use constant here
}]);
That being said you can only access the constant except from within the controller function itself. The names of controllers, services, factories etc must be accessible strings, which is not the case until the constant has been injected.
You could use a plain old JavaScript object outside angular to define the names.
var CONTROLLER = {
ONE: 'ControllerOne',
TWO: 'ControllerTwo'
});
I can only guess that you are trying to do this because the controller or directive you are writing will be referenced/used at multiple points throughout your html.
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.
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
I am wondering if there is a way to abstract out dependencies that are used across multiple Angular Controllers.
For example, if both my StudentCtrl and TeacherCtrl take advantage of $scope, $rootScope, $routeParams, and $http, is there a way to abstract these out into a package of some sort, say standardDependencies and then inject standardDependencies into both controllers instead of writing out all of the shared ones?
ex.
app.controller('StudentCtrl', ['standardDependencies', function(standardDependencies){
}]);
I know this is what services are typically used for I just haven't seen any examples for injecting things like $scope, only custom functions.
You can't do this for $scope because it might be different for each controller.
Actual singleton-like services, yes, you could technically wrap up into another service:
app.service('standardDependencies', ['$rootScope', ..., function($rootScope, ...) {
this.$rootScope = $rootScope;
...
}}]);
But I don't see any good reason to do so. If StudentCtrl and TeacherCtrl are really that similar, maybe they should both depend on something like PersonService that wraps common functionality.
This is probably bad practice, but you could define an array standardDependencies at the top of app.js and then use array.concat.
e.g.:
// app.js
standardDependencies = ['$rootScope', '$rootParams', '$location', '$http', '$translate']
// controller.js
app.controller('StudentCtrl', standardDependencies.concat(['$q', '$log', etc,
function($rootScope, ..., $log){ ... }]);
You still have to list them out as function parameters, but at least you don't have to list them twice, once as a string, once as a parameter.
Use some kind of provider; however, this is not very good practice as it might make it difficult for the angular digest cycle to manage these references. This goes against best practices for dependency injection.
If you would like your scopes to access dependencies all across, either a) do proper inheritance of your scopes or b) just do native injection.
I hope that helps. :D
I have a some experience with AngularJS and have come across this directive on the web, but it is not like anything I have seen before and I am unable to comprehend what it is doing? Can anyone help?
Specific questions: With the little understanding, the signature of the directive must be doing dependency injection. But what I am struggling with is: if $injector is passed in the array, why is it also sent as a parameter in function i.e. function($injector); in other words why are there two $injectors? What will not work if I dont send the $injector in the array?
Also how is it that this directive has got controller embedded? When do you define such controllers?
Also I normally see scope with a $ prefix in the the code below how is it working without a $?
Any links to read more or explaining it here will be useful.
.directive('mycomp', [
'$injector', function($injector) {
var $builder, $compile, $drag;
$builder = $injector.get('$builder');
$drag = $injector.get('$drag');
$compile = $injector.get('$compile');
return {
restrict: 'A',
scope: {
component: '=mycomp'
},
controller: 'mycompController',
link: function(scope, element) {
scope.copyObjectToScope(scope.component);
$drag.draggable($(element), {
mode: 'mirror',
defer: false,
object: {
componentName: scope.component.name
}
});
return scope.$watch('component.template', function(template) {
var view;
if (!template) {
return;
}
view = $compile(template)(scope);
return $(element).html(view);
});
}
};
}
])
why is it also sent as a parameter in function i.e. function($injector); in other words why are there two $injectors?
When doing array-type injection it really doesn't matter how parameter in function is called, it will map to array items. For example, if we have
['$injector', function(a) {..}]
Parameter a will map to $injector instance, and if we have
['$injector', '$scope', function(a, b) {..}]
a will map to $injector instance and b will map to $scope instance. The order here is what matters. More here: https://docs.angularjs.org/tutorial/step_05 in A Note on Minification section.
What will not work if I dont send the $injector in the array?
If you don't, $injector will be undefined, some of that explained above.
Also how is it that this directive has got controller embedded? When do you define such controllers?
Some directives can have controllers if needed, they should hold some heavier logic, $scope binding and so on. link function actually should hold only interactions with $element.
More here: http://www.sitepoint.com/practical-guide-angularjs-directives/
Also I normally see scope with a $ prefix in the the code below how is it working without a $?
In this case scope is used in link function and it is NOT an injectible. In this case it is a simple variable scope which refers to controllers scope. You can call it superBigVariableName and it would still refer to scope and would still work.
So keep in mind, that link function is actually a simple function where first attribute is scope, second is element, third is attributes, you can't inject services into your link function (do that logic in controller)