AngularJS Tutorial: A Beginner’s Guide to AngularJS

(P) Codever is an open source bookmarks and snippets manager for developers & co. See our How To guides to help you get started. Public bookmarks repos on Github ⭐🙏
Contents
Introduction
Since I first started using AngularJS several years ago, it has become the most popular single-page application (SPA) framework for the JavaScript language. AngularJS makes it easy to solve simple problems fast, yet has enough features to enable development teams to build complex large-scale applications.
This tutorial aims to introduce a complete beginner to AngularJS by explaining fundamental concepts and building a sample application. The application will be a simplified admin area for a multi-author blog.
History is important
I believe a limited appreciation of history is important. Often in software, good ideas are a twist or improvement on something that has been done before, made possible by some other technology improvement(s). In the case of AngularJS, the vast performance improvements made to browser JavaScript engines has made ambitious SPA frameworks possible.
AngularJS is an adaptation of the Model-View-Controller (MVC) pattern first created in 1979. MVC helps developers to separate the concerns involved in building user interfaces. Jeff Atwood of Coding Horror wrote of an example that we’re all very familiar with, a static webpage:
MV*
Subtle variations to the MVC pattern have come in recent years, none more so than by JavaScript SPA frameworks. For this reason, the MV* phrase has been coined as a way of describing ‘whatever’ variation works for you.
So although it’s definitely worth understanding the origins of MVC, keep in mind that AngularJS is MV* and does not implement MVC strictly.
Setup
This tutorial will use the stable version of AngularJS at the time of writing (1.4.4). It also intends to only include the bare minimum required to run an AngularJS application.
To get up and running, download an uncompressed copy of AngularJS from angularjs.org. Create a file structure as follows with your favorite text editor:
Initial file layout
Edit the contents of index.html
as follows:
<!DOCTYPE html> <html> <head> <title>Udemy tutorials - admin area</title> </head> <body> <script src="angular.js"></script> </body> </html>
Note that in the above snippet we’re serving angular.js
from the local file system. This saves us from having to introduce any additional moving parts and is adequate for our purposes. Open index.html
in your favorite browser and, although you’ll see nothing but a white screen, check to make sure that you don’t have any errors in your browser’s developer tools/console.
We will add additional .js
files as we progress through the tutorial.
Directives
Considering the fact that I discussed MVC in the introduction, it would be very tempting to structure this tutorial by the AngularJS implementation of models, views and controllers. But there is a construct so fundamental to AngularJS that to not start with it would be an error.
That construct is directives. AngularJS describes itself as “HTML enhanced for web apps.” Directives are what facilitate this enhancement; they extend the capability of HTML elements.
I often find analogies to be a useful tool for explaining abstract concepts, such as directives. Consider a homeowner who is having some building work completed on their house. The team of laborers required to complete any non-trivial building project is varied in role and might include:
- a bricklayer
- an outfitter
- a plumber
The types of capability directives extend HTML elements with can be categorized similarly:
- structural
- decorative
- plumbing
If we keep the analogy going a moment longer, HTML is the house and directives are the team of laborers.
Previously I introduced a sample application that we will build during this tutorial. Let’s build upon that by displaying an article title contained within a plain JavaScript object:
var article = { title: "Learn AngularJS" };
Edit index.html
to contain the ng-app
, ng-controller
and ng-bind
directives as follows:
<!DOCTYPE html> <html ng-app="udemyAdmin"> <head> <title>Udemy tutorials - admin area</title> </head> <body ng-controller="articleCtrl"> <div ng-bind="title"></div> <div ng-bind="getTitle()"></div> <script src="angular.js"></script> <script src="app.js"></script> <script src="controllers.js"></script> </body> </html>
You’ll notice that two .js
files have appeared. There is a small amount of JavaScript required to make the article title appear in the <div>
s and app.js
and controllers.js
is where it happens. Before we create them, I want to briefly discuss the three directives introduced in the above snippet.
ng-app
ng-app
is a plumbing directive. AngularJS looks for which HTML element has the ng-app
directive attached to it and uses it to bootstrap an application. ng-app
can be placed on any HTML element, not just <html>
, and connects a module (in our case, ‘udemyAdmin’) to it. Modules will be covered shortly.
ng-controller
ng-controller
is a plumbing directive. It connects a model and any related behaviors to a branch of the HTML tree made available by the usage of ng-app
.
ng-bind
ng-bind
is a plumbing directive. It connects a model property or the result of a model behavior to the text content of an HTML element. In our example, the title
property and getTitle
behaviour is available because ng-bind
has been used on a <div>
which is a child of <body>
(where ng-controller
has been used).
Edit the file structure to match the following:
New file layout
Edit the contents of app.js
as follows:
angular.module('udemyAdmin', []);
In the above snippet, we first create a module called ‘udemyAdmin’. This module is created with no dependencies (denoted by the empty array). Modules allow us to partition different parts of an application anytime we wish; this is useful in larger applications. For example, a full Udemy blog application might be partitioned as follows:
angular.module('udemy', ['udemy.courses', 'udemy.authors', 'udemy.students']); angular.module('udemy.courses', ['udemy.courses.archived', 'udemy.courses.current']); // and so on
Edit the contents of controllers.js
as follows:
angular.module('udemyAdmin').controller('articleCtrl', function($scope) { var title = "Learn AngularJS"; $scope.title = title; $scope.getTitle = function() { return title; }; });
In the above snippet, we first retrieve the ‘udemyAdmin’ module (angular.module
is dual operation based on the number of arguments) and then register a controller within it. $scope
is a core AngularJS component available for injection to controllers (we cover injection in a later section of this tutorial). $scope
is the model exposed by articleCtrl;
we add properties and behaviors we want to be available for use in our HTML.
Although app.js
and controllers.js
are very small at the moment, they will grow as this tutorial progresses and it is useful to refer to snippets of code by file name.
Bindings
In the previous section of this tutorial, we introduced ng-bind
as a way to connect controller models to the text content of HTML elements. There is an alternative, non-directive way of achieving this, namely double curly bracket notation or . To use it in
index.html
we would edit the <div>
s as follows:
<div></div> <div></div>
Typically `` is used over ng-bind
as it is less verbose (4 characters versus 10).
ng-bind
does have one distinct advantage, though. It prevents any temporary flashing of curly braces, which can happen if there is a gap between the browser rendering HTML and AngularJS executing. The ng-cloak directive is an alternative solution, but we won’t go into details in this tutorial.
Expressions
Both and
ng-bind
require an AngularJS expression. AngularsJS expressions are similar in concept to JavaScript expressions but with subtle differences that I’ll highlight with examples.
We could change the expressions used in index.html
(currently title
and getTitle()
) to any of the following:
title + 1
– expressions can have operators. Here we are operating on a string, so 1 is converted to “1” (as per normal JavaScript) resulting in “Learn AngularJS1”.title + ': ' + description
– expressions are forgiving.description
doesn’t exist, but the result is “Learn AngularJS: ” rather than “Learn AngularJS: undefined” as you might expect.title + (description ? ': ' + description : '')
– conditionals are allowed but only in ternary form, so the result here is just “Learn AngularJS”.
Curly brackets are not allowed in AngularJS expressions, which means that blocks (e.g., if () { }
) and function declarations (e.g., function() { }
) are ruled out.
The recommendation is to have a model behavior instead of complex expressions. In general, it’s a good idea to keep your expressions looking attractive.
Two-way bindings
The connections made between controller models and HTML elements are more commonly known as ‘bindings’. All bindings in AngularJS are two-way. This means that any updates to bound model properties are automatically reflected in the HTML. Even at this early stage, our sample application has two-way bindings. It just doesn’t look like it because there is no binding that will update the model. Let’s change that now so that we can properly highlight two-way binding.
Edit index.html
as follows:
<!DOCTYPE html> <html ng-app="udemyAdmin"> <head> <title>Udemy tutorials - admin area</title> </head> <body ng-controller="articleCtrl"> <div ng-bind="title"></div> <div></div> <input type="text" ng-model="title" /> <script src="angular.js"></script> <script src="app.js"></script> <script src="controllers.js"></script> </body> </html>
In the above snippet, we’ve introduced an HTML text input element and attached an ng-model
directive to it. ng-model
listens for DOM events (e.g., keyup
) raised by the element, reads its value and writes it to the bound model property specified in the expression (i.e., title
). The text content of the <div>
s updates thanks to the binding created by ng-bind
and . Try this out and witness the magic of two-way binding.
Directives again
In the first directives section, I introduced an analogy (a team of laborers completing some building works) and used it to begin categorizing directives as one of three types:
- structural
- decorative
- plumbing
Unfortunately, I then proceeded to only introduce plumbing directives (ng-app
, ng-controller
and ng-bind
). Let’s fix that now by returning to directives and discussing ng-repeat
.
ng-repeat
Most applications built with AngularJS are likely to be data-driven; reading, creating, updating and deleting data. Real-world data is often plural in nature – for example:
- walking into your local foreign exchange provider to buy currency for your next holiday. You’ll likely be presented a board of rates for the most popular currency pairings.
- visiting your local bookstore and browsing your favorite niche. You’ll likely be presented with a range of choices for a book to leaf through.
ng-repeat
is a structural directive because it modifies the “bricks and mortar” of our HTML. At a simple level, it creates the HTML element it is attached to multiple times based on the contents of a model collection property, e.g., an array.
Lets see how ng-repeat
can help us display an array of articles. Edit index.html
to the following:
<!DOCTYPE html> <html ng-app="udemyAdmin"> <head> <title>Udemy tutorials - admin area</title> </head> <body ng-controller="articleCtrl"> <div ng-repeat="article in articles" ng-bind="article.title"></div> <ul> <li ng-repeat="article in articles"> <p></p> </li> </ul> <script src="angular.js"></script> <script src="app.js"></script> <script src="controllers.js"></script> </body> </html>
In the snippet above, article in articles
is much like a traditional JavaScript for…in loop, with article
being the name of the variable assigned to on each iteration of articles
. article
is then available for use in other directives and bindings, either on that HTML element or a descendant (the snippet shows both).
Edit controllers.js
so that an appropriate model property is available as follows:
angular.module('udemyAdmin').controller('articleCtrl', function($scope) { $scope.articles = [ { title: "Learn AngularJS" }, { title: "JavaScript closures explained!" } ]; });
All being well, you’ll see the same two article titles displayed twice – inside <div>
and <p>
elements, respectively.
ng-repeat
is a very powerful directive. We’ve used it to simply create an HTML element per item in a static array. It is more likely that you’ll use ng-repeat
with dynamic collections – for example, live feeds of buy and sell prices for foreign exchange currency pairings. ng-repeat
will track changes in dynamic collections and, in most cases, behave as you would expect, creating, editing and destroying HTML elements appropriately.
limitTo filter
We can further demonstrate the power of ng-repeat
by combining it with a filter. Filters could demand a section of this tutorial to themselves, but in general they are applied to expressions, using the |
operator, and format the result.
AngularJS comes with a handful of built-in filters, including limitTo
. limitTo
will format the result of the expression we have used with ng-repeat
by returning a specified number of articles from an optional starting index.
Edit index.html
as follows:
<!DOCTYPE html> <html ng-app="udemyAdmin"> <head> <title>Udemy tutorials - admin area</title> </head> <body ng-controller="articleCtrl"> <div ng-repeat="article in articles | limitTo:1 " ng-bind="article.title"></div> <ul> <li ng-repeat="article in articles | limitTo:1:1"> <p></p> </li> </ul> <script src="angular.js"></script> <script src="app.js"></script> <script src="controllers.js"></script> </body> </html>
In the above snippet, both ng-repeat
expressions include a contrived usage of the limitTo
filter (we only have two articles thus far, so limiting to one seems pretty pointless).
The first expression is limited to one article, but we haven’t specified a starting index, so 0 is used. This results in HTML of <div>Learn AngularJS</div>
.
The second expression is also limited to one article, but this time we have specified a starting index. This results in HTML of <p>JavaScript closures explained!</p>
.
Custom directives
AngularJS comes with a handful of structural directives, such as ng-include
for fetching and displaying external HTML fragments, but ng-repeat
is the most significant.
This is beyond the scope of this tutorial, but it is possible to create your own custom directives of any type (plumbing, structural or decorative). To whet your appetite for what might lie ahead in your own AngularJS journey, check out the directives in the excellent Angular Material library or my own angular-charts library.
You will probably note that we still haven’t discussed decorative directives. That will come later in one final directives section.
Injection
In the first directives section, we mentioned injection, stating that we would cover it in a later section. Injection might get a little complicated, but I’ll do my best to keep it concise and understandable.
Minimal theory
Dependency injection is a common software design pattern that is frequently used in any non-trivial application. By writing an AngularJS application, you will use dependency injection, even if you’re not aware of it. As such, it makes sense to learn a little of the theory behind the pattern.
The essence of the pattern is to separate the responsibilities of construction and usage of dependencies. In this tutorial, we have already seen an example of this, as follows:
angular.module('udemyAdmin').controller('articleCtrl', function($scope) { $scope.title = "Learn AngularJS"; });
In this snippet, articleCtrl
is dependent on $scope
in order to make model properties and behaviors available for binding. We do not know how $scope
is constructed by AngularJS (nor do we really care), as long as we can use it within articleCtrl
.
If we were to construct $scope
ourselves, in a very simplified form it might look something like the following:
angular.module('udemyAdmin').controller('articleCtrl', function() { var $scope = new angular.Scope(); $scope.title = "Learn AngularJS"; });
In this snippet, the responsibility for the construction and use of the ``<div id="toc_container" class="no_bullets">
Contents
- Introduction
- Directives
- Bindings
- Directives again
- Injection
- Directives continued
- Server integration
- Summary
</div>
Introduction
Since I first started using AngularJS several years ago, it has become the most popular single-page application (SPA) framework for the JavaScript language. AngularJS makes it easy to solve simple problems fast, yet has enough features to enable development teams to build complex large-scale applications.
This tutorial aims to introduce a complete beginner to AngularJS by explaining fundamental concepts and building a sample application. The application will be a simplified admin area for a multi-author blog.
History is important
I believe a limited appreciation of history is important. Often in software, good ideas are a twist or improvement on something that has been done before, made possible by some other technology improvement(s). In the case of AngularJS, the vast performance improvements made to browser JavaScript engines has made ambitious SPA frameworks possible.
AngularJS is an adaptation of the Model-View-Controller (MVC) pattern first created in 1979. MVC helps developers to separate the concerns involved in building user interfaces. Jeff Atwood of Coding Horror wrote of an example that we’re all very familiar with, a static webpage:
MV*
Subtle variations to the MVC pattern have come in recent years, none more so than by JavaScript SPA frameworks. For this reason, the MV* phrase has been coined as a way of describing ‘whatever’ variation works for you.
So although it’s definitely worth understanding the origins of MVC, keep in mind that AngularJS is MV* and does not implement MVC strictly.
Setup
This tutorial will use the stable version of AngularJS at the time of writing (1.4.4). It also intends to only include the bare minimum required to run an AngularJS application.
To get up and running, download an uncompressed copy of AngularJS from angularjs.org. Create a file structure as follows with your favorite text editor:
Initial file layout
Edit the contents of index.html
as follows:
<!DOCTYPE html> <html> <head> <title>Udemy tutorials - admin area</title> </head> <body> <script src="angular.js"></script> </body> </html>
Note that in the above snippet we’re serving angular.js
from the local file system. This saves us from having to introduce any additional moving parts and is adequate for our purposes. Open index.html
in your favorite browser and, although you’ll see nothing but a white screen, check to make sure that you don’t have any errors in your browser’s developer tools/console.
We will add additional .js
files as we progress through the tutorial.
Directives
Considering the fact that I discussed MVC in the introduction, it would be very tempting to structure this tutorial by the AngularJS implementation of models, views and controllers. But there is a construct so fundamental to AngularJS that to not start with it would be an error.
That construct is directives. AngularJS describes itself as “HTML enhanced for web apps.” Directives are what facilitate this enhancement; they extend the capability of HTML elements.
I often find analogies to be a useful tool for explaining abstract concepts, such as directives. Consider a homeowner who is having some building work completed on their house. The team of laborers required to complete any non-trivial building project is varied in role and might include:
- a bricklayer
- an outfitter
- a plumber
The types of capability directives extend HTML elements with can be categorized similarly:
- structural
- decorative
- plumbing
If we keep the analogy going a moment longer, HTML is the house and directives are the team of laborers.
Previously I introduced a sample application that we will build during this tutorial. Let’s build upon that by displaying an article title contained within a plain JavaScript object:
var article = { title: "Learn AngularJS" };
Edit index.html
to contain the ng-app
, ng-controller
and ng-bind
directives as follows:
<!DOCTYPE html> <html ng-app="udemyAdmin"> <head> <title>Udemy tutorials - admin area</title> </head> <body ng-controller="articleCtrl"> <div ng-bind="title"></div> <div ng-bind="getTitle()"></div> <script src="angular.js"></script> <script src="app.js"></script> <script src="controllers.js"></script> </body> </html>
You’ll notice that two .js
files have appeared. There is a small amount of JavaScript required to make the article title appear in the <div>
s and app.js
and controllers.js
is where it happens. Before we create them, I want to briefly discuss the three directives introduced in the above snippet.
ng-app
ng-app
is a plumbing directive. AngularJS looks for which HTML element has the ng-app
directive attached to it and uses it to bootstrap an application. ng-app
can be placed on any HTML element, not just <html>
, and connects a module (in our case, ‘udemyAdmin’) to it. Modules will be covered shortly.
ng-controller
ng-controller
is a plumbing directive. It connects a model and any related behaviors to a branch of the HTML tree made available by the usage of ng-app
.
ng-bind
ng-bind
is a plumbing directive. It connects a model property or the result of a model behavior to the text content of an HTML element. In our example, the title
property and getTitle
behaviour is available because ng-bind
has been used on a <div>
which is a child of <body>
(where ng-controller
has been used).
Edit the file structure to match the following:
New file layout
Edit the contents of app.js
as follows:
angular.module('udemyAdmin', []);
In the above snippet, we first create a module called ‘udemyAdmin’. This module is created with no dependencies (denoted by the empty array). Modules allow us to partition different parts of an application anytime we wish; this is useful in larger applications. For example, a full Udemy blog application might be partitioned as follows:
angular.module('udemy', ['udemy.courses', 'udemy.authors', 'udemy.students']); angular.module('udemy.courses', ['udemy.courses.archived', 'udemy.courses.current']); // and so on
Edit the contents of controllers.js
as follows:
angular.module('udemyAdmin').controller('articleCtrl', function($scope) { var title = "Learn AngularJS"; $scope.title = title; $scope.getTitle = function() { return title; }; });
In the above snippet, we first retrieve the ‘udemyAdmin’ module (angular.module
is dual operation based on the number of arguments) and then register a controller within it. $scope
is a core AngularJS component available for injection to controllers (we cover injection in a later section of this tutorial). $scope
is the model exposed by articleCtrl;
we add properties and behaviors we want to be available for use in our HTML.
Although app.js
and controllers.js
are very small at the moment, they will grow as this tutorial progresses and it is useful to refer to snippets of code by file name.
Bindings
In the previous section of this tutorial, we introduced ng-bind
as a way to connect controller models to the text content of HTML elements. There is an alternative, non-directive way of achieving this, namely double curly bracket notation or . To use it in
index.html
we would edit the <div>
s as follows:
<div></div> <div></div>
Typically `` is used over ng-bind
as it is less verbose (4 characters versus 10).
ng-bind
does have one distinct advantage, though. It prevents any temporary flashing of curly braces, which can happen if there is a gap between the browser rendering HTML and AngularJS executing. The ng-cloak directive is an alternative solution, but we won’t go into details in this tutorial.
Expressions
Both and
ng-bind
require an AngularJS expression. AngularsJS expressions are similar in concept to JavaScript expressions but with subtle differences that I’ll highlight with examples.
We could change the expressions used in index.html
(currently title
and getTitle()
) to any of the following:
title + 1
– expressions can have operators. Here we are operating on a string, so 1 is converted to “1” (as per normal JavaScript) resulting in “Learn AngularJS1”.title + ': ' + description
– expressions are forgiving.description
doesn’t exist, but the result is “Learn AngularJS: ” rather than “Learn AngularJS: undefined” as you might expect.title + (description ? ': ' + description : '')
– conditionals are allowed but only in ternary form, so the result here is just “Learn AngularJS”.
Curly brackets are not allowed in AngularJS expressions, which means that blocks (e.g., if () { }
) and function declarations (e.g., function() { }
) are ruled out.
The recommendation is to have a model behavior instead of complex expressions. In general, it’s a good idea to keep your expressions looking attractive.
Two-way bindings
The connections made between controller models and HTML elements are more commonly known as ‘bindings’. All bindings in AngularJS are two-way. This means that any updates to bound model properties are automatically reflected in the HTML. Even at this early stage, our sample application has two-way bindings. It just doesn’t look like it because there is no binding that will update the model. Let’s change that now so that we can properly highlight two-way binding.
Edit index.html
as follows:
<!DOCTYPE html> <html ng-app="udemyAdmin"> <head> <title>Udemy tutorials - admin area</title> </head> <body ng-controller="articleCtrl"> <div ng-bind="title"></div> <div></div> <input type="text" ng-model="title" /> <script src="angular.js"></script> <script src="app.js"></script> <script src="controllers.js"></script> </body> </html>
In the above snippet, we’ve introduced an HTML text input element and attached an ng-model
directive to it. ng-model
listens for DOM events (e.g., keyup
) raised by the element, reads its value and writes it to the bound model property specified in the expression (i.e., title
). The text content of the <div>
s updates thanks to the binding created by ng-bind
and . Try this out and witness the magic of two-way binding.
Directives again
In the first directives section, I introduced an analogy (a team of laborers completing some building works) and used it to begin categorizing directives as one of three types:
- structural
- decorative
- plumbing
Unfortunately, I then proceeded to only introduce plumbing directives (ng-app
, ng-controller
and ng-bind
). Let’s fix that now by returning to directives and discussing ng-repeat
.
ng-repeat
Most applications built with AngularJS are likely to be data-driven; reading, creating, updating and deleting data. Real-world data is often plural in nature – for example:
- walking into your local foreign exchange provider to buy currency for your next holiday. You’ll likely be presented a board of rates for the most popular currency pairings.
- visiting your local bookstore and browsing your favorite niche. You’ll likely be presented with a range of choices for a book to leaf through.
ng-repeat
is a structural directive because it modifies the “bricks and mortar” of our HTML. At a simple level, it creates the HTML element it is attached to multiple times based on the contents of a model collection property, e.g., an array.
Lets see how ng-repeat
can help us display an array of articles. Edit index.html
to the following:
<!DOCTYPE html> <html ng-app="udemyAdmin"> <head> <title>Udemy tutorials - admin area</title> </head> <body ng-controller="articleCtrl"> <div ng-repeat="article in articles" ng-bind="article.title"></div> <ul> <li ng-repeat="article in articles"> <p></p> </li> </ul> <script src="angular.js"></script> <script src="app.js"></script> <script src="controllers.js"></script> </body> </html>
In the snippet above, article in articles
is much like a traditional JavaScript for…in loop, with article
being the name of the variable assigned to on each iteration of articles
. article
is then available for use in other directives and bindings, either on that HTML element or a descendant (the snippet shows both).
Edit controllers.js
so that an appropriate model property is available as follows:
angular.module('udemyAdmin').controller('articleCtrl', function($scope) { $scope.articles = [ { title: "Learn AngularJS" }, { title: "JavaScript closures explained!" } ]; });
All being well, you’ll see the same two article titles displayed twice – inside <div>
and <p>
elements, respectively.
ng-repeat
is a very powerful directive. We’ve used it to simply create an HTML element per item in a static array. It is more likely that you’ll use ng-repeat
with dynamic collections – for example, live feeds of buy and sell prices for foreign exchange currency pairings. ng-repeat
will track changes in dynamic collections and, in most cases, behave as you would expect, creating, editing and destroying HTML elements appropriately.
limitTo filter
We can further demonstrate the power of ng-repeat
by combining it with a filter. Filters could demand a section of this tutorial to themselves, but in general they are applied to expressions, using the |
operator, and format the result.
AngularJS comes with a handful of built-in filters, including limitTo
. limitTo
will format the result of the expression we have used with ng-repeat
by returning a specified number of articles from an optional starting index.
Edit index.html
as follows:
<!DOCTYPE html> <html ng-app="udemyAdmin"> <head> <title>Udemy tutorials - admin area</title> </head> <body ng-controller="articleCtrl"> <div ng-repeat="article in articles | limitTo:1 " ng-bind="article.title"></div> <ul> <li ng-repeat="article in articles | limitTo:1:1"> <p></p> </li> </ul> <script src="angular.js"></script> <script src="app.js"></script> <script src="controllers.js"></script> </body> </html>
In the above snippet, both ng-repeat
expressions include a contrived usage of the limitTo
filter (we only have two articles thus far, so limiting to one seems pretty pointless).
The first expression is limited to one article, but we haven’t specified a starting index, so 0 is used. This results in HTML of <div>Learn AngularJS</div>
.
The second expression is also limited to one article, but this time we have specified a starting index. This results in HTML of <p>JavaScript closures explained!</p>
.
Custom directives
AngularJS comes with a handful of structural directives, such as ng-include
for fetching and displaying external HTML fragments, but ng-repeat
is the most significant.
This is beyond the scope of this tutorial, but it is possible to create your own custom directives of any type (plumbing, structural or decorative). To whet your appetite for what might lie ahead in your own AngularJS journey, check out the directives in the excellent Angular Material library or my own angular-charts library.
You will probably note that we still haven’t discussed decorative directives. That will come later in one final directives section.
Injection
In the first directives section, we mentioned injection, stating that we would cover it in a later section. Injection might get a little complicated, but I’ll do my best to keep it concise and understandable.
Minimal theory
Dependency injection is a common software design pattern that is frequently used in any non-trivial application. By writing an AngularJS application, you will use dependency injection, even if you’re not aware of it. As such, it makes sense to learn a little of the theory behind the pattern.
The essence of the pattern is to separate the responsibilities of construction and usage of dependencies. In this tutorial, we have already seen an example of this, as follows:
angular.module('udemyAdmin').controller('articleCtrl', function($scope) { $scope.title = "Learn AngularJS"; });
In this snippet, articleCtrl
is dependent on $scope
in order to make model properties and behaviors available for binding. We do not know how $scope
is constructed by AngularJS (nor do we really care), as long as we can use it within articleCtrl
.
If we were to construct $scope
ourselves, in a very simplified form it might look something like the following:
angular.module('udemyAdmin').controller('articleCtrl', function() { var $scope = new angular.Scope(); $scope.title = "Learn AngularJS"; });
In this snippet, the responsibility for the construction and use of the`` dependency is solely with articleCtrl
. A principle of good software design is for a component, such as articleCtrl
, to do one thing and to do it well.
Dependency annotations
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:
angular.module('udemyAdmin').controller('articleCtrl', ['$scope', function($scope) { $scope.title = "Learn AngularJS"; }]);
In the above snippet, we’re now passing an array as the second parameter to controller()
. 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:
a.m('udemyAdmin').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 file structure to match the following:
Edit the contents of services.js
as follows:
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:
angular.module('udemyAdmin').controller('articleCtrl', function($scope, pageSize) { $scope.articles = [ { title: "Arduino Tutorial" }, { title: "After Effects Tutorial" }, { title: "Django 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:
<!DOCTYPE html> <html ng-app="udemyAdmin"> <head> <title>Udemy tutorials - 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"> </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:
angular.module('udemyAdmin') .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:
angular.module('udemyAdmin') .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:
angular.module('udemyAdmin').controller('articleCtrl', function($scope, pageSize, calculateCategoryPercentage) { $scope.articles = [ { title: "Arduino Tutorial", categories: ['tutorial', 'hardware'] }, { title: "After Effects Tutorial", categories: ['tutorial', 'graphics'] }, { title: "Django 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 in index.html
with a simple binding, such as:
<span>Percentage of categories used: </span>
Value and factory services are a more advanced technique, but their use allows us to achieve a level of decoupling in our AngularJS applications that will greatly improve the speed of implementing any future requirements.
Directives continued
In this last directives section, we again return to our analogy (a team of laborers completing some building works), as we have yet to cover any decorative directives.
Rather than strictly being about styling and CSS, by decorative I mean in the sense of HTML elements being decorated with additional behaviour. This is like having a new kitchen fitted, which isn’t stuctural “bricks and mortar”, nor is it plumbing as the pipes have already been laid and are waiting to be used. But a new kitchen definitely gives a house additional behavior, especially if it has one of those fancy taps producing instant boiling water.
Lets now look at a couple of example decorative directives, namely ng-click
andng-checked
.
ng-click
ng-click
decorates HTML elements with the ability to invoke a model behaviour when they are clicked, which I suspect is fairly self-explanatory.
In the previous section, we introduced a factory servicecalculateCategoryPercentage
and used it with a hard-coded list of articles. Let’s make our application a little more interesting by allowing new articles to be created.
Edit index.html
as follows:
<!DOCTYPE html> <html ng-app="udemyAdmin"> <head> <title>Udemy tutorials - admin area</title> </head> <body ng-controller="articleCtrl"> <input type="text" ng-model="newTitle" placeholder="Enter article name..." /> <button name="Add" ng-click="addArticle()">Add</button> <hr /> Number of articles to display: <input type="text" ng-model="numArticles" /> <ul> <li ng-repeat="article in articles | limitTo:numArticles"> </li> </ul> <span>Percentage of categories used: </span> <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, we’ve added two new HTML elements. The text input element will update a model property newTitle
via the ng-model
directive (which has been discussed in a previous section on “Bindings”). The button element will invoke a model behaviour addArticle
when clicked via ng-click
.
Edit controllers.js
as follows:
angular.module('udemyAdmin').controller('articleCtrl', function($scope, pageSize, calculateCategoryPercentage) { $scope.articles = [ { title: "Arduino Tutorial", categories: ['tutorial', 'hardware'] }, { title: "After Effects Tutorial", categories: ['tutorial', 'graphics'] }, { title: "Django Tutorial", categories: ['tutorial'] } ]; $scope.newTitle = ''; $scope.addArticle = function() { $scope.articles.push({ title: $scope.newTitle, categories: [] }); }; $scope.numArticles = pageSize; $scope.categoryPercentage = calculateCategoryPercentage($scope.articles); });
In the above snippet, addArticle
uses $scope.newTitle
when invoked to push a new article onto $scope.articles
. Note that the new article has an empty categories array.
ng-checked
Our blog admin application needs to allow articles, new and old alike, to be re-categorized. Wouldn’t it be great if when this happens $scope.categoryPercentage
is re-calculated? This can be accomplished with another usage of ng-click
and a new directive ng-checked
. Let’s explore how now.
Edit controllers.js
as follows:
angular.module('udemyAdmin').controller('articleCtrl', function($scope, calculateCategoryPercentage, pageSize, availableCategories) { $scope.categories = availableCategories; $scope.articles = [ { title: "Arduino Tutorial", categories: ['tutorial', 'hardware'] }, { title: "After Effects Tutorial", categories: ['tutorial', 'graphics'] }, { title: "Django Tutorial", categories: ['tutorial'] } ]; $scope.$watch('articles', function(articles) { $scope.categoryPercentage = calculateCategoryPercentage(articles); }, true); $scope.containsCategory = function(article, category) { return article.categories.indexOf(category) >= 0; }; $scope.toggleCategory = function(article, category) { var index = article.categories.indexOf(category); if (index == -1) { article.categories.push(category); } else { article.categories.splice(index, 1); } }; $scope.newTitle = ''; $scope.addArticle = function() { $scope.articles.push({ title: $scope.newTitle, categories: [] }); }; $scope.numArticles = pageSize; });
We’ve introduced quite a lot in the above snippet. Let’s discuss each in turn.
We previously mentioned that we would find another use foravailableCategories
. It is now also injected into articleCtrl
and set as$scope.categories
.
When we create bindings in our HTML (e.g., via ng-bind
, ,
ng-model
and many others), we are implicitly creating watches. At a simplified level, whenever AngularJS detects something that might affect an application (be it a browser event, HTTP response, and many others), it checks all of its watches against their previous values and updates bindings if there is a difference. We use $scope.$watch
to create a manual watch of the articles
array and re-calculate $scope.categoryPercentage
if there is a difference. The third parameter to $scope.$watch
indicates that we want a “deep” watch. For more information, please have a look at this excellent StackOverflow answer.
Finally, we introduce two new model behaviors for interacting with an article’s categories. $scope.containsCategory
simply saves us from having an ugly expression in our HTML. $scope.toggleCategory
either adds or removes a category based on whether an article is already categorized as such.
To see all of this in action, edit index.html
as follows:
<!DOCTYPE html> <html ng-app="udemyAdmin"> <head> <title>Udemy tutorials - admin area</title> </head> <body ng-controller="articleCtrl"> <input type="text" ng-model="newTitle" placeholder="Enter article name..." /> <button name="Add" ng-click="addArticle()">Add</button> <hr /> Number of articles to display: <input type="text" ng-model="numArticles" /> <div ng-repeat="article in articles | limitTo:numArticles"> <p></p> <label ng-repeat="category in categories"> <input type="checkbox" ng-checked="containsCategory(article, category)" ng-click="toggleCategory(article, category)" /> </label> </div> <span>Percentage of categories used: </span> <script src="angular.js"></script> <script src="app.js"></script> <script src="controllers.js"></script> <script src="services.js"></script> </body> </html>
In the above snippet, we come across the usage of ng-click
and ng-checked
mentioned a little while ago. We’ve introduced a second ng-repeat
loop to create a checkbox for each category. This ng-repeat
loop is nested and, as such, creates the set of checkboxes for each article. ng-checked
is used to ensure that the underlying state of the HTML checkbox element is kept in sync with the model (this is very similar to ng-model
). ng-click
is used to invoke toggleCategory
with the appropriate article and category.
If you try this out, you should see the percentage of categories used being updated as you categorize articles.
Server integration
The aim of this section is to discuss two services AngularJS provides for integrating with a server, namely $http
and $resource
. We’ll look at $http
first and $resource
second. $resource
is essentially a RESTful extension of $http
.
Previously we introduced a feature to our blog admin application that calculated the percentage of used categories. We originally used this feature to look at how we can create our own injectable components. We’ll now use it again to discuss $http
.
$http
The requirements for our blog admin application have changed, and we now need to fetch the categories from a server.
The simplest way to achieve this is to use $http
directly in articleCtrl
.
The contents of articleCtrl
became quite lengthy in the previous section, so the following two snippets are intended to be illustrative.
angular.module('udemyAdmin').controller('articleCtrl', function($scope, $http, calculateCategoryPercentage) { $scope.articles = [ { title: "Arduino Tutorial", categories: ['tutorial', 'hardware'] }, { title: "After Effects Tutorial", categories: ['tutorial', 'graphics'] }, { title: "Django Tutorial", categories: ['tutorial'] } ]; $scope.categoryPercentage = 0; $http.get('/categories').then(function(response) { $scope.categoryPercentage = calculateCategoryPercentage($scope.articles, response.data); }); });
In the above snippet, we issue an HTTP request to the URL ‘/categories’ and, if it is successful, we pass the response data to calculateCategoryPercentage
. There are two key things to note here.
First, there will be a delay while the server responds. During this delay, the $scope
property is not set and therefore a percentage is not displayed in the browser. We can lessen the impact of this by setting an initial value.
Second, we have returned calculateCategoryPercentage
to its simple value service version. But rather than hard-coding the available categories, calculateCategoryPercentage
is passed as follows:
angular.module('udemyAdmin') .value('calculateCategoryPercentage', function(articles, availableCategories) { 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)); });
$httpBackend
For the purposes of a tutorial, it is desirable to avoid introducing any tangential complexity. In our case, that would be a server to respond to the URL ‘/categories’.
AngularJS has a very useful additional module for backend-less development, namely $httpBackend
. This is perfect for tutorials, but I have also used it in a team of four AngularJS developers building an application against a partially built server. We didn’t want the road map of server features to impact us, so for a period of time, we developed backend-less.
Download angular-mocks.js and add another script reference in index.html
in the following order:
<script src="angular.js"></script> <script src="angular-mocks.js"></script> <script src="app.js"></script> <script src="controllers.js"></script> <script src="services.js"></script>
There are two variants of $httpBackend
, one for unit testing and one for backend-less development. We need to tell our application to use the backend-less variant; this is achieved by adding a dependency to ngMockE2E
to our udemyAdmin
module. We then to configure how $httpBackend
should respond to URL and HTTP verb combinations. Currently we only need one combination, HTTP GET and ‘/categories’.
Edit app.js
as follows:
angular.module('udemyAdmin', ['ngMockE2E']).run(function($httpBackend) { $httpBackend.whenGET('/categories').respond(['tutorial', 'hardware', 'graphics']); });
In the above snippet, the run
method of our ‘udemyAdmin’ module is used for one-off configuration code that needs to execute as our application starts up.
Promises
We first looked at fetching categories from a server by using $http
directly inarticleCtrl
. For this to work, we would have to undo some of the decoupling we previously achieved by having calculateCategoryPercentage
as a factory service with a dependency on availableCategories
.
Let’s now return to that and see if we can extract $http
from articleCtrl
and use it in availableCategories
. If we can achieve that, articleCtrl
and calculateCategoryPercentage
can become oblivious to how the available categories are obtained. This is a more advanced technique that will help keep code flexible as our applications grow.
We glossed over it in our simple illustration of $http
, but the get
method made the HTTP request (which is inherently asynchronous) and immediately returned a Promise object. A Promise object represents the future value of an asynchronous operation. If the operation succeeds, then the Promise is ‘resolved’; if it fails, then the Promise is ‘rejected’. A Promise object exposes several methods, one of which then
allows us to register a resolution and/or rejection handler. In our simple usage, we just registered a resolution handler as follows:
$http.get('/categories').then(function(response) { $scope.categoryPercentage = calculateCategoryPercentage($scope.articles, response.data); });
Promises can, without doubt, get reasonably complicated. If you want to learn more, I suggest studying the readme for the Q library. AngularJS implements a lightweight version of the Q library.
Now that we’ve briefly introduced Promises, let’s look at how we can use them to get our decoupling back. In the following snippets, I’ve added numbered comments, which we’ll discuss in detail shortly.
Edit controllers.js
as follows:
angular.module('udemyAdmin').controller('articleCtrl', function($scope, calculateCategoryPercentage, pageSize, availableCategories) { $scope.categories = []; availableCategories.then(function(categories) { Array.prototype.splice.apply( $scope.categories, [$scope.categories.length, 0].concat(categories) ); }); $scope.articles = [ { title: "Arduino Tutorial", categories: ['tutorial', 'hardware'] }, { title: "After Effects Tutorial", categories: ['tutorial', 'graphics'] }, { title: "Django Tutorial", categories: ['tutorial'] } ]; $scope.$watch('articles', function(articles) { // 3 calculateCategoryPercentage(articles).then(function(percentage) { // 6 $scope.categoryPercentage = percentage; }); }, true); $scope.containsCategory = function(article, category) { return article.categories.indexOf(category) >= 0; }; $scope.toggleCategory = function(article, category) { var index = article.categories.indexOf(category); if (index == -1) { article.categories.push(category); } else { article.categories.splice(index, 1); } }; $scope.newTitle = ''; $scope.addArticle = function() { $scope.articles.push({ title: $scope.newTitle, categories: [] }); }; $scope.numArticles = pageSize; });
Edit services.js
as follows:
angular.module('udemyAdmin') .value('pageSize', 1) .factory('availableCategories', function($http) { // 1 return $http.get('/categories').then(function(response) { return response.data; }); }) .factory('calculateCategoryPercentage', function(availableCategories) { // 2 return function calculate(articles) { var uniqueCategories = []; articles.forEach(function(article) { article.categories.forEach(function(category) { if (uniqueCategories.indexOf(category) == -1) { uniqueCategories.push(category); } }); }); // 4 return availableCategories.then(function(categories) { // 5 return Math.floor(100 * (uniqueCategories.length / categories.length)); }); }; });
In the above snippets, the numbered comments relate to the following points:
articleCtrl
depends oncalculateCategoryPercentage
which depends onavailableCategories
. The Promise object returned from$http.get('/categpries').then
is registered asavailableCategories
. Note thatavailableCategories
has been made a factory service so that it, too, can have a dependency injected (namely,$http
).calculateCategoryPercentage
is next in the dependency chain, so the functioncalculate
is registered.articleCtrl
runs as the last step in the dependency chain. It callscalculateCategoryPercentage
each time its articles change (via$scope.$watch
). A Promise object is returned, andarticleCtrl
assigns a resolution handler.- A resolution handler is assigned to the
availableCategories
Promise object. Assigning resolution handlers viathen
returns another Promise object, which allows for chained resolution. availableCategories
is resolved (i.e., a response is received from the server), and the category percentage is calculated and returned.- The chained resolution set in step 4 allows
articleCtrl
to set the category percentage as a$scope
property.
You may wonder about the benefit of this approach over the simpler use of $http
direct in articleCtrl
we had previously. In both approaches, we have had to change how calculateCategoryPercentage
is used in articleCtrl
. In this approach, the change has been to work with Promises. Promises are a very general API. For example, in the future our application could first look in the browser’s local storage for categories before resorting to an HTTP server call. The Promise API that articleCtrl
works with wouldn’t change one bit, but behind the scenes, obtaining the categories would be more involved. With Promises, articleCtrl
has no insight into how the categories are obtained for the calculation, just that somehow they are.
$resource
Until now, the initial articles in our blog admin application have been hard-coded in articleCtrl
. This clearly isn’t the most flexible application around; as such, the requirements have changed yet again.
We’re now asked to provide a means of retrieving and creating articles stored on a server. The server has provided us a RESTful API for interacting with articles. Sending an HTTP GET request to the URL ‘/articles’ will return an array of articles, while sending an HTTP POST request will create and return a new article. $resource
is another additional module and is perfect for working with this type of API.
Download angular-resource.js and add another script reference in index.html
in the following order:
<script src="angular.js"></script> <script src="angular-resource.js"></script> <script src="angular-mocks.js"></script> <script src="app.js"></script> <script src="controllers.js"></script> <script src="services.js"></script>
As with $http
previously, the simplest way to get up and running with $resource
is to use it directly in articleCtrl
. We could encapsulate $resource
in another factory service so that articleCtrl
isn’t aware of how articles are retrieved and created. For our purposes, the first approach allows us to focus on the detail of using $resource
, but in a larger real-world application, I would certainly consider the latter approach.
Edit app.js
as follows:
angular.module('udemyAdmin', ['ngResource', 'ngMockE2E']).run(function($httpBackend) { $httpBackend.whenGET('/categories').respond(['tutorial', 'hardware', 'graphics']); });
The change in the above snippet is a simple one: we’ve just added an additional dependency for the ‘udemyAdmin’ module, namely ‘ngResource’.
Edit controllers.js
as follows:
angular.module('udemyAdmin').controller('articleCtrl', function($scope, calculateCategoryPercentage, pageSize, availableCategories, $resource) { var Article = $resource('/articles'); $scope.categories = []; availableCategories.then(function(categories) { Array.prototype.splice.apply( $scope.categories, [$scope.categories.length, 0].concat(categories) ); }); $scope.articles = Article.query(); $scope.$watch('articles', function(articles) { calculateCategoryPercentage(articles).then(function(percentage) { $scope.categoryPercentage = percentage; }); }, true); $scope.containsCategory = function(article, category) { return article.categories.indexOf(category) >= 0; }; $scope.toggleCategory = function(article, category) { var index = article.categories.indexOf(category); if (index == -1) { article.categories.push(category); } else { article.categories.splice(index, 1); } }; $scope.newTitle = ''; $scope.addArticle = function() { var newArticle = new Article({ title: $scope.newTitle }); newArticle.$save().then(function(article) { $scope.articles.push(article); }); }; $scope.numArticles = pageSize; });
In the above snippet, $resource
is declared as a dependency of articleCtrl
and subsequently injected in. We create a resource object Article
by invoking $resource
with our URL.
We then invoke Article.query
to retrieve an array of articles from the server. This results in an HTTP GET request to ‘/articles’. As this is an asynchronous operation, it does not block the application waiting for the server to respond, but what value would be useful to return in the meantime? $resource
immediately returns an empty array, which will be filled when the server responds. This is a neat trick, as combined with setting the array as a property on $scope
, any binding we make in our HTML will automatically update.
We haven’t looked at the contents of index.html
for a while, but the usage of ng-repeat
doesn’t need to change, even though we’ve switched to $resource
and $scope.articles
is, at first, an empty array.
Returning to the snippet of articleCtrl
above, when $scope. addArticle
is invoked we now create a new instance of Article
with $scope.newTitle
as its title. We then invoke $save
resulting in an HTTP POST request to ‘/articles’. A Promise object is returned, which is resolved by the server responding with an HTTP 200 status code and body. A instance of Article
is created from the server response for us, and we simply push it onto $scope.articles
. The usage of $scope.addArticle
and $scope.newTitle
in index.html
does not need to change.
$httpBackend again
As $resource
is an extension of $http
, we can also use $httpBackend
to configure how requests to the URL ‘/articles’ should be handled.
Edit app.js
as follows:
angular.module('udemyAdmin', ['ngResource', 'ngMockE2E']).run(function($httpBackend) { $httpBackend.whenGET('/categories').respond(['tutorial', 'hardware', 'graphics']); var articles = [ { title: "Arduino Tutorial", categories: ['tutorial', 'hardware'] }, { title: "After Effects Tutorial", categories: ['tutorial', 'graphics'] }, { title: "Django Tutorial", categories: ['tutorial'] } ]; $httpBackend.whenGET('/articles').respond(articles); $httpBackend.whenPOST('/articles').respond(function(method, url, data) { var article = angular.fromJson(data); article.categories = article.categories || []; articles.push(article); return [200, article, {}]; }); });
In the above snippet, we’ve simply moved the hard-coded array of articles from articleCtrl
and returned it whenever an HTTP GET request is made to ‘/articles’. An HTTP POST request to ‘/articles’ pushes another article onto the array, meaning that any subsequent GET requests would include it.
Summary
If you’ve made this far, after all that copy and pasting of snippets, then I salute you for sticking with me.
You should have a working sample application that demonstrates AngularJS as a great framework that’s both powerful and fun to use.
This tutorial certainly doesn’t cover everything. My hope is that it has covered enough of the basics that, along with a couple of intermediate concepts, it will give you the knowledge to continue the journey you have started with AngularJS.
Thanks for reading!
Published on Codepedia.org with the permission of Udemy – source AngularJS Tutorial: A Beginner’s Guide to AngularJS from https://blog.udemy.com/