Read Recipes With Angular PDF
Read Recipes With Angular PDF
js
Table of Contents
Introduction
Code Examples
How to contact me
Acknowledgements
An Introduction to Angular.js
Including the Angular.js Library Code in an HTML Page
Binding a Text Input to an Expression
Responding to Click Events using Controllers
Converting Expression Output with Filters
Creating Custom HTML Elements with Directives
Controllers
Assigning a Default Value to a Model
Changing a Model Value with a Controller Function
Encapsulating a Model Value with a Controller Function
Responding to Scope Changes
Sharing Models Between Nested Controllers
Sharing Code Between Controllers using Services
Testing Controllers
Directives
Enabling/Disabling DOM Elements Conditionally
Changing the DOM in Response to User Actions
Rendering an HTML Snippet in a Directive
Rendering a Directives DOM Node Children
Passing Configuration Params Using HTML Attributes
Repeatedly Rendering Directives DOM Node Children
Directive-to-Directive Communication
Testing Directives
Filters
Formatting a String With a Currency Filter
Implementing a Custom Filter to Reverse an Input String
Passing Configuration Params to Filters
Filtering a List of DOM Nodes
Chaining Filters together
Testing Filters
Consuming Externals Services
Introduction
Angular.js is an open-source Javascript MVC (Model-View-Controller) framework developed by
Google. It gives Javascript developers a highly structured approach to developing rich browserbased applications which, leads to very high productivity.
If you are using Angular.js, or considering it, this cookbook provides easy-to-follow recipes for
If you are using Angular.js, or considering it, this cookbook provides easy-to-follow recipes for
issues you are likely to face. Each recipe solves a specific problem and provides a solution and indepth discussion of it.
Code Examples
All code examples in this book can be found on Github (http://github.com/fdietz/recipes-withangular-js-examples).
How to contact me
If you have questions or comments please get in touch with:
Frederik Dietz ([email protected])
Acknowledgements
Special thanks go to my english editor and friend Robert William Smales!
Thanks go to John Lindquist for his excellent screencast project Egghead IO (http://egghead.io/),
Lukas Ruebbelke for his awesome videos (http://www.youtube.com/user/simpulton/videos?
flow=grid&view=0), Matias Niemela for his great blog (http://www.yearofmoo.com/). And of course
the whole development team behind Angular.js!
{{mainmatter}}
An Introduction to Angular.js
Including the Angular.js Library Code in an HTML
Page
Problem
You wish to use Angular.js on a web page.
Solution
In order to get your first Angular.js app up and running you need to include the Angular Javascript
file via script tag and make use of the ng-app directive.
1 <html>
2
<head>
3
<script src="http://ajax.googleapis.com/ajax/libs/
4
angularjs/1.0.4/angular.js">
5
</script>
6
</head>
7
<body ng-app>
8
<p>This is your first angular expression: {{ 1 + 2 }}</p>
9
</body>
10 </html>
Discussion
Adding the ng-app directive tells Angular to kick in its magic. The expression {{ 1 + 2 }} is
evaluated by Angular and the result 3 is rendered. Note that removing ng-app will result in the
browser rendering the expression as is instead of evaluating it. Play around with the expression!
You can, for instance, concatenate Strings and invert or combine Boolean values.
For reasons of brevity we will skip the boilerplate code in the following recipes.
On document ready we bind to the keypress event in the text input and replace the text in the
paragraph in the callback function. Using jQuery you need to deal with document ready callbacks,
element selection, event binding and the context of this. Quite a lot of concepts to swallow and
lines of code to maintain!
1 function MyCtrl($scope) {
2
$scope.visible = true;
3
4
$scope.toggle = function() {
5
$scope.visible = !$scope.visible;
6
};
7 }
The browser will only show the Hello World paragraph if the checkbox is checked and will hide it
otherwise.
You can find the complete example on github (https://github.com/fdietz/recipes-with-angular-js-
Controllers
Controllers in Angular provide the business logic to handle view behavior, for example responding
to a user clicking a button or entering some text in a form. Additionally, controllers prepare the
model for the view template.
As a general rule, a controller should not reference or manipulate the DOM directly. This has the
benefit of simplifying unit testing controllers.
In this case {{value}} will simply be not rendered at all due to the fact that expression evaluation
in Angular.js is forgiving for undefined and null values.
This function can be directly called in an expression, in our example we use ng-init :
1 <div ng-controller="MyCtrl">
2
<p ng-init="incrementValue(1)">{{value}}</p>
3 </div>
Discussion
The ng-init directive is executed on page load and calls the function incrementValue defined in
MyCtrl . Functions are defined on the scope very similar to values but must be called with the
familiar parenthesis syntax.
Of course, it would have been possible to increment the value right inside of the expression with
value = value +1 but imagine the function being much more complex! Moving this function into a
controller separates our business logic from our declarative view template and we can easily write
unit tests for it.
In our example we use the text input value to print a friendly greeting.
1 <div ng-controller="MyCtrl">
2
<input type="text" ng-model="name" placeholder="Enter your name">
3
<p>{{greeting}}</p>
4 </div>
The value greeting will be changed whenever theres a change to the name model and the value is
not blank.
You can find the complete example on github (https://github.com/fdietz/recipes-with-angular-jsexamples/tree/master/chapter2/recipe4).
Discussion
The first argument name of the $watch function is actually an Angular expression, so you can use
more complex expressions (for example: [value1, value2] | json ) or even a Javascript function. In
this case you need to return a string in the watcher function:
1 $scope.$watch(function() {
2
return $scope.name;
3 }, function(newValue, oldValue) {
4
console.log("change detected: " + newValue)
5 });
The second argument is a function which gets called whenever the expression evaluation returns a
different value. The first parameter is the new value and the second parameter the old value.
Internally, this uses angular.equals to determine equality which means both objects or values pass
The app.js file contains the controller definition and initializes the scope with some defaults:
1
2
3
4
5
6
7
8
9
10
11
Play around with the various input fields and see how changes affect each other.
You can find the complete example on github (https://github.com/fdietz/recipes-with-angular-jsexamples/tree/master/chapter2/recipe5).
Discussion
All the default values are defined in MyCtrl which is the parent of MyNestedCtrl . When making
changes in the first input field, the changes will be in sync with the other input fields bound to the
name variable. They all share the same scope variable as long as they only read from the variable. If
you change the nested value, a copy in the scope of the MyNestedCtrl will be created. From now on,
changing the first input field will only change the nested input field which explicitly references the
parent scope via $parent.name expression.
The object-based value behaves differently in this regard. Whether you change the nested or the
MyCtrl scopes input fields, the changes will stay in sync. In Angular, a scope prototypically inherits
properties from a parent scope. Objects are therefore references and kept in sync. Whereas
primitive types are only in sync as long they are not changed in the child scope.
Generally I tend to not use $parent.name and instead always use objects to share model properties.
If you use $parent.name the MyNestedCtrl not only requires certain model attributes but also a
correct scope hierarchy to work with.
Tip: The Chrome plugin Batarang (https://github.com/angular/angularjs-batarang)
simplifies debugging the scope hierarchy by showing you a tree of the nested scopes. It
is awesome!
The service and controller implementation in app.js implements a user service and the controllers
The service and controller implementation in app.js implements a user service and the controllers
set the scope initially:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function as params.
Using dependency injection here is quite nice for testing your controllers, since you can easily inject
a UserService stub. The only downside is that you cant minify the code from above without
breaking it, since the injection mechanism relies on the exact string representation of UserService .
It is therefore recommended to define dependencies using inline annotations, which keeps working
even when minified:
1 app.controller("AnotherCtrl", ["$scope", "UserService",
2
function($scope, UserService) {
3
$scope.firstUser = UserService.first();
4
}
5 ]);
The syntax looks a bit funny, but since strings in arrays are not changed during the minification
process it solves our problem. Note that you could change the parameter names of the function,
since the injection mechanism relies on the order of the array definition only.
This requires you to use a temporary variable to call the $inject service. Again, you could change
the function parameter names. You will most probably see both versions applied in apps using
Angular.
Testing Controllers
Problem
You wish to unit test your business logic.
Solution
Implement a unit test using Jasmine (http://pivotal.github.com/jasmine/) and the angular-seed
(https://github.com/angular/angular-seed) project. Following our previous $watch recipe, this is
how our spec would look.
1 describe('MyCtrl', function(){
2
var scope, ctrl;
3
4
beforeEach(inject(function($controller, $rootScope) {
5
scope = $rootScope.$new();
6
ctrl = $controller(MyCtrl, { $scope: scope });
7
}));
8
9
it('should change greeting value if name value is changed', function() {
10
scope.name = "Frederik";
11
scope.$digest();
12
expect(scope.greeting).toBe("Greetings Frederik");
13
});
14 });
The scope and controller initialization is a bit more involved. We use inject to initialize the scope
The scope and controller initialization is a bit more involved. We use inject to initialize the scope
and controller as closely as possible to how our code would behave at runtime too. We cant just
initialize the scope as a Javascript object {} since we would then not be able to call $watch on it.
Instead $rootScope.$new() will do the trick. Note that the $controller service requires MyCtrl to
be available and uses an object notation to pass in dependencies.
The $digest call is required in order to trigger a watch execution after we have changed the scope.
We need to call $digest manually in our spec whereas at runtime Angular will do this for us
automatically.
Directives
Directives are one of the most powerful concepts in Angular since they let you invent new HTML
elements specific to your application. This allows you to create reusable components which
encapsulate complex DOM structures, stylesheets and even behavior.
We use a link function in our directive implementation to change the CSS of the paragraph.
1 var app = angular.module("MyApp", []);
2
3 app.directive("myWidget", function() {
4
var linkFunction = function(scope, element, attributes) {
5
var paragraph = element.children()[0];
6
$(paragraph).on("click", function() {
7
$(this).css({ "background-color": "red" });
8
});
9
};
10
11
return {
12
restrict: "E",
13
link: linkFunction
14
};
15 });
1 <div my-widget>
2
<p>Hello World</p>
3 </div>
Whether you use the attribute or element mechanism will depend on your use case. Generally
speaking one would use the element mechanism to define a custom reusable component. The
attribute mechanism would be used whenever you want to configure some element or enhance it
with more behavior. Other available options are using the directive as a class attribute or a
comment.
The directive method expects a function that can be used for initialization and injection of
dependencies.
1 app.directive("myWidget", function factory(injectables) {
2
// ...
3 }
The link function is much more interesting since it defines the actual behavior. The scope, the
actual HTML element my-widget and the HTML attributes are passed as params. Note that this has
nothing to do with Angulars dependency injection mechanism. Ordering of the parameters is
important!
Firstly we select the paragraph element, which is a child of the my-widget element using Angulars
function as defined by element. In the second step we use jQuery to bind to the click
event and modify the css property on click. This is of particular interest since we have a mixture of
Angular element functions and jQuery here. In fact under the hood Angular will use jQuery in the
children() function if it is defined and will fall back to jqLite (shipped with Angular) otherwise. You
children()
In this case element is alreay a jQuery element and we can directly use the on function.
Implement a directive and use the template attribute to define the HTML.
1
2
3
4
5
6
7
8
9
10
11
12
<body ng-app="MyApp">
<my-widget/>
</body>
var app = angular.module("MyApp", []);
app.directive("myWidget", function() {
return {
restrict: "E",
template: "<p>Hello World</p>"
};
});
Another option would be to use a file for the HTML snippet. In this case you will need to use the
templateUrl attribute, for example as follows:
1 app.directive("myWidget", function() {
2
return {
3
restrict: "E",
4
replace: true,
5
templateUrl: "widget.html"
6
};
7 });
The widget.html should reside in the same directory as the index.html file. This will only work if
you use a web server to host the file. The example on Github uses angular-seed as bootstrap again.
Solution
Use the transclude attribute together with the ng-transclude directive.
1
2
3
4
5
6
7
8
9
10
11
12
13
<my-widget>
<p>This is my paragraph text.</p>
</my-widget>
var app = angular.module("MyApp", []);
app.directive("myWidget", function() {
return {
restrict: "E",
transclude: true,
template: "<div ng-transclude><h3>Heading</h3></div>"
};
});
This will render a div element containing an h3 element and append the directives child node
with the paragraph element below.
You can find the complete example on github (https://github.com/fdietz/recipes-with-angular-jsexamples/tree/master/chapter3/recipe4).
Discussion
In this context, transclusion refers to the inclusion of a part of a document into another document
by reference. The ng-transclude attribute should be positioned depending on where you want your
child nodes to be appended.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<body ng-app="MyApp">
<div my-widget="Hello World"></div>
</body>
var app = angular.module("MyApp", []);
app.directive("myWidget", function() {
var linkFunction = function(scope, element, attributes) {
scope.text = attributes["myWidget"];
};
return {
restrict: "A",
template: "<p>{{text}}</p>",
link: linkFunction
};
});
In Angular this is called an isolate scope. It does not prototypically inherit from the parent scope
and is especially useful when creating reusable components.
Lets look into another way of passing params to the directive. This time we will define an HTML
element my-widget2 .
The scope definition using @text is binding the text model to the directives attribute. Note that any
changes to the parent scope text will change the local scope text , but not the other way around.
If you want instead to have a bi-directional binding between the parent scope and the local scope,
you should use the = equality character:
1 scope: {
2
text: "=text"
3 }
Changes to the local scope will also change the parent scope.
Another option would be to pass an expression as a function to the directive using the & character.
1 <my-widget-expr fn="count = count + 1"></my-widget-expr>
2
3 app.directive("myWidgetExpr", function() {
4
var linkFunction = function(scope, element, attributes) {
5
scope.text = scope.fn({ count: 5 });
6
};
7
8
return {
9
restrict: "E",
10
template: "<p>{{text}}</p>",
11
link: linkFunction,
12
scope: {
13
fn: "&fn"
14
}
15
};
16 });
We pass the attribute fn to the directive and since the local scope defines fn accordingly we can
call the function in the linkFunction and pass in the expression arguments as a hash.
You wish to render an HTML snippet repeatedly using the directives child nodes as the stamp
content.
Solution
Implement a compile function in your directive.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<repeat-ntimes repeat="10">
<h1>Header 1</h1>
<p>This is the paragraph.</p>
</repeat-n-times>
var app = angular.module("MyApp", []);
app.directive("repeatNtimes", function() {
return {
restrict: "E",
compile: function(tElement, attrs) {
var content = tElement.children();
for (var i=1; i<attrs.repeat; i++) {
tElement.append(content.clone());
}
}
};
});
Directive-to-Directive Communication
Problem
You wish a directive to communicate with another directive and augment each others behavior
using a well-defined interface (API).
Solution
We implement a directive basket with a controller function and two other directive orange and
apple
which require this controller. Our example starts with an apple and orange directive used
as attributes.
1 <body ng-app="MyApp">
2
<basket apple orange>Roll over me and check the console!</basket>
3 </body>
The basket directive manages an array to which one can add apples and oranges!
And finally the apple and orange directives, which add themselves to the basket using the baskets
controller.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
app.directive("apple", function() {
return {
require: "basket",
link: function(scope, element, attrs, basketCtrl) {
basketCtrl.addApple();
}
};
});
app.directive("orange", function() {
return {
require: "basket",
link: function(scope, element, attrs, basketCtrl) {
basketCtrl.addOrange();
}
};
});
If you hover with the mouse over the rendered text the console should print and the baskets
content.
You can find the complete example on github (https://github.com/fdietz/recipes-with-angular-jsexamples/tree/master/chapter3/recipe7).
Discussion
Basket
is the example directive that demonstrates an API using the controller function, whereas the
Basket
is the example directive that demonstrates an API using the controller function, whereas the
and orange directives augment the basket directive. They both define a dependency to the
basket controller with the require attribute. The link function then gets basketCtrl injected.
apple
Note how the basket directive is defined as an HTML element and the apple and orange directives
are defined as HTML attributes (the default for directives). This demonstrates the typical use case of
a reusable component augmented by other directives.
Now there might be other ways of passing data back and forth between directives - we have seen
the different semantics of using the (isolated) context in directives in previous recipes - but whats
especially great about the controller is the clear API contract it lets you define.
Testing Directives
Problem
You wish to test your directive with a unit test. As an example we will use a tab component directive
implementation, which can easily be used in your HTML document.
1 <tabs>
2
<pane title="First Tab">First pane.</pane>
3
<pane title="Second Tab">Second pane.</pane>
4 </tabs>
The directive implementation is split into the tabs and the pane directive. Let us start with the tabs
directive.
1 app.directive("tabs", function() {
2
return {
3
restrict: "E",
4
transclude: true,
5
scope: {},
6
controller: function($scope, $element) {
7
var panes = $scope.panes = [];
8
9
$scope.select = function(pane) {
10
angular.forEach(panes, function(pane) {
11
pane.selected = false;
12
});
13
pane.selected = true;
14
console.log("selected pane: ", pane.title);
15
};
16
17
this.addPane = function(pane) {
18
if (!panes.length) $scope.select(pane);
19
panes.push(pane);
20
};
21
},
22
template:
23
'<div class="tabbable">' +
24
'<ul class="nav nav-tabs">' +
25
'<li ng-repeat="pane in panes"' +
26
'ng-class="{active:pane.selected}">'+
27
'<a href="" ng-click="select(pane)">{{pane.title}}</a>' +
28
'</li>' +
29
'</ul>' +
30
'<div class="tab-content" ng-transclude></div>' +
31
'</div>',
32
replace: true
33
};
34 });
It manages a list of panes and the selected state of the panes . The template definition makes use
of the selection to change the class and responds on the click event to change the selection.
The pane directive depends on the tabs directive to add itself to it.
1 app.directive("pane", function() {
2
return {
3
require: "^tabs",
4
restrict: "E",
5
transclude: true,
6
scope: {
7
title: "@"
8
},
9
link: function(scope, element, attrs, tabsCtrl) {
10
tabsCtrl.addPane(scope);
11
},
12
template:
13
'<div class="tab-pane" ng-class="{active: selected}"' +
14
'ng-transclude></div>',
15
replace: true
16
};
17 });
Solution
Using the angular-seed in combination with jasmine and jasmine-jquery, you can implement a unit
test.
Combining jasmine with jasmine-jquery gives you useful assertions like toHaveClass and actions
like click , which are used extensively in the example above.
To prepare the template we use $compile and $digest in the beforeEach function and then access
the resulting Angular element in our tests.
The angular-seed project was slightly extended to add jquery and jasmine-jquery to the project.
The example code was extracted from Vojta Jina Github example (https://github.com/vojtajina/ngdirective-testing/tree/start) - the author of the awesome Testacular
(https://github.com/testacular/testacular).
Filters
Angular Filters are typically used to format expressions in bindings in your template. They transform
the input data to a new formatted data type.
In our example we explicitly load the German locale settings and therefore the default formatting
will be in German. The English locale is shipped by default, so theres no need to include the
angular-locale_en.js file. If you remove the script tag, you will see the formatting change to English
instead. This means in order for a localized application to work correctly you need to load the
corresponding locale file. All available locale files can be seen on github
(https://github.com/angular/angular.js/tree/master/src/ngLocale).
<body ng-app="MyApp">
<input type="text" ng-model="text" placeholder="Enter text"/>
<p>Input: {{ text }}</p>
<p>Filtered input: {{ text | reverse }}</p>
</body>
var app = angular.module("MyApp", []);
app.filter("reverse", function() {
return function(input) {
var result = "";
input = input || "";
for (var i=0; i<input.length; i++) {
result = input.charAt(i) + result;
}
return result;
};
});
Problem
You wish to make your filter customizable by introducing config params.
Solution
Angular filters can be passed a hash of params which can be directly accessed in the filter function.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<body ng-app="MyApp">
<input type="text" ng-model="text" placeholder="Enter text"/>
<p>Input: {{ text }}</p>
<p>Filtered input: {{ text | reverse: { suffix: "!"} }}</p>
</body>
var app = angular.module("MyApp", []);
app.filter("reverse", function() {
return function(input, options) {
input = input || "";
var result = "";
var suffix = options["suffix"] || "";
for (var i=0; i<input.length; i++) {
result = input.charAt(i) + result;
}
if (input.length > 0) result += suffix;
return result;
};
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<body ng-app="MyApp">
<ul ng-init="names = ['Peter', 'Anton', 'John']">
<li ng-repeat="name in names | exclude:'Peter' ">
<span>{{name}}</span>
</li>
</ul>
</body>
var app = angular.module("MyApp", []);
app.filter("exclude", function() {
return function(input, exclude) {
var result = [];
for (var i=0; i<input.length; i++) {
if (input[i] !== exclude) {
result.push(input[i]);
}
}
return result;
};
});
We pass Peter as the single param to the exclude filter, which will render all names except Peter .
You can find the complete example on github (https://github.com/fdietz/recipes-with-angular-jsexamples/tree/master/chapter4/recipe4).
Discussion
The filter implementation loops through all names and creates a result array excluding Peter. Note
that the actual syntax of the filter function didnt change at all from our previous example with the
String input.
Discussion
The pipe symbol ( | ) is used to chain multiple filters together. First we will start with the initial Array
of names. After applying the exclude filter the Array contains only ['Anton', 'John'] and
afterwards we will sort the names in ascending order.
I leave the implementation of the sortAscending filter as an exercise to the reader ;-)
Testing Filters
Problem
You wish to unit test your new filter. Let us start with an easy filter, which renders a checkmark
depending on a boolean value.
1
2
3
4
5
6
7
8
9
10
11
12
Solution
Use the angular-seed project as a bootstrap again.
1 describe('MyApp Tabs', function() {
2
beforeEach(module('MyApp'));
3
4
describe('checkmark', function() {
5
it('should convert boolean values to unicode checkmark or cross',
6
inject(function(checkmarkFilter) {
7
expect(checkmarkFilter(true)).toBe('\u2713');
8
expect(checkmarkFilter(false)).toBe('\u2718');
9
}));
10
});
11 });
Discussion
The beforeEach loads the module and the it method injects the filter function for us. Note, that it
has to be called checkmarkFilter , otherwise Angular cant inject our filter function correctly.
Angular has built-in support for communication with remote HTTP servers. The $http
(http://docs.angularjs.org/api/ng.%24http) service handles low-level AJAX requests via the
browsers XMLHttpRequest (http://en.wikipedia.org/wiki/XMLHttpRequest) object or via JSONP
(http://en.wikipedia.org/wiki/JSONP). The $resource
(http://docs.angularjs.org/api/ngResource.%24resource) service lets you interact with RESTful
data sources and provides high-level behaviors which naturally map to RESTful resources.
<body ng-app="MyApp">
<div ng-controller="PostsCtrl">
<ul ng-repeat="post in posts">
<li>{{post.title}}</li>
</ul>
</div>
</body>
var app = angular.module("MyApp", []);
app.controller("PostsCtrl", function($scope, $http) {
$http.get('data/posts.json').
success(function(data, status, headers, config) {
$scope.posts = data;
}).
error(function(data, status, headers, config) {
// log error
});
});
You can find the complete example using the angular-seed project on github
(https://github.com/fdietz/recipes-with-angular-js-examples/tree/master/chapter5/recipe1).
Discussion
The controller defines a dependency to the $scope and the $http module. An HTTP GET request to
the data/posts.json endpoint is carried out with the get method. It returns a $promise
(http://docs.angularjs.org/api/ng.%24q) object with a success and an error method. Once
successful, the JSON data is assigned to $scope.posts to make it available in the template.
The $http service supports the HTTP verbs get , head , post , put , delete and jsonp . We are
going to look into more examples in the following chapters.
The $http service automatically adds certain HTTP headers like for example
X-Requested-With: XMLHttpRequest
$http.defaults
. But you can also set custom HTTP headers by yourself using the
function:
1 $http.defaults.headers.common["X-Custom-Header"] = "Angular.js"
Until now the $http service does not really look particularly special. But if you look into the
documentation (http://docs.angularjs.org/api/ng.%24http) you find a whole lot of nice features
like, for example, request/response transformations, to automatically deserialize JSON for you,
response caching, response interceptors to handle global error handling, authentication or other
preprocessing tasks and, of course, promise support. We will look into the $q service, Angulars
promise/deferred service in a later chapter.
Let us now start by defining the application module and our Post model as an Angular service:
1 var app = angular.module('myApp', ['ngResource']);
2
3 app.factory("Post", function($resource) {
4
return $resource("/api/posts/:id");
5 });
Now we can use our service to retrieve a list of posts inside a controller (example: HTTP GET
/api/posts):
1 app.controller("PostIndexCtrl", function($scope, Post) {
2
Post.query(function(data) {
3
$scope.posts = data;
4
});
5 });
We can create a new post using save (example: HTTP POST /api/posts):
1 Post.save(data);
to work with our resource. In the example above we implement several controllers to cover
the typical use cases. The get and query methods expect three arguments, the request
parameters, the success and the error callback. The save method expects four arguments, the
request parameters, the POST data, the success and the error callback.
The $resource service currently does not support promises and therefore has a distinctly different
interface to the $http service. But we dont have to wait very long for it, since work has already
started in the 1.1 development branch to introduce promise support for the $resource service!
The returned object of a $resource query or get function is a $resource instance which provides
$save
, $remove and $delete methods. This allows you to easily fetch a resource and update it as
It is important to notice that the get call immediately returns an empty reference - in our case the
post
variable. Once the data is returned from the server the existing reference is populated and we
can change our post title and use the $save method conveniently.
Note that having an empty reference means that our post will not be rendered in the template. Once
the data is returned though, the view automatically re-renders itself showing the new data.
CONFIGURATION
What if your response of posts is not an array but a more complex json? This typically results in the
following error:
1 TypeError: Object #<Resource> has no method 'push'
Angular seems to expect your service to return a JSON array. Have a look at the following JSON
example, which wraps a posts array in a JSON object:
1 {
2
"posts": [
3
{
4
"id"
: 1,
5
"title" : "title 1"
6
},
7
{
8
"id": 2,
9
"title" : "title 2"
10
}
11
]
12 }
app.factory("Post", function($resource) {
return $resource("/api/posts/:id", {}, {
query: { method: "GET", isArray: false }
});
});
app.controller("PostIndexCtrl", function($scope, Post) {
Post.query(function(data) {
$scope.posts = data.posts;
});
});
We only change the configuration of the query action to not expect an array by setting the isArray
attribute to false . Then in our controller we can directly access data.posts .
It is generally good practice to encapsulate your model and $resource usage in an Angular service
module and inject that in your controller. This way you can easily reuse the same model in different
controllers and test it more easily.
You wish to synchronize multiple asynchronous functions and avoid Javascript callback hell.
Solution
As an example, we want to call three services in sequence and combine the result of them. Let us
start with a nested approach:
1 tmp = [];
2
3 $http.get("/app/data/first.json").success(function(data) {
4
tmp.push(data);
5
$http.get("/app/data/second.json").success(function(data) {
6
tmp.push(data);
7
$http.get("/app/data/third.json").success(function(data) {
8
tmp.push(data);
9
$scope.combinedNestedResult = tmp.join(", ");
10
});
11
});
12 });
We call the get function three times to retrieve three JSON documents each with an array of strings.
We havent even started adding error handling but already using nested callbacks the code
becomes messy and can be simplified using the $q service:
1
2
3
4
5
6
7
8
9
10
11
12
13
Before finishing this recipe let us quickly discuss an example where we have to create our own
deferred object:
1 function deferredTimer(success) {
2
var deferred = $q.defer();
3
4
$timeout(function() {
5
if (success) {
6
deferred.resolve({ message: "This is great!" });
7
} else {
8
deferred.reject({ message: "Really bad" });
9
}
10
}, 1000);
11
12
return deferred.promise;
13 }
Our startDeferredTimer function will get a success parameter which it passes along to the
deferredTimer
. The then function expects a success and an error callback as arguments which we
Testing Services
Problem
You wish to unit test your controller and service consuming a JSONP API.
Note that it slightly changed from the previous recipe as the TwitterAPI is pulled out of the
controller and resides in its own service now.
Solution
Use the angular-seed project and the $http_backend mocking service.
1 describe('MyCtrl', function(){
2
var scope, ctrl, httpBackend;
3
4
beforeEach(module("MyApp"));
5
6
beforeEach(
7
inject(
8
function($controller, $rootScope, TwitterAPI, $httpBackend) {
9
httpBackend = $httpBackend;
10
scope = $rootScope.$new();
11
ctrl = $controller("MyCtrl", {
12
$scope: scope, TwitterAPI: TwitterAPI });
13
14
var mockData = { key: "test" };
15
var url = "http://search.twitter.com/search.json?" +
16
"callback=JSON_CALLBACK&q=angularjs";
17
httpBackend.whenJSONP(url).respond(mockData);
18
}
19
)
20
);
21
22
it('should set searchResult on successful search', function() {
23
scope.searchTerm = "angularjs";
24
scope.search();
25
httpBackend.flush();
26
27
expect(scope.searchResult.key).toBe("test");
28
});
29
30 });
And the partial person_details.html shows more detailed information for a specific person :
1
2
3
4
5
<h3>{{person.name}} Details</h3>
<p>Name: {{person.name}}</p>
<p>Age: {{person.age}}</p>
<a href="#!persons">Go back</a>
This example is based on the Angular Seed Bootstrap again and will not work without starting the
development server.
You can find the complete example on github (https://github.com/fdietz/recipes-with-angular-jsexamples/tree/master/chapter6/recipe1).
Discussion
Lets give our app a try and open the index.html . The otherwise defined route redirects us from
index.html to index.html#!/persons . This is the default behavior in case other when conditions
dont apply.
Take a closer look at the index.html#!/persons URL and note how the hashbang (#!) separates the
index.html from the dynamic client-side part /persons . By default, Angular would use the hash (#)
character but we configured it to use the hashbang instead, following Googles Making AJAX
applications crawlable (https://developers.google.com/webmasters/ajax-crawling/) guide.
The /persons route loads the person_list.html partial via HTTP Request (that is also the reason
why it wont work without a development server). It shows a list of persons and therefore defines a
ng-controller directive inside the template. Let us assume for now that the controller
implementation defines a $scope.persons somewhere. Now for each person we also render a link to
show the details via #!persons/{{person.id}} .
The route definition for the persons details uses a placeholder /persons/:id which resolves to a
specific persons details, for example /persons/1 . The person_details.html partial and
additionally a controller are defined for this URL. The controller will be scoped to the partial, which
basically resembles our index.html template where we defined our own ng-controller directive to
achieve the same effect.
The person_details.html has a back link to #!persons which leads back to the person_list.html
page.
Let us come back to the ng-view directive. It is automatically bound to the router definition.
Therefore you can currently use only a single ng-view on your page. For example, you cannot use
nested ng-view s to achieve user interaction patterns with a first and second level navigation.
And finally the HTTP request for the partials happens only once and is then cached via
$templateCache service.
Finally, the hashbang-based routing is client-side only and doesnt require server-side
configuration. Let us look into the HTML5-based approach next.
1 app.config(function($routeProvider, $locationProvider) {
2
$locationProvider.html5Mode(true);
3
4
$routeProvider.
5
when("/persons",
6
{ templateUrl: "/partials/index.jade",
7
controller: "PersonIndexCtrl" }).
8
when("/persons/:id",
9
{ templateUrl: "/partials/show.jade",
10
controller: "PersonShowCtrl" }).
11
otherwise( { redirectTo: "/persons" });
12 });
There are no changes except for the html5Mode method, which enables our new routing mechanism.
The Controller implementation does not change at all.
We have to take care of the partial loading though. Our Express app will have to serve the partials
for us. The initial typical boilerplate for an Express app loads the module and creates a server:
1 var express = require('express');
2 var app
= module.exports = express.createServer();
We will skip the configuration here and jump directly to the server-side route definition:
1 app.get('/partials/:name', function (req, res) {
2
var name = req.params.name;
3
res.render('partials/' + name);
4 });
The Express route definition loads the partial with given name from the partials directory and
renders its content.
When supporting HTML5 routing, our server has to redirect all other URLs to the entry point of our
application, the index page. First we define the rendering of the index page, which contains the
ng-view
directive:
Then the catch all route which redirects to the same page:
1 app.get('*', function(req, res) {
2
res.redirect('/');
3 });
Let us quickly check the partials again. Note that they use the Jade (http://jade-lang.com/)
template engine, which relies on indentation to define the HTML document:
The index page creates a list of persons and the show page shows some more details:
1 h3 Person Details {{person.name}}
2 p Age: {{person.age}}
3 a(href="/persons") Back
The person details link /persons/{{person.id}} and the back link /persons are both now much
cleaner in my opinion compared to the hashbang URLs.
Have a look at the complete example on Github (https://github.com/fdietz/recipes-with-angular-jsexamples/tree/master/chapter6/recipe2) and start the Express app with node app.js .
You can find the complete example on github (https://github.com/fdietz/recipes-with-angular-jsexamples/tree/master/chapter6/recipe2).
Discussion
If we werent to redirect all requests to the root, what would happen if we were to navigate to the
persons list at http://localhost:3000/persons ? The Express framework would show us an error
because there is no route defined for persons , we only defined routes for our root URL ( / ) and the
partials URL /partials/:name . The redirect ensures that we actually end up at our root URL, which
then kicks in our Angular app. When the client-side routing takes over we then redirect back to the
/persons URL.
Also note how navigating to a persons detail page will load only the show.jade partial and
navigating back to the persons list wont carry out any server requests. Everything our app needs is
loaded once from the server and cached client-side.
If you have a hard time understanding the server implementation, I suggest you read the excellent
Express Guide (http://expressjs.com/guide.html). Additionally, there is going to be an extra
chapter, which goes into more details on how to integrate Angular.js with server-side frameworks.
Use the $location service in a controller to compare the address bar URL to the navigation menu
item the user selected.
The navigation menu is the classic ul/li menu using a class attribute to mark one of the li
elements as active :
1 <body ng-controller="MainCtrl">
2
<ul class="menu">
3
<li ng-class="menuClass('persons')"><a href="#!persons">Home</a></li>
4
<li ng-class="menuClass('help')"><a href="#!help">Help</a></li>
5
</ul>
6
...
7 </body>
Next we will define a login form to enter the username, skipping the password for the sake of
simplicity:
1 <form ng-submit="login()">
2
<label>Username</label>
3
<input type="text" ng-model="username">
4
<button>Login</button>
5 </form>
and finally the login controller, which sets the logged in user and redirects to the persons URL:
1 app.controller("LoginCtrl", function($scope, $location, $rootScope) {
2
$scope.login = function() {
3
$rootScope.loggedInUser = $scope.username;
4
$location.path("/persons");
5
};
6 });
Using Forms
Every website eventually uses some kind of form for users to enter data. Angular makes it
particularly easy to implement client-side form validations to give immediate feedback for an
improved user experience.
The novalidate attribute disables the HTML5 validations, which are client-side validations
supports by modern browsers. In our example we only want the Angular.js validations running to
have complete control over the look and feel.
The controller binds the form data to your user model and implements the submit() function:
It is still the same form but this time we defined the name attribute on the form and made the input
It is still the same form but this time we defined the name attribute on the form and made the input
required for the firstname.
Let us add some more debug output below the form:
1
2
3
4
5
6
7
Let us use Angulars form integration to actually show validation errors in the next recipe.
You wish to show validation errors to the user by marking the input field red and displaying an error
message.
Solution
We can use the ng-show directive to show an error message if a form input is invalid and CSS
classes to change the input elements background color depending on its state.
Let us start with the styling changes:
1 <style type="text/css">
2
input.ng-invalid.ng-dirty {
3
background-color: red;
4
}
5
input.ng-valid.ng-dirty {
6
background-color: green;
7
}
8 </style>
And here is a small part of the form with an error message for the input field:
1
2
3
4
5
<label>Firstname</label>
<input name="firstname" type="text" ng-model="user.firstname" required/>
<p ng-show="form.firstname.$invalid && form.firstname.$dirty">
Firstname is required
</p>
When using the .horizontal-form class Twitter Bootstrap uses div elements to structure label,
input fields and help messages into groups. The group div has the class control-group and the
actual controls are further nested in another div element with the CSS class controls . Twitter
Bootstrap shows a nice validation status when adding the CSS class error on the div with the
control-group
class.
Note that we use the ng-class directive on the control-group div. So lets look at the controller
implementation of the error function:
1 app.controller("User", function($scope) {
2
// ...
3
$scope.error = function(name) {
4
var s = $scope.form[name];
5
return s.$invalid && s.$dirty ? "error" : "";
6
};
7 });
The error function gets the input name attribute passed as a string and checks for the $invalid and
$dirty flags to return either the error class or a blank string.
You can find the complete example on github (https://github.com/fdietz/recipes-with-angular-jsexamples/tree/master/chapter7/recipe4).
Discussion
Again we check both the invalid and dirty flags because we only show the error message in case the
user has actually changed the form. Note that this ng-class function usage is pretty typical in
Angular since expressions do not support ternary checks.
You wish to filter and sort a relatively small list of items all available on the client.
Solution
For this example we will render a list of friends using the ng-repeat directive. Using the built-in
filter and orderBy filters we will filter and sort the friends list client-side.
1 <body ng-app="MyApp">
2
<div ng-controller="MyCtrl">
3
<form class="form-inline">
4
<input ng-model="query" type="text"
5
placeholder="Filter by" autofocus>
6
</form>
7
<ul ng-repeat="friend in friends | filter:query | orderBy: 'name' ">
8
<li>{{friend.name}}</li>
9
</ul>
10
</div>
11 </body>
A plain text input field is used to enter the filter query and bound to the filter . Any changes are
therefore directly used to filter the list.
The controller defines the default friends array:
1 app.controller("MyCtrl", function($scope) {
2
$scope.friends = [
3
{ name: "Peter",
age: 20 },
4
{ name: "Pablo",
age: 55 },
5
{ name: "Linda",
age: 20 },
6
{ name: "Marta",
age: 37 },
7
{ name: "Othello", age: 20 },
8
{ name: "Markus", age: 32 }
9
];
10 });
That way we can filter by name and age at the same time. And lastly you could call a function
defined in the controller, which does the filtering for you:
1
2
3
4
5
6
7
8
9
The filterFunction must return either true or false . In this example we use a regular expression
on the name starting with Ma to filter the list.
1 <div ng-controller="PaginationCtrl">
2
<table class="table table-striped">
3
<thead>
4
<tr>
5
<th>Id</th>
6
<th>Name</th>
7
<th>Description</th>
8
</tr>
9
</thead>
10
<tbody>
11
<tr ng-repeat="item in items |
12
offset: currentPage*itemsPerPage |
13
limitTo: itemsPerPage">
14
<td>{{item.id}}</td>
15
<td>{{item.name}}</td>
16
<td>{{item.description}}</td>
17
</tr>
18
</tbody>
19
<tfoot>
20
<td colspan="3">
21
<div class="pagination">
22
<ul>
23
<li ng-class="prevPageDisabled()">
24
<a href ng-click="prevPage()"> Prev</a>
25
</li>
26
<li ng-repeat="n in range()"
27
ng-class="{active: n == currentPage}" ng-click="setPage(n)">
28
<a href="#">{{n+1}}</a>
29
</li>
30
<li ng-class="nextPageDisabled()">
31
<a href ng-click="nextPage()">Next </a>
32
</li>
33
</ul>
34
</div>
35
</td>
36
</tfoot>
37
</table>
38 </div>
The offset Filter is responsible for selecting the subset of items for the current page. It uses the
slice function on the Array given the start param as the index.
1 app.filter('offset', function() {
2
return function(input, start) {
3
start = parseInt(start, 10);
4
return input.slice(start);
5
};
6 });
The controller manages the actual $scope.items array and handles the logic for enabling/disabling
the pagination buttons.
1 app.controller("PaginationCtrl", function($scope) {
2
3
$scope.itemsPerPage = 5;
4
$scope.currentPage = 0;
5
$scope.items = [];
6
7
for (var i=0; i<50; i++) {
8
$scope.items.push({
9
id: i, name: "name "+ i, description: "description " + i
10
});
11
}
12
13
$scope.prevPage = function() {
14
if ($scope.currentPage > 0) {
15
$scope.currentPage--;
16
}
17
};
18
19
$scope.prevPageDisabled = function() {
20
return $scope.currentPage === 0 ? "disabled" : "";
21
};
22
23
$scope.pageCount = function() {
24
return Math.ceil($scope.items.length/$scope.itemsPerPage)-1;
25
};
26
27
$scope.nextPage = function() {
28
if ($scope.currentPage < $scope.pageCount()) {
29
$scope.currentPage++;
30
}
31
};
32
33
$scope.nextPageDisabled = function() {
34
return $scope.currentPage === $scope.pageCount() ? "disabled" : "";
35
};
36
37 });
The offset filter uses the currentPage*itemsPerPage to calculate the offset for the array slice
The offset filter uses the currentPage*itemsPerPage to calculate the offset for the array slice
operation. This will generate an array from the offset to the end of the array. Then we use the built-in
limitTo filter to subset the array to the number of itemsPerPage . All this is done on the client side
with filters only.
The controller is responsible for supporting a nextPage and prevPage action and the accompanying
functions to check the disabled state of these actions via ng-class directive: nextPageDisabled
and prevPageDisabled . The prevPage function first checks if it has not reached the first page yet
before decrementing the currentPage and the nextPage does the same for the last page and the
same logic is applied for the disabled checks.
This example is already quite involved and I intentionally omitted an explanation of the rendering of
links between the previous and next buttons. The full implementation
(https://github.com/fdietz/recipes-with-angular-js-examples/tree/master/chapter8/recipe2) is
online though for you to investigate.
In order to simplify the example we will fake a server-side service by providing an Angular service
implementation for the items.
1 app.factory("Item", function() {
2
3
var items = [];
4
for (var i=0; i<50; i++) {
5
items.push({
6
id: i, name: "name "+ i, description: "description " + i
7
});
8
}
9
10
return {
11
get: function(offset, limit) {
12
return items.slice(offset, offset+limit);
13
},
14
total: function() {
15
return items.length;
16
}
17
};
18 });
The service manages a list of items and has methods for retrieving a subset of items for a given
offset and limit and the total number of items.
The controller uses dependency injection to access the Item service and contains almost the same
methods as our previous recipe.
You wish to paginate through server-side data with a Load More button, which just keeps
appending more data until no more data is available.
Solution
Lets start by looking at how the item table is rendered with the ng-repeat Directive.
1 <div ng-controller="PaginationCtrl">
2
<table class="table table-striped">
3
<thead>
4
<tr>
5
<th>Id</th>
6
<th>Name</th>
7
<th>Description</th>
8
</tr>
9
</thead>
10
<tbody>
11
<tr ng-repeat="item in pagedItems">
12
<td>{{item.id}}</td>
13
<td>{{item.name}}</td>
14
<td>{{item.description}}</td>
15
</tr>
16
</tbody>
17
<tfoot>
18
<td colspan="3">
19
<button class="btn" href="#" ng-class="nextPageDisabledClass()"
20
ng-click="loadMore()">Load More</button>
21
</td>
22
</tfoot>
23
</table>
24 </div>
The controller uses the same Item Service as used for the previous recipe and handles the logic for
the Load More button.
In a web framework like Ruby on Rails, the form submit will lead to a redirect with the flash
In a web framework like Ruby on Rails, the form submit will lead to a redirect with the flash
confirmation message, relying on the browser session. For our client-side approach we bind to route
changes and manage a queue of flash messages.
In our example we use a home page with a form and on form submit we navigate to another page
and show the flash message. We use the ng-view Directive and define the two pages as script tags
here.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
Note that the flash message just like the navigation is always shown but conditionally hidden
depending on whether there is a flash message available.
The route definition defines the pages, nothing new here for us:
1 var app = angular.module("MyApp", []);
2
3 app.config(function($routeProvider) {
4
$routeProvider.
5
when("/home", { templateUrl: "home.html" }).
6
when("/page", { templateUrl: "page.html" }).
7
otherwise({ redirectTo: "/home" });
8 });
The interesting part is the flash service, which handles a queue of messages and listens for route
The interesting part is the flash service, which handles a queue of messages and listens for route
changes to provide a message from the queue to the current page.
1 app.factory("flash", function($rootScope) {
2
var queue = [];
3
var currentMessage = "";
4
5
$rootScope.$on("$routeChangeSuccess", function() {
6
currentMessage = queue.shift() || "";
7
});
8
9
return {
10
setMessage: function(message) {
11
queue.push(message);
12
},
13
getMessage: function() {
14
return currentMessage;
15
}
16
};
17 });
The controller handles the form submit and navigates to the other page.
1 app.controller("MyCtrl", function($scope, $location, flash) {
2
$scope.flash = flash;
3
$scope.message = "Hello World";
4
5
$scope.submit = function(message) {
6
flash.setMessage(message);
7
$location.path("/page");
8
}
9 });
The flash service is dependency-injected into the controller and made available to the scope since
we want to use it in our template.
When you press the submit button you will be navigated to the other page and see the flash
message. Note that using the navigation to go back and forth between pages doesnt show the flash
message.
You can find the complete example on github (https://github.com/fdietz/recipes-with-angular-jsexamples/tree/master/chapter8/recipe5).
Discussion
The controller uses the setMessage function of the flash service and the service stores the
message in an array called queue . When the controller then uses $location service to navigate the
service routeChangeSuccess listener will be called and retrieves the message from the queue.
In the template we use ng-show to hide the div element with the flash messaging using
flash.getMessage()
Since this is a service it can be used anywhere in your code and it will show a flash message on the
next route change.
The directive is especially interesting since it uses ng-model instead of custom attributes.
1 app.directive("contenteditable", function() {
2
return {
3
restrict: "A",
4
require: "ngModel",
5
link: function(scope, element, attrs, ngModel) {
6
7
function read() {
8
ngModel.$setViewValue(element.html());
9
}
10
11
ngModel.$render = function() {
12
element.html(ngModel.$viewValue || "");
13
};
14
15
element.bind("blur keyup change", function() {
16
scope.$apply(read);
17
});
18
}
19
};
20 });
It requires the ngModel controller for data binding in conjunction with the link function. The
implementation binds an event listener, which executes the read function with apply
(http://docs.angularjs.org/api/ng.%24rootScope.Scope). This ensures that even though we call the
read function from within a DOM event handler we notify Angular about it.
The read function updates the model based on the views user input. And the $render function is
doing the same in the other direction, updating the view for us whenever the model changes.
The directive is surprisingly simple, leaving the ng-model aside. But without the ng-model support
we would have to come up with our own model-attribute handling which would not be consistent
with other directives.
Note that even though we dont specify it explicitly the modal dialog is hidden initially via the
modal attribute. The controller only handles the button click and the showModal value used by the
modal
attribute.
1
2
3
4
5
6
7
8
9
10
11
12
13
Do not forget to download and include the angular-ui.js file in a script tag. The module dependency
is defined directly to ui.bootstrap.modal. The full example (https://github.com/fdietz/recipeswith-angular-js-examples/tree/master/chapter8/recipe7) is available on Github including the
angular-ui module.
You can find the complete example on github (https://github.com/fdietz/recipes-with-angular-jsexamples/tree/master/chapter8/recipe7).
Discussion
The modal as defined in the template is straight from the Twitter bootstrap documentation
(http://twitter.github.com/bootstrap/javascript.html#modals). We can control the visibility with the
modal attribute. Additionally, the close attribute defines a close function which is called
whenever the dialog is closed. Note that this could happen if the user presses the escape key or
clicking outside the modal.
Our own cancel button uses the same function to close the modal manually, whereas the okay
button uses the ok function. This makes it easy for us to distinguish between a user who simply
cancelled the modal or actually pressed the okay button.
An Angular.js interceptor for all AJAX calls is used, which allows you to execute code before the
actual request is started and when it is finished.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Note that we use jQuery to show the spinner in the configuration step and hide the spinner in the
interceptor.
Additionally we use a controller to handle the button click and execute the search request.
Dont forget to add ngResource to the module and load it via script tag.
You can find the complete example on github (https://github.com/fdietz/recipes-with-angular-jsexamples/tree/master/chapter8/recipe8).
Discussion
The template is the easy part of this recipe since it renders a list of tweets using the ng-repeat
directive. Let us jump straight to the interceptor code.
The interceptor is implemented using the factory method and attaches itself to the promise function
of the AJAX response to hide the spinner on success or failure. Note that on failure we use the
reject function of the $q (http://docs.angularjs.org/api/ng.%24q) service, Angulars
promise/deferred implementation.
Now, in the config method we add our inceptor to the list of responseInterceptors of
to register it properly. In a similar manner we add the spinnerFunction to the default
transformRequest list in order to call it before each AJAX request.
$httpProvider
The controller is responsible for using a $resource object and handling the button click with the
load function. We are using JSONP here to allow this code to be executed locally even though it is
served by a different domain.
We can now fetch a list of contacts using Contact.index() and a single contact with
Contact.show(id) . These actions can be directly mapped to the ContactsController actions in your
Rails backend.
1 class ContactsController < ApplicationController
2
respond_to :json
3
4
def index
5
@contacts = Contact.all
6
respond_with @contacts
7
end
8
9
def show
10
@contact = Contact.find(params[:id])
11
respond_with @contact
12
end
13
14
...
15 end
The Rails action controller uses a Contact ActiveRecord model with the usual contact attributes like
firstname, lastname, age, etc. By specifying respond_to :json the controller only responds to the
JSON resource format and we can use respond_with to automatically transform the Contact model
to a JSON response.
The route definition uses the Rails default resource routing and an api scope to separate the API
requests from other requests.
1 Contacts::Application.routes.draw do
2
scope "api" do
3
resources :contacts
4
end
5 end
This will generate paths like for example api/contacts and api/contacts/:id for the HTTP GET
method.
You can find the complete example on github (https://github.com/fdietz/recipes-with-angular-jsexamples/tree/master/chapter9/recipe1).
Discussion
If you want to get up to speed with Ruby on Rails, I suggest that you look into the Rails Guides
(http://guides.rubyonrails.org/index.html) which will help you understand how all the pieces fit
together.
RAILS SECURITY USING AUTHENTICITY TOKEN
The example code above works nicely until we use the HTTP methods POST , PUT and DELETE with
the resource. As a security mechanism, Rails expects an authenticity token to prevent a CSRF (Cross
Site Request Forgery (http://guides.rubyonrails.org/security.html#cross-site-request-forgery-csrf))
attack. We need to submit an additional HTTP header X-CSRF-Token with the token. It is
conveniently rendered in the HTML meta tag csrf-token by Rails. Using jQuery we can fetch that
meta tag definition and configure the $httpProvider appropriately.
1 var app = angular.module("Contacts", ["ngResource"]);
2 app.config(function($httpProvider) {
3
$httpProvider.defaults.headers.common['X-CSRF-Token'] =
4
$('meta[name=csrf-token]').attr('content');
5 });
RAILS JSON RESPONSE FORMAT
If you are using a Rails version prior 3.1, youll notice that the JSON response will use a contact
namespace for the model attributes which breaks your Angular.js code. To disable this behavior you
can configure your Rails app accordingly.
1 ActiveRecord::Base.include_root_in_json = false
There are still inconsistencies between the Ruby and Javascript world. For example, in Ruby we use
underscored attribute names (display_name) whereas in Javascript we use camelCase
(displayName).
The actual layout template defines our ng-view directive and resides in
app/views/layouts/application.html - nothing new here. So lets skip ahead to the Angular route
definition in app.js.erb .
We set the $locationProvider to use the HTML5 mode and define our client-side routes for listing,
showing, editing and creating new contacts.
You can find the complete example on github (https://github.com/fdietz/recipes-with-angular-jsexamples/tree/master/chapter9/recipe1).
Discussion
Let us have a look into the route definition again. First of all the filename ends with erb , since it
uses ERB tags in the javascript file, courtesy of the Rails Asset Pipeline
(http://guides.rubyonrails.org/asset_pipeline.html). The asset_path method is used to retrieve the
URL to the HTML partials since it will change depending on the environment. On production the
filename contains an MD5 checksum and the actual ERB output will change from
/assets/contacts/index.html to /assets/contacts/index-7ce113b9081a20d93a4a86e1aacce05f.html .
If your Rails app is configured to use an asset host, the path will in fact be absolute.
It defines a validation on the age attribute. It must be an integer and less or equal to 50 years.
In the ContactsController we can use that to make sure the REST API returns proper error
messages. As an example let us look into the create action.
1 class ContactsController < ApplicationController
2
respond_to :json
3
4
def create
5
@contact = Contact.new(params[:contact])
6
if @contact.save
7
render json: @contact, status: :created, location: @contact
8
else
9
render json: @contact.errors, status: :unprocessable_entity
10
end
11
end
12
13 end
On success it will render the contact model using a JSON presentation and on failure it will return all
validation errors transformed to JSON. Let us have a look at an example JSON response:
1 { "age": ["must be less than or equal to 50"] }
It is a hash with an entry for each attribute with validation errors. The value is an array of Strings
since there might be multiple errors at the same time.
Let us move on to the client-side of our application. The Angular.js contact $resource calls the
create function and passes the failure callback function.
1 Contact.create($scope.contact, success, failure);
2
3 function failure(response) {
4
_.each(response.data, function(errors, key) {
5
_.each(errors, function(e) {
6
$scope.form[key].$dirty = true;
7
$scope.form[key].$setValidity(e, false);
8
});
9
});
10 }
Note that ActiveRecord attributes can have multiple validations defined. That is why the failure
function iterates through each validation entry and each error and uses $setValidity and $dirty
to mark the form fields as invalid.
Now we are ready to show some feedback to our users using the same approach discussed already
in the forms chapter.
1 <div class="control-group" ng-class="errorClass('age')">
2
<label class="control-label" for="age">Age</label>
3
<div class="controls">
4
<input ng-model="contact.age" type="text" name="age"
5
placeholder="Age" required>
6
<span class="help-block"
7
ng-show="form.age.$invalid && form.age.$dirty">
8
{{errorMessage('age')}}
9
</span>
10
</div>
11 </div>
The errorClass function adds the error CSS class if the form field is invalid and dirty. This will
render the label, input field and the help block with a red color.
1 $scope.errorClass = function(name) {
2
var s = $scope.form[name];
3
return s.$invalid && s.$dirty ? "error" : "";
4 };
The errorMessage will print a more detailed error message and is defined in the same controller.
1 $scope.errorMessage = function(name) {
2
result = [];
3
_.each($scope.form[name].$error, function(key, value) {
4
result.push(value);
5
});
6
return result.join(", ");
7 };
It iterates over each error message and creates a comma separated String out of it.
You can find the complete example on github (https://github.com/fdietz/recipes-with-angular-jsexamples/tree/master/chapter9/recipe1).
Discussion
Finally, the errorMessage handling is of course pretty primitive. A user would expect a localized
failure message instead of this technical presentation. The Rails Internationalization Guide
(http://guides.rubyonrails.org/i18n.html#translations-for-active-record-models) describes how to
translate validation error messages in Rails and might prove helpful to further use that in your
client-side code.
In this chapter we will have a look into solving common problems when combining Angular.js with
the Node.js Express (http://expressjs.com/) framework. The examples used in this chapter are
based on a Contacts app to manage a list of contacts. As an extra we use MongoDB as a backend for
our contacts since it requires further customization to make it work in conjunction with Angulars
$resource service.
We can now fetch a list of contacts using Contact.index() and a single contact with
Contact.show(id)
1
2
3
4
5
6
7
8
9
10
. These actions can be directly mapped to the API routes defined in app.js .
I like to keep routes in a separate file routes/api.js and just reference them in app.js in order to
keep it small. The API implementation first initializes the Mongoose (http://mongoosejs.com/)
library and defines a schema for our Contact model.
1
2
3
4
5
6
7
We can now use the Contact model to implement the API. Lets start with the index action:
1 exports.contacts = function(req, res) {
2
Contact.find({}, function(err, obj) {
3
res.json(obj)
4
});
5 };
Skipping the error handling we retrieve all contacts with the find function provided by Mongoose
and render the result in the JSON format. The show action is pretty similar except it uses findOne
and the id from the URL parameter to retrieve a single contact.
1 exports.contact = function(req, res) {
2
Contact.findOne({ _id: req.params.id }, function(err, obj) {
3
res.json(obj);
4
});
5 };
As a final example we will create a new Contact instance passing in the request body and call the
save method to persist it:
1 exports.createContact = function(req, res) {
2
var contact = new Contact(req.body);
3
contact.save();
4
res.json(req.body);
5 };
Problem
You wish to use client-side routing in conjunction with an Express backend.
Solution
Every request to the backend should initially render the complete layout in order to load our Angular
app. Only then will the client-side rendering take over. Let us first have a look at the route definition
for this catch all route in our app.js .
1 var express = require('express'),
2
routes = require('./routes');
3
4 app.get('/', routes.index);
5 app.get('*', routes.index);
It uses the wildcard character to catch all requests in order to get processed with the routes.index
module. Additionally, it defines the route to use the same module. The module again resides in
routes/index.js .
1 exports.index = function(req, res){
2
res.render('layout');
3 };
The implementation only renders the layout template. It uses the Jade (http://jade-lang.com/)
template engine.
1 !!!
2 html(ng-app="myApp")
3
head
4
meta(charset='utf8')
5
title Angular Express Seed App
6
link(rel='stylesheet', href='/css/bootstrap.css')
7
body
8
div
9
ng-view
10
11
script(src='js/lib/angular/angular.js')
12
script(src='js/lib/angular/angular-resource.js')
13
script(src='js/app.js')
14
script(src='js/services.js')
15
script(src='js/controllers.js')
Now that we can actually render the initial layout we can get started with the client-side routing
definition in app.js
We define route definitions to list, show and edit contacts and use a set of partials and
corresponding controllers. In order for the partials to get loaded correctly we need to add another
express route in the backend which serves all these partials.
1 app.get('/partials/:name', function (req, res) {
2
var name = req.params.name;
3
res.render('partials/' + name);
4 });
It uses the name of the partial as an URL param and renders the partial with the given name from
the partial directory. Keep in mind that you must define that route before the catch all route,
otherwise it will not work.
You can find the complete example on github (https://github.com/fdietz/recipes-with-angular-jsexamples/tree/master/chapter10/recipe1).
Discussion
Compared to Rails the handling of partials is quite explicit by defining a route for partials. On the
other hand it is quite nice to being able to use jade templates for our partials too.
Buzz (/buzz) | Blog (http://blog.leanpub.com) | Manifesto (/manifesto) | Affiliate Program (/affiliates) | Terms of Service (/terms) | Privacy Policy
(/privacy) | Copyright Take Down Policy (/takedown) | About (/team) | Contact Us (/contact)
Leanpub is copyright 2010-2014 Ruboss Technology Corporation. All rights reserved.