Which is the preferred way of defining an AngularJS controller? - javascript

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.

Related

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

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

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.

How can I use an Angular constant in a view?

I am trying to remove a great number of string literal values from angular directives all over my views. I have the beginnings of a constant I wish to make global, e.g:
// UI Validation constants.
MyAngularApp.constant('valid', {
"postalCode": {
"pattern": "/^[0-9]+$/",
"minLength": 4
}
});
Then I would like to use this constant in input directives in my view templates, in a way similar to:
<input type="text" ng-pattern="{{valid.postalCode.pattern}}" blah blah>
But here I have serious doubts about binding within a directive's parameter.
I have seen a few suggestions to add such a constant to root scope, but also some suggestions to avoid root scope and only add this to local scopes inside controllers, but this would involve code changes to several controllers, where I would prefer just one change.
if I decide to go with root scope, how would I add this constant? My misguided attempt at this is:
console.log("Abandon all hope who enter here!");
MyAngularApp.config(function ($rootScope) {
$rootScope.valid = valid; // How to access the constant here?
});
But this code gives me the error:
Uncaught Error: [$injector:modulerr]
ADDED: Some suggestions below involve the Angular run function, but I can't find that. The module call is like this:
var MyAngularApp = angular.module('MlamAngularApp', ['kendo.directives', 'ngRoute', 'ui.bootstrap', 'ngReallyClickModule', 'ngMessages', 'angular-loading-bar', 'ngAnimate']);
Per the OP - If you want to bind to rootScope you when the app loads you can use a run block like this:
app.run(function($rootScope) {
$rootScope.name = 'bound during run'
})
I updated the plunker demonstrating scope inheritance to show it in action.
Plunker
It's bad practice to store stuff in $rootScope.
Here is an example why:
$scope inheritance
It can make things confusing and messy, especially if multiple people are maintaining code. You bind something to rootScope and EVERY controller's scope has that property attached to it's scope. If you have to do it, you should document it very clearly.
Do it like this:
app.constant('myConstant', {
"postalCode": {
"pattern": "/^[0-9]+$/",
"minLength": 4
}
})
Just inject wherever you need it:
app.controller('MainCtrl', function($scope, myConstant) {
$scope.name = myConstant;
});
Banner's method is fine as well, though I think it is better to do it this way to maintain a high level of abstraction rather than attaching a bunch of stuff to the module like that. This way let's you tuck all your constants into constants.js or whatever, and keeps everything clean.
See the snippet.
valid constant is getting injected into $rootScope when calling run.
On the view side, just do {{$root.valid.postalCode.pattern}} where you need to inject it.
And thus... the only thing that you need to change is the run function and the corresponding views' binding.
Injecting into config will not work as per Cannot get to $rootScope
(function() {
'use strict';
angular.module('myApp', []);
angular.module('myApp')
.constant('valid', {
"postalCode": {
"pattern": "/^[0-9]+$/",
"minLength": 4
}
});
angular.module('myApp')
.run(runFn);
runFn.$inject = ['$rootScope', 'valid'];
function runFn($rootScope, valid) {
$rootScope.valid = valid;
}
angular.module('myApp')
.controller('myCtrl', myCtrl);
function myCtrl() {
var self = this;
}
}());
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp" ng-controller="myCtrl as vm">
{{$root.valid.postalCode.pattern}}
</div>

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.

How to make lodash work with Angular JS?

I'm trying to use lodash use it at ng-repeat directives, in this way:
<div ng-controller="GridController" ng-repeat="n in _.range(5)">
<div>Hello {{n}}</div>
</div>
Being GridController:
IndexModule.controller('GridController', function () {
this._ = _
})
However is not working and so, nothing being repeated. If I change the directive to ng-repeat="i in [1,2,3,4,5]" it'll work.
lodash is already included via <script> at <header> before angular. How can I make it work?
I prefer to introduce '_' globally and injectable for tests, see my answer to this question
Use underscore inside controllers
var myapp = angular.module('myApp', [])
// allow DI for use in controllers, unit tests
.constant('_', window._)
// use in views, ng-repeat="x in _.range(3)"
.run(function ($rootScope) {
$rootScope._ = window._;
});
I just wanted to add some clarification to #beret's and #wires's answer. They definitely helped and got the jist of it but getting the whole process in order might suit someone. This is how I set up my angular environment with lodash and got it working with yeoman gulp-angular to serve properly
bower install lodash --save (This adds to bower and a saves to bower json)
modify bower json to have lodash load before angular does. (This helps if you're using gulp inject and don't want to manually put it into index.html. otherwise put it into the index.html before the angular link)
Make a new constant per #wires's direction.
'use strict';
angular.module('stackSample')
// lodash support
.constant('_', window._);
Inject into any angular service, filter, or controller as you would anything else:
.filter('coffeeFilter', ['_', function(_) {...}]);
To throw it into some angular html view just inject it into the controller and assign a scope variable to it:
.controller('SnesController', function ($scope, _) {
$scope._ = _;
})
Hope this helps someone set up their site. :)
ng-repeat requires an Angular expression, which has access to Angular scope variables. So instead assigning _ to this, assign it to the $scope object you inject into the controller:
IndexModule.controller('GridController', function ($scope) {
$scope._ = _;
})
Demo
I am not sure what version of Angular you are using. Looks like you should have just used the 'Controller as' syntax when you use 'this' to access variables in the dom.
Here is the solution with this and not using scope.
http://plnkr.co/edit/9IybWRrBhlgQAOdAc6fs?p=info
<body ng-app="myApp" ng-controller="GridController as grid">
<div ng-repeat="n in grid._.range(5)">
<div>Hello {{n}}</div>
</div>
</body>

Categories

Resources