AngularJS needs assistance to know what to inject into the callbacks we provide to certain functions, such as controller()
. In the examples we’ve seen so far, AngularJS uses the argument name, so if we were to make a typo such as £scope
AngularJS wouldn’t know what to inject.
Although typos are possible, they’re fairly quick to spot (via errors in your browser’s developer tools/console) and fix. A more significant problem is associated with the minification of scripts comprising our AngularJS applications. We’ve all seen the output of most minifers: unintelligble single-character variable names everywhere! Minifers will rename $scope,
for example, and AngularJS won’t get any injection assistance.
To be able to use minifers, we can use dependency annotations as follows:
1 2 3 |
angular.module('tutsocean').controller('articleCtrl', ['$scope', function($scope) { $scope.title = "Learn AngularJS"; }]); |
In the above snippet, we’re now passing an array as the second parameter tocontroller()
. The last item in the array needs to be our original callback, while the first items are the string names of the dependencies to be injected. A rudimentary minifier might output the above snippet as follows:
1 2 3 |
a.m('tutsocean').c('articleCtrl', ['$scope', function(s) { s.t = "Learn AngularJS"; }]); |
Strings can’t be renamed, so AngularJS can use dependency annotations to get the injection assistance it needs. There’s a small caveat to this approach: the array item order and callback argument order must be in sync. Additionally, we’re still prone to errors from typos, but overall the benefit of minification far outweighs these caveats in any larger application.
However, we won’t use dependency annotations in this tutorial, preferring instead to keep snippets lean and focused.
Custom injectables
AngularJS offers four techniques for registering our own injectable components, much like $scope
, as follows:
- constant
- value
- factory
- service
Injectable components are known as services in AngularJS speak, so it’s a shame that they decided to call one of the techniques the same.
We will discuss the more common techniques that I have used myself and have seen used by others, namely value and factory. Constant and service are less common and more complicated in use; as such, I consider them beyond the scope of this tutorial.
Value services
A value service allows us to register injectable values (be it strings, numbers, objects, arrays or functions). They allow us to define a value that can’t be changed, much like a constant in other programming languauges. The fact that there is also a constant service is another unfortunate naming choice by AngularJS. The difference between the two is associated with the configuration life cycle of an AngularJS application, rather than anything to do with the value registered.
A value service allows us to register a value once and use it many times (via injection). Consider the limitTo
filter from the “Directives again†section of this tutorial. We could introduce a pageSize
value service, inject it into articleCtrl
and use it as the default number of articles to display. pageSize
could also be reused in other areas of the application, such as administering a subset of a large number of categories.
Edit the contents of services.js
as follows:
1 |
angular.module('udemyAdmin').value('pageSize', 2); |
In the above snippet, we create the most basic of value services, registering the value 2 for the pageSize
injectable component.
Edit the contents of controller.js
as follows:
1 2 3 4 5 6 7 8 9 |
angular.module('tutsocean').controller('articleCtrl', function($scope, pageSize) { $scope.articles = [ { title: "HTML Tutorial" }, { title: "CSS Tutorial" }, { title: "jQuery Tutorial" } ]; $scope.numArticles = pageSize; }); |
In the above snippet, articleCtrl
now has a dependency on pageSize
, so the value 2 is injected in. We then store it as $scope.numArticles
for use in our HTML.
Finally, edit index.html
as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
<html ng-app="tutsocean"> <head> <title>tutsocean - admin area</title> </head> <body ng-controller="articleCtrl"> Number of articles to display: <input type="text" ng-model="numArticles" /> <ul> <li ng-repeat="article in articles | limitTo:numArticles"> {{article.title}} </li> </ul> <script src="angular.js"></script> <script src="app.js"></script> <script src="controllers.js"></script> <script src="services.js"></script> </body> </html> |
In the snippet above, limitTo
is part of an expression and can therefore include$scope
properties. We’ve replaced the hard-coded number of articles to limit to with numArticles
. Additionally, we’ve attached ng-model
to a new HTML text input. This is to allow a user to override the default provided by pageSize
, should they wish to do so.
Factory services
A factory service also allows us to register injectable values. The key difference is that the injectable value is the return value of a function that can itself be injected with dependencies. This is definitely best explained with an example.
Edit services.js
as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
angular.module('tutsocean') .value('pageSize', 2) .value('calculateCategoryPercentage', function(articles) { var availableCategories = ['tutorial', 'graphics', 'hardware']; var uniqueCategories = []; articles.forEach(function(article) { article.categories.forEach(function(category) { if (uniqueCategories.indexOf(category) == -1) { uniqueCategories.push(category); } }); }); return Math.floor(100 * (uniqueCategories.length / availableCategories.length)); }); |
In the above snippet, we have registered calculateCategoryPercentage
as a value service that will calculate a percentage of used categories from an array from articles. The details of how (i.e., the nested looping) isn’t important, but note how we have hard-coded the available categories in a local variable.
Now edit services.js
as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
angular.module('tutsocean') .value('pageSize', 1) .value('availableCategories', ['tutorial', 'graphics', 'hardware']) .factory('calculateCategoryPercentage', function(availableCategories) { return function(articles) { var uniqueCategories = []; articles.forEach(function(article) { article.categories.forEach(function(category) { if (uniqueCategories.indexOf(category) == -1) { uniqueCategories.push(category); } }); }); return Math.floor(100 * (uniqueCategories.length / availableCategories.length)); }; }); |
In the snippet above, we have registered the previously hard-coded available categories as a value service, enabling them to be used elsewhere in the application via injection. We have then registered calculateCategoryPercentageÂ
as a factory service, a function that gets called once with its dependency (availableCategories
) injected and then returns the same function as the value service version.
To see how to use calculateCategoryPercentage,
edit controllers.js
as follows:
1 2 3 4 5 6 7 8 9 10 11 |
angular.module('tutsocean').controller('articleCtrl', function($scope, pageSize, calculateCategoryPercentage) { $scope.articles = [ { title: "HTML", categories: ['tutorial', 'hardware'] }, { title: "CSS Tutorial", categories: ['tutorial', 'graphics'] }, { title: "jQuery Tutorial", categories: ['tutorial'] } ]; $scope.numArticles = pageSize; $scope.categoryPercentage = calculateCategoryPercentage($scope.articles); }); |
In the snippet above, calculateCategoryPercentage
is injected into the controller and then invoked with our hard-coded list of articles. It’s important to note that availableCategories
could also be injected into the controller, and in a later section we will do so.
As with other examples we’ve seen, $scope.categoryPercentage
can be used inindex.html
with a simple binding, such as:
1 |
<span>Percentage of categories used: {{categoryPercentage}}</span> |