$scope.bill.totalCart = total;
$scope.bill.discount = total > 100 ? 10 : 0;
$scope.bill.subtotal = total - $scope.bill.discount;
});
Watching multiple things
What if you want to watch multiple properties or objects and execute a function when‐
ever any of them change? You’d have two basic options:
• Put them into an array or object and pass in deepWatch as true.
• Watch a concatenated value of the properties.
In the first case, if you’ve got an object with two properties a and b in your scope, and want to execute the callMe() function on change, you could watch both of them, like so:
$scope.$watch('things.a + things.b', callMe(...));
Of course, a and b could be on different objects, and you could make the list as long as you like. If the list is long, you would likely write a function that returns the concatenated value rather than relying on an expression for the logic.
In the second case, you might want to watch all the properties on the things object. In this case, you could do this:
$scope.$watch('things', callMe(...), true);
Here, passing in true as the third parameter asks Angular to walk the properties of things and call callMe() on a change to any of them. This works equally well on an array as it does here on an object.
Organizing Dependencies with Modules
In any non-trivial application, figuring out how to organize the functionality of your code into areas of responsibility is often a hard task. We’ve seen how controllers give us a place to put the code that exposes the right data and functions to the view template.
But what about the rest of the code we need to support our applications? The most obvious place to put this would be in functions on the controllers.
This works fine for small apps and the examples that we’ve seen so far, but it quickly becomes unmanageable in real apps. The controllers would become a dumping ground for everything and anything we need to do. They’d be hard to understand and likely hard to change.
Organizing Dependencies with Modules | 33
Enter modules. They provide a way to group dependencies for a functional area within your application, and a mechanism to automatically resolve dependencies (also known as dependency injection). Generically, we call these dependencies services, as they pro‐
vide specific services to our application.
For example, if in our shopping website a controller needs to get a list of items for sale from the server, we’d want some object—let’s call it Items—to take care of getting the items from the server. The Items object, in turn, needs some way to communicate with the database on the server over XHR or WebSockets.
Doing this without modules looks something like this:
function ItemsViewController($scope) { // make request to server
…
// parse response into Item objects …
// set Items array on $scope so the view can display it ...
}
While this would certainly work, it has a number of potential problems.
• If some other controller also needs to get Items from the server, we now have to replicate this code. This makes maintenance a burden, as now if we make schema or other changes, we have to update that code in several places.
• With other factors like server authentication, parsing complexity, and so on, it is difficult to reason about the boundaries of responsibility for this controller object, and reading the code is harder.
• To unit test this bit of code, we’d either need to actually have a server running, or monkey patch XMLHttpRequest to return mock data. Having to run the server will make tests very slow, it’s a pain to set up, and it usually introduces flakiness into tests. The monkey patching route solves the speed and flakiness problems, but it means you have to remember to un-patch any patched objects between tests, and it brings additional complexity and brittleness by forcing you to specify the exact on-the-wire format for your data (and in having to update the tests whenever this format changes).
With modules, and the dependency injection we get from them, we can write our con‐
troller much more simply, like this:
function ShoppingController($scope, Items) { $scope.items = Items.query();
}
You’re probably now asking yourself, “Sure, that looks cool, but where does Items come from?” The preceding code assumes that we’ve defined Items as a service.
Services are singleton (single-instance) objects that carry out the tasks necessary to support your application’s functionality. Angular comes with many services like $loca tion, for interacting with the browser’s location, $route, for switching views based on location (URL) changes, and $http, for communicating with servers.
You can, and should, create your own services to do all of the tasks unique to your application. Services can be shared across any controllers that need them. As such, they’re a good mechanism to use when you need to communicate across controllers and share state. Angular’s bundled services start with a $, so while you can name them anything you like, its a good idea to avoid starting them with $ to avoid naming colli‐
sions.
You define services with the module object’s API. There are three functions for creating generic services, with different levels of complexity and ability:
Function Defines
provider(name, Object
OR constructor() ) A configurable service with complex creation logic. If you pass an Object, it should have a function named $get that returns an instance of the service. Otherwise, Angular assumes you’ve passed a constructor that, when called, creates the instance.
factory(name, $get
Function() ) A non-configurable service with complex creation logic. You specify a function that, when called, returns the service instance. You could think of this as provider(name, { $get:
$getFunction() } ). service(name, con
structor() ) A non-configurable service with simple creation logic. Like the constructor option with provider, Angular calls it to create the service instance.
We’ll look at the configuration option for provider() later, but let’s discuss an example with factory() for our preceding Items example. We can write the service like this:
// Create a module to support our shopping views
var shoppingModule = angular.module('ShoppingModule', []);
// Set up the service factory to create our Items interface to the // server-side database
shoppingModule.factory('Items', function() { var items = {};
items.query = function() {
// In real apps, we'd pull this data from the server...
return [
{title: 'Paint pots', description: 'Pots full of paint', price: 3.95}, {title: 'Polka dots', description: 'Dots with polka, price: 2.95}, {title: 'Pebbles', description: 'Just little rocks', price: 6.95}
];
};
return items;
});
Organizing Dependencies with Modules | 35
When Angular creates the ShoppingController, it will pass in $scope and the new Items service that we’ve just defined. This is done by parameter name matching. That is, Angular looks at the function signature for our ShoppingController class, and notices that it is asking for an Items object. Since we’ve defined Items as a service, it knows where to get it.
The result of looking up these dependencies as strings means that the arguments of injectable functions like controller constructors are order-independent. So instead of this:
function ShoppingController($scope, Items) {...}
we can write this:
function ShoppingController(Items, $scope) {...}
and it all still functions as we intended.
To get this to work with our template, we need to tell the ng-app directive the name of our module, like the following:
<html ng-app='ShoppingModule'>
To complete the example, we could implement the rest of the template as:
<body ng-controller="ShoppingController">
<h1>Shop!</h1>
<table>
<td>{{item.title}}</td>
<td>{{item.description}}</td>
<td>{{item.price | currency}}</td>
</tr>
</table>
</div>
with a resulting app that looks like Figure 2-2.
Figure 2-2. Shop items