AngualrJS Fundamentals
AngualrJS Fundamentals
1 Welcome 3
4 Preventing flickering 15
12 Pushy Angular 55
1
2 CONTENTS
23 Conclusion 119
Chapter 1
Welcome
3
4 CHAPTER 1. WELCOME
Chapter 2
One of the most misunderstood components of Angular that beginners often ask
is about the differences between the service(), factory(), and provide() methods.
This is where we’ll start the twenty-five days of Angular calendar.
The Service
In Angular, services are singleton objects that are created when necessary and
are never cleaned up until the end of the application life-cycle (when the browser
is closed). Controllers are destroyed and cleaned up when they are no longer
needed.
Let’s dive into creating service. Every method that we’ll look at has the same
method signature as it takes two arguments
Each one also creates the same underlying object type. After they are instanti-
ated, they all create a service and there is no functional difference between the
object types.
5
6 CHAPTER 2. THE SHORT GUIDE TO SERVICE DEFINITIONS
factory()
Arguably the easiest way to create a service is by using the factory() method.
The factory() method allows us to define a service by returning an object that
contains service functions and service data. The service definition function is
where we place our injectable services, such as $http and $q.
angular.module('myApp.services')
.factory('User', function($http) { // injectables go here
var backendUrl = "http://localhost:3000";
var service = {
// our factory definition
user: {},
setName: function(newName) {
service.user['name'] = newName;
},
setEmail: function(newEmail) {
service.user['email'] = newEmail;
},
save: function() {
return $http.post(backendUrl + '/users', {
user: service.user
});
}
};
return service;
});
It’s easy to use the factory in our application as we can simply inject it where
we need it at run-time.
angular.module('myApp')
.controller('MainController', function($scope, User) {
$scope.saveUser = User.save;
});
service()
The service() method, on the other hand allows us to create a service by defining
a constructor function. We can use a prototypical object to define our service,
instead of a raw javascript object.
Similar to the factory() method, we’ll also set the injectables in the function
definition:
angular.module('myApp.services')
.service('User', function($http) { // injectables go here
var self = this; // Save reference
this.user = {};
this.backendUrl = "http://localhost:3000";
this.setName = function(newName) {
self.user['name'] = newName;
}
this.setEmail = function(newEmail) {
self.user['email'] = newEmail;
}
this.save = function() {
return $http.post(self.backendUrl + '/users', {
user: self.user
})
}
});
Functionally equivalent to using the factory() method, the service() method will
hold on to the object created by the constructor object.
It’s easy to use the service in our application as we can simply inject it where
we need it at run-time.
angular.module('myApp')
.controller('MainController', function($scope, User) {
$scope.saveUser = User.save;
});
8 CHAPTER 2. THE SHORT GUIDE TO SERVICE DEFINITIONS
provide()
The lowest level way to create a service is by using the provide() method. This
is the only way to create a service that we can configure using the .config()
function.
Unlike the previous to methods, we’ll set the injectables in a defined this.$get()
function definition.
angular.module('myApp.services')
.provider('User', function() {
this.backendUrl = "http://localhost:3000";
this.setBackendUrl = function(newUrl) {
if (url) this.backendUrl = newUrl;
}
this.$get = function($http) { // injectables go here
var self = this;
var service = {
user: {},
setName: function(newName) {
service.user['name'] = newName;
},
setEmail: function(newEmail) {
service.user['email'] = newEmail;
},
save: function() {
return $http.post(self.backendUrl + '/users', {
user: service.user
})
}
};
return service;
}
});
9
In order to configure our service, we can inject the provider into our .config()
function.
angular.module('myApp')
.config(function(UserProvider) {
UserProvider.setBackendUrl("http://myApiBackend.com/api");
})
We can use the service in our app just like any other service now:
angular.module('myApp')
.controller('MainController', function($scope, User) {
$scope.saveUser = User.save;
});
Almost all non-trivial, non-demo Single Page App (SPA) require multiple pages.
A settings page is different from a dashboard view. The login page is different
from an accounts page.
We can get this functionality with Angular incredibly simply and elegantly using
the angular.route module.
Installation
In order to actually use routes, we’ll need to install the routing module. This is
very easy and can be accomplished in a few ways:
Using bower
Usage
It’s easy to define routes. In our main module, we’ll need to inject the ngRoute
module as a dependency of our app.
11
12 CHAPTER 3. A SHORT GUIDE TO ROUTING
angular.module('myApp', ['ngRoute'])
.config(function($routeProvider) {});
Now, we can define the routes of our application. The route module adds a
$routeProvider that we can inject into our .config() function. It presents us two
methods that we can use to define our routes.
when()
The when() method defines our routes. It takes two arguments, the string of
the route that we want to match and a route definition object. The route string
will match on the url in the browser. The main route of the app is usually the
/ route.
The route definition can also accept symbols that will be substituted by angular
and inserted into a service called the $routeParams that we can access from our
routes.
angular.module('myApp', ['ngRoute'])
.config(function($routeProvider) {
$routeProvider
.when('/', {
templateUrl: 'views/main.html',
controller: 'MainController'
})
.when('/day/:id', {
templateUrl: 'views/day.html',
controller: 'DayController'
})
The route definition object is where we’ll define all of our routes from a high-
level. This is where we’ll assign a controller to manages the section in the DOM,
the templates that we can use, and other route-specific functionality.
Most often, we’ll set these routes with a controller and a templateUrl to define
the functionality of the route.
otherwise()
The otherwise() method defines the route that our application will use if a route
is not found. It’s the default route.
For example, the route definition for this calendar is:
13
angular.module('myApp', ['ngRoute'])
.config(function($routeProvider) {
$routeProvider
.when('/', {
templateUrl: 'views/main.html',
controller: 'MainController'
})
.when('/day/:id', {
templateUrl: 'views/day.html',
controller: 'DayController'
})
.otherwise({
redirectTo: '/'
});
})
Okay, so we’ve defined routes for our application, now how do we actually use
them in our app?
As a route is simply a new view with new functionality for a portion of a page,
we’ll need to tell angular which portion of the page we want to switch. To do
this, we’ll use the ng-view directive:
Now, anytime that we switch a route, only the DOM element (<div
ng-view></div>) will be updated and the header/footer will stay the same.
14 CHAPTER 3. A SHORT GUIDE TO ROUTING
Chapter 4
Preventing flickering
ng-cloak
The ng-cloak directive is a built-in angular directive that hides all of the ele-
ments on the page that contain the directive.
<div ng-cloak>
<h1>Hello {{ name }}</h1>
</div>
After the browser is done loading and the compile phase of the template is
rendering, angular will delete the ngCloak element attribute and the element
will become visible.
The safest way to make this IE7 safe is to also add a class of ng-cloak:
15
16 CHAPTER 4. PREVENTING FLICKERING
ng-bind
The ng-bind directive is another built-in Angular directive that handles data-
binding in the view. We can use ng-bind instead of using the {{ }} form to
bind elements to the page.
Using ng-bind instead of {{ }} will prevent the unrendered {{ }} from showing
up instead of empty elements being rendered.
The example from above can be rewritten to the following which will prevent
the page from flickering with {{ }}:
<div>
<h1>Hello <span ng-bind="name"></span></h1>
</div>
resolve
When we’re using routes with different pages, we have another option to prevent
pages from rendering until some data has been loaded into the route.
We can specify data that we need loaded before the route is done loading by
using the resolve key in our route options object.
When the data loads successfully, then the route is changed and the view will
show up. If it’s not loaded successfully, then the route will not change and the
$routeChangeError event will get fired.
angular.module('myApp', ['ngRoute'])
.config(function($routeProvider) {
$routeProvider
.when('/account', {
controller: 'AccountController',
templateUrl: 'views/account.html',
resolve: {
// We specify a promise to be resolved
account: function($q) {
var d = $q.defer();
$timeout(function() {
d.resolve({
id: 1,
name: 'Ari Lerner'
})
}, 1000);
return d.promise;
}
17
}
})
});
The resolve option takes an object, by key/value where the key is the name
of the resolve dependency and the value is either a string (as a service) or a
function whose return value is taken as the dependency.
When the route loads, the keys from the resolve parameter are accessible as
injectable dependencies:
angular.module('myApp')
.controller('AccountController',
function($scope, account) {
$scope.account = account;
});
We can use the resolve key to pass back results from $http methods as well,
as $http returns promises from it’s method calls:
angular.module('myApp', ['ngRoute'])
.config(function($routeProvider) {
$routeProvider
.when('/account', {
controller: 'AccountController',
templateUrl: 'views/account.html',
resolve: {
account: function($http) {
return $http.get('http://example.com/account.json')
}
}
})
});
The recommended usage of the resolve key is to define a service and let the
service respond with the required data (plus, this makes testing much easier).
To handle this, all we’ll need to do is build the service.
First, the accountService:
18 CHAPTER 4. PREVENTING FLICKERING
angular.module('app')
.factory('accountService', function($http, $q) {
return {
getAccount: function() {
var d = $q.defer();
$http.get('/account')
.then(function(response) {
d.resolve(response.data)
}, function err(reason) {
d.reject(reason);
});
return d.promise;
}
}
})
We can use this service method to replace the $http call from above:
angular.module('myApp', ['ngRoute'])
.config(function($routeProvider) {
$routeProvider
.when('/account', {
controller: 'AccountController',
templateUrl: 'views/account.html',
resolve: {
// We specify a promise to be resolved
account: function(accountService) {
return accountService.getAccount()
}
}
})
});
Chapter 5
Switching content based upon state is trivial inside of Angular, but how about
styles? For instance, let’s say that we have a list of elements, each with a
checkbox to indicate to our users that we’re selecting an element to run some
action on the element or to indicate that a date in a calendar is today (like on
the 25 days of Angular calendar front-page).
Angular has a number of built-in ways that we can apply custom styling to
DOM elements based upon different data states inside of our $scope objects.
ng-style
The ng-style directive is a built-in directive that allows us to set styles directly
on a DOM element based upon angular expressions.
<div ng-controller='DemoController'>
<h1 ng-style="{color:'blue'}">
Hello {{ name }}!
</h1>
</div>
19
20 CHAPTER 5. CONDITIONALLY APPLYING CSS CLASSES
For example, we can use a dropdown to set the specific style on the element and
update a model with the selected color on the dropdown:
<div ng-controller='DemoController'>
<select ng-model="selectedColor"
ng-options="color for color in allColors">
</select>
<h1 ng-style="{color:selectedColor}">
Hello {{ name }}!
</h1>
</div>
angular.module('adventApp')
.controller('DemoController', function($scope) {
$scope.allColors = ['blue', 'red', '#abc', '#bedded'];
$scope.selectedColor = 'blue';
});
When we change the selectedColor, the ng-style directive updates and our
HTML changes.
ng-class
<div ng-controller='DemoController'>
<button ng-click="shouldHighlight=!shouldHighlight">
Highlight text
</button>
<h1 ng-class="{highlight:shouldHighlight}">
Hello {{ name }}!
</h1>
</div>
<div ng-controller='DemoController'>
<button ng-click="shouldHighlight=!shouldHighlight">
Highlight text
</button>
<button ng-click="emphasize=!emphasize">
Emphasize
</button>
<h1 ng-class="{
important:emphasize,
highlight:shouldHighlight
}">
Hello {{ name }}!
</h1>
</div>
custom directive
Another solution we can use to create custom styles for our elements is by setting
up a directive to generate a custom CSS stylesheet for us.
angular.module('myApp')
.directive('myStylesheet', function() {
return {
restrict: 'A',
require: 'ngModel',
scope: { ngModel: '=', className: '@' },
template: "<style " +
"type='text/stylesheet'>" +
".{{ className }} {" +
" font-size: {{ ngModel.fontsize }};" +
" color: {{ ngModel.color }};" +
"}" +
"</style>"
}
});
This directive will add a <style> tag to our page and allow us to pass in a
single ng-model that can contain our custom styles.
22 CHAPTER 5. CONDITIONALLY APPLYING CSS CLASSES
We can use this directive simply by attaching our data model that contains our
DOM element’s style on the tag.
<div ng-controller='DemoController'>
<div my-stylesheet
ng-model="pageObject"
class-name="customPage" >
</div>
<div class="customPage">
<h1>Hello</h1>
</div>
</div>
Chapter 6
Like with any modern software development lifecycle that’s destined for produc-
tion, we’re likely to have multiple deployment destinations. We might have a
development server, a staging server, a production server, etc.
For each of these different scenarios, we’re probably using different versions of
the app, with the latest, bleeding edge version on development and staging and
the stable version in production.
Each of these likely is talking to different back-end services, is using different
individual settings, such as which back-end url we’re talking to, if we’re in
testing mode or not, etc.
Here are a few of the multiple methods for how we can handle separating out
our environments based upon deployment type. But first…
Configuration in Angular
The easiest way to use configuration inside of an Angular app is to set a constant
with an object. For example:
angular.module('myApp.config')
.constant('myConfig', {
'backend': 'http://localhost:3000/api',
'version': 0.2
})
Our Angular objects now have access to this myConfig as a service and can
inject it just like any other Angular service to get access to the configuration
variables:
23
24 CHAPTER 6. SETTING CONFIGS IN ANGULAR
angular.module('myApp', ['myApp.config'])
.factory('AccountSrv',
function($http, myConfig) {
return {
getAccount: function() {
return $http({
method: 'GET',
url: myConfig.backend + '/account'
})
// ...
}
}
});
In this example, we separate out the main app module from the one
that contains the configuration and then later set it as a dependency
of our main app. We’ll follow this pattern for all examples in this
snippet.
We’ll use this same pattern in the few different methods to get config data in
our app.
Server-side rendering
CONFIG = {
development: {
backend: "http://localhost:3000"
},
production: {
backend: "http://my.apibackend.com"
}
}
angular.module('myApp', ['myApp.config'])
.service('AccountService',
function($http, myConfig) {
this.getAccount = function() {
return $http({
method: 'GET',
url: myConfig.backend+'/account.json'
});
}
});
Client-side
If we are not using a back-end to serve our app and cannot depend upon html
interpolation to render us back a configuration, we must depend upon configu-
ration at compile-time.
As we can see above, we can manually set our configuration files if we’re not
depending upon any complex configuration or deploying only to one or two
environments.
We will generate a file for each environment that contains the relevant configu-
ration for each environment. For instance, the development might look like:
angular.module('myApp.development', [])
.constant('myConfig', {
backend: 'http://dev.myapp.com:3000'
})
angular.module('myApp.production', [])
.constant('myConfig', {
26 CHAPTER 6. SETTING CONFIGS IN ANGULAR
backend: 'http://myapp.com'
})
Now, inside of our app, we’ll only need to switch a single line to define which
configuration it should use in production. As we said before, we’ll need to add
one of these to our dependencies of our app module:
angular.module('myApp', ['myApp.production'])
// ...
The tricky part of this is that we’ll need to manually change the configuration
file for each build. Although it’s not a difficult task for a single developer, this
can become a giant hassle for larger teams.
We can use our build tools instead of doing this configuration switch manually.
Using Grunt, we can use the grunt-template task. This task allows us to
specify variables once and output a template of our choosing.
If you’re using yeoman to build your angular apps (and you should
be), this should look very familiar.
Installation
Since Gruntfiles are simply JavaScript, we can write our configuration in .json
files in our path and load them from our Gruntfile. Since we’re interested
in using different environments, we’ll set up our configuration files in a config
directory.
For instance, the development config file in config/development.json:
{
backend: 'http://dev.myapi.com:3000'
}
{
backend: 'http://myapp.com'
}
To load the configuration for the current environment, we’ll simply use a bit
of JavaScript in our Gruntfile to take care of this process. At the top of the
Gruntfile we’ll load the following to load the right config into a variable we’ll
call config:
This will set the configuration to load the object from config/development.json
by default. We can pass the environment variable ENV to set it to production:
Now all we need to do is compile our template. To do this, we’ll use the
grunt-template task:
grunt.initConfig({
// ...
'template': {
'config': {
'options': {
data: config
}
},
'files': {
// The .tmp destination is a yeoman
// default location. Set this dest
// to fit your own needs if not using
// yeoman
'.tmp/scripts/config.js':
['src/config.js.tpl']
}
28 CHAPTER 6. SETTING CONFIGS IN ANGULAR
}
// ...
});
grunt.loadNpmTasks('grunt-template');
grunt.registerTask('default', ['template']);
We’ll need to write our src/config.js template. This is the easy part as we’ll
simply generate the file we manually created above. In our src/config.js.tpl,
add the following:
angular.module('myApp.config', [])
.constant('myConfig', function() {
backend: '<%= backend %>'
});
With this automated process, we can eliminate the manual process of building
our configuration entirely.
Chapter 7
• primitives
• data-structures
• expressions
In this article, we are walking through an example where we are going to move
from simple data-binding to a fairly complex and interesting expression binding
example.
29
30 CHAPTER 7. THE POWER OF EXPRESSION BINDING
Simple Bindings
angular.module('myApp')
.controller('StepOneController', ['$scope', function ($scope) {
// Example categories
$scope.categories = [
{name: 'one', display: 'Category One'},
{name: 'two', display: 'Category Two'},
{name: 'three', display: 'Category Three'},
{name: 'four', display: 'Category Four'}
];
$scope.currentCategory = null;
In the view, we are looping over the categories array with ng-repeat and
create a category-item div for each object in the categories array.
Within the div we are binding the display property on the category object.
When our user clicks on the category-item div, the ng-click directive calls
setCurrentCategory() with the category object along with it.
In the h4 tag below, we’re simply binding the currentCategory.display data
above as we would in simply data-binding.
31
Albeit simple, the above example is impressive in its own right with how much
we can accomplish in a little amount of code with data-binding.
Now that the foundation has been laid and now we are going to extend it to
allow us to bind to an expression with Angular.
In the demo code, we have create second controller called StepTwoController
that is identical to StepOneController with an additional method
isCurrentCategory() that returns a boolean if the current category matches
the category argument.
angular.module('myApp')
.controller('StepTwoController', function ($scope) {
$scope.categories = [
{name: 'one', display: 'Category One'},
{name: 'two', display: 'Category Two'},
{name: 'three', display: 'Category Three'},
{name: 'four', display: 'Category Four'}
];
$scope.currentCategory = null;
ng-repeat="category in categories"
ng-click="setCurrentCategory(category)"
ng-class="{'current-active':isCurrentCategory(category)}">
<strong>{{category.display}}</strong>
</div>
</div>
In our CSS, the current-active class applies a 10 pixel border to the element
it is applied to. Practically speaking, when a user clicks a category, there will
be a grey border around it indicating that it is the active element.
.current-active {
border: 10px solid #666;
}
We have made just a few small additions to our code to dynamically update the
UI based on the value of a simple expression.
The final example where we get into some really powerful stuff using expressions
within expressions.
The first thing we need to do to make this work is to create a class that corre-
sponds to each category object we have in our categories array.
The idea is that .current-one applies to the category that has the name one and
.current-two corresponds to the category that has the name two, etc.
.current-one {
border: 10px solid #457b97;
}
.current-two {
border: 10px solid #727372;
}
.current-three {
border: 10px solid #a66a48;
}
33
.current-four {
border: 10px solid #f60;
}
The only difference between these four classes is the color of the
border defined in the above CSS.
<div ng-controller='StepThreeController'>
<div
class="col-xs-3 category-item btn btn-primary"
ng-repeat="category in categories"
ng-click="setCurrentCategory(category)"
ng-class="
{'current-{{category.name}}':
isCurrentCategory(category)
}">
<strong>{{category.display}}</strong>
</div>
<div class="clear"></div>
</div>
This works is that Angular knows to evaluate the bindings before evaluating the
ng-class expression.
Binding to expressions is an incredibly powerful technique that allows you to
accomplish some pretty impressive things when done responsibly.
The code for this article is available here
34 CHAPTER 7. THE POWER OF EXPRESSION BINDING
Chapter 8
It’s incredibly easy for us to build prototypes of angular apps, but what about
when we head to production?
Will our app hold up? What about when we start dealing with large amounts
of data? How can we make sure our application is still performant?
In this part 1 of optimizing angular, we’ll walk through a few ways to optimize
our view and why they are important.
Filters are incredibly powerful ways to sort and manage our way through data
in the view. They can help us format data, live-search data, etc.
<table ng-controller="FilterController">
<thead>
<tr>
<th>Filter</th>
<th>Input</th>
<th>Output</th>
</tr>
</thead>
<tbody>
<tr>
<td>uppercase</td>
<td ng-non-bindable>{{ msg | uppercase }}</td>
<td>{{ msg | uppercase }}</td>
35
36 CHAPTER 8. OPTIMIZING ANGULAR: THE VIEW (PART 1)
</tr>
<tr>
<td>currency</td>
<td ng-non-bindable>{{ 123.45 | currency }}</td>
<td>{{ 123.45 | currency }}</td>
</tr>
<tr>
<td>date</td>
<td ng-non-bindable>{{ today | date:'longDate' }}</td>
<td>{{ today | date:'longDate' }}</td>
</tr>
<tr>
<td>custom pig-latin filter</td>
<td ng-non-bindable>{{ msg | piglatin }}</td>
<td>{{ msg | piglatin }}</td>
</tr>
</tbody>
</table>
angular.module('myApp')
.filter('piglatin', function() {
return function(text) {
text = text || '';
text = text
.replace(/\b([aeiou][a-z]*)\b/gi, "$1way");
text = text
.replace(/\b([bcdfghjklmnpqrstvwxy]+)([a-z]*)\b/gi, "$2$1ay");
return text;
}
});
(jsbin example)
Although this is really incredibly useful to be able to do when we’re prototyping,
it’s a cause for a lot of slow-down in the view. Every time that there is a $digest
event, these filters will run for every value. That’s exponentially worse when
we’re using ng-repeat and setting a filter on those values.
How do we get away with moving this functionality out of the view so it runs
once, rather than every single time? Use the controller!
We can change the view above to hand us the filtered value so we don’t need to
do any parsing in the view ourselves.
37
To get access to a filter inside of a controller, we’ll need to inject it into the
controller. For instance:
angular.module('myApp')
.controller('HomeController', function($scope, $filter) {
// We now have access to the $filter service
});
With the $filter service, we can fetch the filter we’re interested in and apply
it to our data.
The date example from above is a good candidate to provide a formatted date
in the controller. We might hand back a formatted_date instead of using the
raw date in the view:
angular.module('myApp')
.controller('HomeController', function($scope, $filter) {
$scope.today = new Date();
var dateFilter = $filter('date');
$scope.formatted_today = dateFilter($scope.today, 'mediumDate');
})
Instead of showing the today scope property in our view, we can simply show
the formatted_today value and have the filter run in the background.
38 CHAPTER 8. OPTIMIZING ANGULAR: THE VIEW (PART 1)
Chapter 9
In this recipe, we’re going to continue optimizing the view with Angular.
It’s relatively well-known that the Angular event system can handle approxi-
mately 2000 watches on any given page for a desktop browser. Although this
number has been somewhat arbitrarily cited by the Angular team and others,
it holds true with our experience as well.
What’s a watch
Before we can talk about how to optimize the numbers of watches in the view,
it’s a good idea to define what we mean when we say watch.
Angular sets up these constructs called watches which monitor the changes to
interesting data in an angular application. This interesting data is specified
by our app by showing elements on-screen using directives or the $scope from
inside of a controller using the {{ }} template tags.
The following creates two watches, one for the input and one for the output:
<div>
Enter your name: <input type="text" ng-model="name" />
<h3>Hello {{ name }}</h3>
</div>
For instance, anytime that we set up a list that uses ng-repeat with a bunch
of components that may or may not change. If we set up a binding between the
front-end and the backend, we create at minimum 1 watch. If we loop over a
39
40 CHAPTER 9. OPTIMIZING ANGULAR: THE VIEW (PART 2)
list while we create that, that’s at least n watches. When we start setting up
multiple bindings for each iteration, that’s n*n watches.
There are a few strategies that we can take to limit the number of watches on
a page. For this snippet, we’ll talk about the bindonce library.
Whenever we’re using static information, information that we don’t expect to
change, it’s inefficient to keep a watcher around to watch data that will never
change.
For instance, when we get a list of our user’s Facebook friends we don’t expect
their names to change. We’ll look at this data as static information.
Rather than creating a watch for every one of our facebook friend’s names, we
can tell Angular to only create a single watch to watch the list of friends to
repeat over using the bindonce library.
This code creates a 3 watchers for every friend of ours. In a list of 20 friends,
that’s 60 watchers in this code alone:
<ul>
<li ng-repeat="friend in friends">
<a ng-href="friend.link">
<h1 ng-bind="friend.name">
<h4 ng-bind="friend.birthday">
</h4>
</a>
</li>
</ul>
Installation
angular.module('app', [
'pasvaz.bindonce'
])
41
Using bindonce, we can tell angular to watch data while it’s unresolved and as
soon as it resolves we can set it to remove the watcher. The following code only
resolves 1 watcher for the entire loop:
<ul>
<li bindonce
ng-repeat="friend in friends">
<a bo-href="friend.link">
<h1 bo-text="friend.name">
<h4 bo-text="friend.birthday">
</h4>
</a>
</li>
</ul>
Another approach to optimize the watches on our view is to keep them as simple
as possible. The $digest cycle will run watches at a minimum of 2 times per
cycle, so the simpler the $watch function is, the faster it will run.
For deeper look into other optimizations, check out the optimization chapter in
ng-book.com.
42 CHAPTER 9. OPTIMIZING ANGULAR: THE VIEW (PART 2)
Chapter 10
Webapps are only as interesting as the functionality and data that they provide
us. Other than isolated apps such as calculators and solitaire games, data powers
most functionality.
In this snippet, we’re going to look at the Angular Way of connecting to data
sources. Specifically, we’re going to work with the flickr API to get public photos.
Let’s get started!
angular.module('myApp')
.controller('HomeController', function($scope, $http) {
// We now have access to the $http object
});
We’re going to use the $http object to fetch photos from the public flickr API:
43
44 CHAPTER 10. GETTING CONNECTED TO DATA
</a>
</div>
We are fetching this photo from the flickr API using the $http service in our
controller using:
angular.module('myApp')
.controller('FlickrApiController', function($scope, $http) {
$http({
method: 'JSONP',
url: 'http://api.flickr.com/services/feeds/photos_public.gne',
params: {
'format': 'json',
'jsoncallback': 'JSON_CALLBACK'
}
}).success(function(photos) {
$scope.photo = photos.items[0];
})
})
Now, although this works, it’s not the Angular Way of fetching photos. Using
the $http service in our controller makes it incredibly difficult to test controllers
and our functional logic for fetching from our external services.
Rather than using our controllers to get our photo, we’ll create a service that
will house our flickr interactions.
A service is a singleton object that we can both bundle our common services
together as well as use to share data around our application. In this case, we’ll
create a Flickr service that bundles together our flickr functionality.
As the Flickr API requires an API key for any non-public requests, we’ll likely
want to create a provider as we’ll want to configure our api key on a per-app
basis.
angular.module('adventApp')
.provider('Flickr', function() {
var base = 'http://api.flickr.com/services',
api_key = '';
// Service interface
this.$get = function($http) {
var service = {
// Define our service API here
};
return service;
}
})
This gives us the ability to set our api_key from Flickr in our .config() func-
tion on our app so we can use it later in our calls to the flickr api:
angular.module('myApp')
.config(function(FlickrProvider) {
FlickrProvider.setApiKey('xxxxxxxxxxxxxxxxxxxxxxx')
})
Instead of using the $http service in our controller, we can take the entire
functionality from above and copy+paste it into our service and return the
promise.
// ...
// Service interface
this.$get = function($http) {
var service = {
// Define our service API here
getPublicFeed: function() {
return $http({
method: 'JSONP',
url: base + '/feeds/photos_public.gne?format=json',
params: {
'api_key': api_key,
'jsoncallback': 'JSON_CALLBACK'
}
});
}
};
return service;
}
// ...
46 CHAPTER 10. GETTING CONNECTED TO DATA
Our Flickr service now has a single method: getPublicFeed() that we can
use in our controller. This changes our entire call to the flickr api to look like:
angular.module('myApp')
.controller('FlickrApiController', function($scope, Flickr) {
Flickr.getPublicFeed()
.then(function(data) {
$scope.newPhoto = data.data.items[0];
});
})
One final component that we like to change here is how the data.data.items[0]
looks in the usage of our getPublicFeed() function. This is particularly un-
appealing and requires users of our service to know exactly what’s going on
behind the scenes.
We like to clean this up in our service by creating a custom promise instead of
returning back the $http promise. To do that, we’ll use the $q service directly
to create a promise and resolve that directly. We’ll change our getPublicFeed()
api method to look like:
// ...
this.$get = function($q, $http) {
var service = {
getPublicFeed: function() {
var d = $q.defer();
$http({
method: 'JSONP',
url: base + '/feeds/photos_public.gne?format=json',
params: {
'api_key': api_key,
'jsoncallback': 'JSON_CALLBACK'
}
}).success(function(data) {
d.resolve(data);
}).error(function(reason) {
d.reject(reason);
})
return d.promise;
}
};
return service;
}
// ...
47
Now we can call the getPublicFeed() without needing to use the extra .data:
Flickr.getPublicFeed()
.then(function(photos) {
$scope.newPhoto = photos.items[0];
});
Once logged in, we can head to account overview and create a Firebase. Here,
we’ll create a unique name that’s associated with our account and click create.
The name that we create our Firebase with indicates how we will fetch it later.
For instance, if we named it ng-advent-realtime, then the URL to retrieve
49
50CHAPTER 11. REAL-TIME PRESENCE MADE EASY WITH ANGULARJS AND FIREBASE
Now that we have a Firebase set up, we need need to add the Firebase library
to our project.
We just need to add the script tag below to our index.html page.
<script src="https://cdn.firebase.com/v0/firebase.js"></script>
<script
src='https://cdn.firebase.com/libs/angularfire/0.6.0/angularfire.min.js'>
</script>
We are also kicking things off by bootstrapping our Angular app that we’ll name
myRealtimeAdvent by setting the ng-app directive in our DOM:
<html ng-app="myRealtimeAdvent">
We’ll also set the MainController (that we’ll create shortly) to drive the view:
<body ng-controller="MainController">
Now, in the script.js file, we’ll create the barebone version of our app, so that
our index.html page can actually load:
angular.module('myRealtimeAdvent', [])
.factory('PresenceService', function() {
// Define our presence service here
})
.controller('MainController', ['$scope',
function($scope, PresenceService) {
// Controller goes here
}
])
app.factory('PresenceService', ['$rootScope',
function($rootScope) {
var onlineUsers = 0;
return {
getOnlineUserCount: getOnlineUserCount
}
}
]);
1. Create a main Firebase reference keeps track of how many connected users
are on the page. We are also going to create a child Firebase reference
that uniquely identifies us as a user.
2. Create a presence reference that points to a special Firebase location (at
.info/connected) that monitors a client connection state. We will use this
reference to modify the state of our other references which will propagate
to all of the connected clients.
3. Listen to the main Firebase reference for changes in the user count so that
we can update the rest of the AngularJS application.
We are not going to delve into every part of the Firebase API as it
exists in this code but we strongly encourage checking out the docs
for further reading.
52CHAPTER 11. REAL-TIME PRESENCE MADE EASY WITH ANGULARJS AND FIREBASE
Once the presenceRef is connected, we set the userRef to true on the page
to add ourselves to the listRef.
We will also tell Firebase to remove us when we disconnect using the function
on UserRef userRef.onDisconnect().remove():
Now that we have added ourselves to the list, we need to listen for when other
users are added to the same location.
We can do this by listening for the value and then updating onlineUsers
to snap.numChildren and then broadcasting an onOnlineUser event to all
interested parties.
onlineUsers = snap.numChildren();
$rootScope.$broadcast('onOnlineUser');
});
.factory('PresenceService', ['$rootScope',
function($rootScope) {
var onlineUsers = 0;
return {
getOnlineUserCount: getOnlineUserCount
}
}
]);
Our new PresenceService is complete and ready to be used within our appli-
cation.
54CHAPTER 11. REAL-TIME PRESENCE MADE EASY WITH ANGULARJS AND FIREBASE
And at this point, it we can simply listen for the onOnlineUser event on $scope
and update $scope.totalViewers to the value returned by the function on the
PresenceService called: PresenceService.getOnlineUserCount().
$scope.$on('onOnlineUser', function() {
$scope.$apply(function() {
$scope.totalViewers =
PresenceService.getOnlineUserCount();
});
});
}
])
And now we can bind to totalViewers in our HTML and complete the circuit.
Pushy Angular
Get pushy
In order to work with the Pusher service, we’ll need to sign up for it (obviously).
Head to Pusher and sign up for the service. We’ll be working with the free
account.
Once we’ve signed up, we’ll need to include loading the library in our HTML.
Now, we can do this in the usual way by placing a script tag on the page:
<script src="http://js.pusher.com/2.1/pusher.min.js"
type="text/javascript"></script>
Or we can create a provider to load the library for us. This has many advantages,
the most of which is that it allows us to use Angular’s dependency injection with
externally loaded scripts.
Although we won’t walk through the source here line-by-line, we’ve included
comments at the relevant parts. The following snippet is simply dynamically
loading the library on the page for us.
angular.module('alPusher', [])
.provider('PusherService', function() {
55
56 CHAPTER 12. PUSHY ANGULAR
this.setOptions = function(opts) {
_initOptions = opts || _initOptions;
return this;
}
this.setToken = function(token) {
_token = token || _token;
return this;
}
function onSuccess() {
57
With the PusherService above, we can create a secondary service that will
actually handle subscribing to the Pusher events.
We’ll create a single method API for the Pusher service that will subscribe us
to the Pusher channel.
angular.module('myApp', ['alPusher'])
.factory('Pusher', function($rootScope, PusherService) {
return {
subscribe: function(channel, eventName, cb) {
PusherService.then(function(pusher) {
pusher.subscribe(channel)
.bind(eventName, function(data) {
if (cb) cb(data);
$rootScope
.$broadcast(channel + ':' + eventName, data);
$rootScope.$digest();
})
})
}
58 CHAPTER 12. PUSHY ANGULAR
}
})
This Pusher service allows us to subscribe to a channel and listen for an event.
When it receives one, it will $broadcast the event from the $rootScope. If we
pass in a callback function, then we’ll run the callback function.
For example:
Triggering events
Although it is possible to trigger events from the client, pusher discourages this
usage as it’s insecure and we must be careful to accept all events as client-side
events cannot always be trusted. To allow the client application to trigger events,
make sure to enable it in the applications settings in the browser.
We can then create a trigger function in our factory above:
angular.module('myApp', ['alPusher'])
.factory('Pusher', function($rootScope, PusherService) {
return {
trigger: function(channel, eventName, data) {
channel.trigger(eventName, data);
}
}
})
Tracking nodes
We’ll need to keep track of different nodes with our dashboard. Since we’re
good angular developers and we write tests, we’ll store our nodes and their
active details in a factory.
There is nothing magical about the NodeFactory and it’s pretty simple. It’s
entire responsibility is to hold on to a list of nodes and their current stats:
angular.module('myApp')
.factory('NodeFactory', function($rootScope) {
var service = {
// Keep track of the current nodes
59
nodes: {},
// Add a node with some default data
// in it if it needs to be added
addNode: function(node) {
if (!service.nodes[node]) {
service.nodes[node] = {
load5: 0,
freemem: 0
};
// Broadcast the node:added event
$rootScope.$broadcast('node:added');
}
},
// Add a stat for a specific node
// on a specific stat
addStat: function(node, statName, stat) {
service.addNode(node);
service.nodes[node][statName] = stat;
}
}
return service;
})
Tracking
We’re almost ready to track our server stats now. All we have left to do is
configure our Pusher service with our API key and set up our controller to
manage the stats.
We need to configure the PusherService in our .config() function, like nor-
mal:
angular.module('myApp')
.config(function(PusherServiceProvider) {
PusherServiceProvider
.setToken('xxxxxxxxxxxxxxxxxxxx')
.setOptions({});
})
Now we can simply use our Pusher factory to keep real-time track of our nodes.
We’ll create a StatsController to keep track of the current stats:
angular.module('myApp')
.controller('StatsController', function($scope, Pusher, NodeFactory) {
Pusher.subscribe('stats', 'stats', function(data) {
60 CHAPTER 12. PUSHY ANGULAR
Lastly, our HTML is going to be pretty simple. The only tricky part is looping
over the current nodes as we iterate over the collection. Angular makes this
pretty easy with the ng-repeat directive:
<table>
<tr ng-repeat="(name, stats) in nodes track by $id(name)">
<td>{{ name }}</td>
<td>{{ stats.load5 }}</td>
<td>{{ stats.freemem }}</td>
</tr>
</table>
Google does not run (most) JavaScript when it crawls the web, so it can’t index
our fat JavaScript client websites, including AngularJS pages.
We can have a headless browser – like PhantomJS – open our pages, run the
JavaScript, and save the loaded DOM as an HTML snapshot.
When the Googlebot visits your site, we can serve them that snapshot instead
of our normal page so that Google will see the rendered page same thing your
users see without needing to run any JavaScript.
Read our post for more detailed explanation of this process.
However, a naive implementation of this solution isn’t good enough for most
sites.
In this recipe, we’re going to look at a few key things to watch out for.
Caching snapshots
First and foremost, we’ll need to cache our snapshots in advance. If we open and
run our pages in a headless browser as Googlebot requests them, we’re going
going to see a ton of drag in our requests and the number of problems we’re
opening ourselves up for is going to cause us lots issues.
At best, it will make our site appear really slow to Google, hurting our rankings.
More likely, Google will request a ton of pages at once and our webserver won’t
be able to launch headless browsers fast enough to keep up them. When Google
comes calling, we definitely don’t want to respond with 500 errors.
61
62 CHAPTER 13. ANGULARJS SEO, MISTAKES TO AVOID.
Now that we have a cache, we’ll want to need a way to keep it up to date. When
the content on a page changes, we’ll need to design a way for our system to know
how to update the snapshot in the cache. The specific implementation depends
heavily on our architecture and business needs, but it is a question that we’ll
need to answer.
Do we want to make it time-based? Do we want to manually clear the cache?
How do we know what needs to be cleared?
Also, what happens when we make a site wide change to our site? We’ll need
to be able to regenerate our entire cache. This can take a long time, depending
upon how many pages our app is, how long our system will take to generate
snapshots, etc. When we’re adding a few new pages to the cache everyday, this
might not take that much power, but when we’re regenerating everything, this
process can take a LONG time. It can also introduce the complexities of scaling
past a single machine.
Google does execute some JavaScript. Sometimes things like javascript redirects
or javascript content clearing can have unexpected results when left in the snap-
shot. Since our HTML snapshot already contains exactly what we want Google
to see, we don’t need to leave any javascript in the page to potentially cause
problems for the search engine bot.
Design to fail
It’s important that we are ready for crashes in our system. PhantomJS is great,
but it has a habit of just crashing anytime it gets confused. Our solution needs to
handle this gracefully, restarting PhantomJS as needed and reporting problems
so we can track them down later. By designing a system that we expect to fail,
we can start from the get-go to build a system designed for stability.
Special characters!!!!
Special characters are the bane of web programming and working with your
headless browser is no exception. If we have any urls with characters that have
to be encoded (with those ugly percent signs), we’ll need to set up a test suite
against them such that the routes actually resolve. It’s important that we test
any two byte characters, spaces, or percent signs.
Finally, if we don’t want to have to deal with all of these issues ourselves, then
Brombone might be a good option to check out. Reminder, they are offering
calendar readers 25% off a year of SEO services. We can focus on our core
products and leave the SEO hoola-hooping to them.
63
For our in-depth post on Angular SEO, head to What you need to
know about Angular SEO
64 CHAPTER 13. ANGULARJS SEO, MISTAKES TO AVOID.
Chapter 14
AngularJS 1.2 comes jam packed with animation support for common ng direc-
tives via ngAnimate module.
To enable animations within our application, we’ll need to link the angular-
animate.js JavaScript file and include ngAnimate as a dependency within
our application. The angular-animate.js file can be downloaded from the angu-
larjs.org homepage or from the code.angularjs.org CDN.
We can also use animations within our own directives by using the $animate
service within our link functions.
65
66 CHAPTER 14. ANGULARJS WITH NGANIMATE
What’s nice about ngAnimate is that is fully isolates animations from our direc-
tive and controller code. Animations can be easily reused across other applica-
tions by just including the CSS and/or JS animation code.
Pretty simple!
Transitions
ngAnimate works very nicely with CSS3 Transitions. All we need to do is provide
starting and ending CSS classes. The starting CSS class (known as the setup
class) is where we place the starting CSS styles that we wish to have when the
animation starts. The ending CSS class (known as the active class) is where
we’ll place the styles that we expect our animation to animate towards.
For the example below, let’s setup animations with ngRepeat.
Click here to view this demo live!
/*
* ngRepeat triggers three animation events: enter, leave and move.
CSS3 TRANSITIONS & KEYFRAME ANIMATIONS 67
*/
.repeat-animation.ng-enter,
.repeat-animation.ng-leave,
.repeat-animation.ng-move {
-webkit-transition:0.5s linear all;
transition:0.5s linear all;
}
(click here to and or edit the demo code in a live editor on plnkr.co).
Keyframe Animations
Keyframe animations triggered by ngAnimate work just about the same as with
transitions, however, only the setup class is used. To make this work, we’ll
just reference the keyframe animation in our starting CSS class (the setup class).
For the example below, let’s setup animations with ngView.
Click here to view this demo live!
<div class="view-container">
<div ng-view class="view-animation"></div>
</div>
/*
* ngView triggers three animation events: enter, leave and move.
*/
68 CHAPTER 14. ANGULARJS WITH NGANIMATE
.view-container {
height:500px;
position:relative;
}
.view-animation.ng-enter {
-webkit-animation: enter_animation 1s;
animation: enter_animation 1s;
/*
* ng-animate has a slight starting delay for optimization purposes
* so if we see a flicker effect then we'll need to put some extra
* styles to "shim" the animation.
*/
left:100%;
}
.view-animation.ng-leave {
-webkit-animation: leave_animation 1s;
animation: leave_animation 1s;
}
.view-animation.ng-leave,
.view-animation.ng-enter {
position:absolute;
top:0;
width:100%;
}
/*
* the animation below will move enter in the view from
* the right side of the screen
* and move the current (expired) view from the center
* of the screen to the left edge
*/
@keyframes enter_animation {
from { left:100%; }
to { left:0; }
}
@-webkit-keyframes enter_animation {
from { left:100%; }
to { left:0; }
}
@keyframes leave_animation {
JAVASCRIPT ANIMATIONS 69
from { left:0; }
to { left:-100%; }
}
@-webkit-keyframes leave_animation {
from { left:0; }
to { left:-100%; }
}
(click here to and or edit the demo code in a live editor on plnkr.co).
Both CSS3 Transitions and Keyframe Animations with ngAnimate are automat-
ically canceled and cleaned up if a new animation commences when an existing
animation is midway in its own animation.
JavaScript Animations
JavaScript animations are also supported and they also work by referencing a
CSS class within the HTML template.
To define our own JS animations, just use the .animation() factory method
within our ngModule code.
For the example below, let’s setup animations with ngShow using the amazing
animation library http://www.greensock.com/.
Click here to view this demo live!
myModule.animation('.show-hide-animation', function() {
/*
* the reason why we're using beforeAddClass and
* removeClass is because we're working
* around the .ng-hide class (which is added when ng-show
* evaluates to false). The
* .ng-hide class sets display:none!important and we want
* to apply the animation only
* when the class is removed (removeClass) or before
* it's added (beforeAddClass).
*/
return {
/*
* make sure to call the done() function when the animation is complete.
*/
beforeAddClass : function(element, className, done) {
if(className == 'ng-hide') {
TweenMax.to(element, 1, { height: 0, onComplete: done });
/*
* make sure to call the done() function when the animation is complete.
*/
removeClass : function(element, className, done) {
if(className == 'ng-hide') {
//set the height back to zero to make the animation work properly
var height = element.height();
element.css('height', 0);
} else {
done();
}
}
}
});
(click here to and or edit the demo code in a live editor on plnkr.co).
In addition to beforeAddClass and removeClass, we can also use
beforeRemoveClass and addClass as well. For structural animations,
such as ngRepeat, ngView, ngIf, ngSwitch or ngInclude use, we can use enter,
leave and move.
It’s up to us to determine how to perform animations within JS animations.
Just be sure to call done() to close off the animation (even if we’re skipping
an animation based on the className … Otherwise the animation would never
close).
.horizontal-animation {
-webkit-transition: 0.5s linear all;
transition: 0.5s linear all;
width:100px;
}
.horizontal-animation.on {
width:500px;
}
(click here to and or edit the demo code in a live editor on plnkr.co).
With Angular, it’s trivial to use HTML5 APIS given to us by the browser. In
this snippet, we’re going to walk through how to get a popular one from the
browser: the geolocation.
When using an HTML5 API, we’ll always need to handle the case when someone
is visiting our page from a non-html browser that doesn’t implement the same
HTML5 apis. We’ll deal with this shortly. First, let’s look look at how to grab
the geolocation on an HTML5 compliant browser (if you’re not, you should be
using Chrome to visit this page for the best experience).
Are we compatible?
With this simple check, we’re ready to get the geolocation for our browser. The
geolocation.getCurrentPosition() method takes a single argument: a callback
function to run with the position. Because the getCurrentPosition() is asyn-
chronous, we have to pass in a callback function to actually fetch the position.
window.navigator.geolocation.getCurrentPosition(function(position) {
// position is a JSON object that holds the lat and long
73
74 CHAPTER 15. HTML5 API: GEOLOCATION
We can wrap this into a controller function, like so such that we can attach the
results to the view (as we’ve done in the example below):
$scope.getLocation = function() {
$window.navigator.geolocation.getCurrentPosition(function(pos) {
$scope.pos = pos;
$scope.$digest();
});
}
<div ng-controller='GeoLocationController'>
<div ng-if="geoSupport()">
<a ng-click="getLocation()" class="btn btn-primary">Get location</a>
<span ng-bind-html="pos"></span>
</div>
<div ng-if="!geoSupport()"></div>
</div>
Handling errors
Sometimes the geolocation api does not come back successfully. It may be that
the user chose not to allow our app access to their geolocation or that perhaps the
GPS unit did not return in time. We need to be able to handle these failures as
they will happen. In order to handle them, the getCurrentPosition() allows
us to pass a second error handling function that will be called with the error:
$scope.getLocation = function() {
$window.navigator.geolocation.getCurrentPosition(function(pos) {
$scope.pos = pos;
$scope.$digest();
}, function(err) {
75
Tracking location
With the getCurrentPosition() function, it will only ever get executed once.
If we want to continuously track the user’s position, we’ll need to use a different
function on the geolocation object: watchPosition(). The watchPosition()
method will call the callback function anytime the position changes.
$scope.watchPosition = function(cb) {
$window.navigator.geolocation.watchPosition(cb);
}
$scope.positionChanged = function(pos) {
console.log("called positionChanged");
$scope.$digest();
}
Okay, this is all well and great, but it’s not very angular. Instead of placing all of
our functions on top of our $scope, it’s better to nest them on a service that can
be called from the $scope. We can basically copy and paste the code we already
have written above into our new service with only a few minor modifications
to account for using promises and events to communicate state. We’ll include
comments inline to explain the code:
return service;
})
We can use this factory by calling the API on our scope. Notice that we provide
multiple ways to use the results on our scope. This way we can pass in a function
OR we can simply listen for events on our scopes.
angular.module('myApp')
.controller('GeoController', function($scope, Geolocation) {
$scope.getPosition = function() {
Geolocation.getPosition()
.then(function(pos) {
$scope.position = pos;
})
}
});
78 CHAPTER 15. HTML5 API: GEOLOCATION
Chapter 16
In the previous recipe, we looked at how to grab the geolocation using the
HTML5 geolocation API. In this snippet, we’re going to check out how to grab
camera controls through the HTML5 api with Angular.
There is quite a lot of history surrounding the camera API. Although we won’t
go through it here as it’s outside of the scope of this snippet, but it’s important
to note that because of the not-yet-solidified camera API and we’ll need to work
around it. We’ll also then dive head-first into using it with Angular. Let’s get
started.
Are we compatible?
In order to check if our user’s browser implements the camera API we’ll need to
check to see if it implements any of the known camera API functions. We can
iterate through all of the different options and check if the browser implements
them.
It’s always a good idea to check for compatibility and show the result to the
user. We can do it with the following snippet:
“‘javascript
79
80 CHAPTER 16. HTML5 API: CAMERA
Once we’ve determined if the camera API is available, we can request access
to it from the browser. To request access, we’ll need to request which type of
access we would like. There are two types of available input we can take:
• audio
• video
The first argument of the userMedia object is the JSON object of which media
input we are interested. The second two are the success callback function and
the error callback function that get called with their respective place when the
method succeeds or fails.
getUserMedia({
audio: false,
video: true
}, function success(stream) {
// We now have access to the stream
}, function failure(err) {
// It failed for some reason
});
Once one of these callbacks return, we’ll have access to our media api.
Before we get into actually using the API, let’s wrap this component in a service.
This will make it incredibly easy to test later on (not covered in this snippet). It
will also abstract away any dealings we’ll need to do with the userMedia object
and is quite simple:
angular.module('myApp')
.factory('CameraService', function($window) {
var hasUserMedia = function() {
return !!getUserMedia();
}
81
return {
hasUserMedia: hasUserMedia(),
getUserMedia: getUserMedia
}
})
With this service, we can simply request access to the user media inside of our
other angular objects. For instance:
angular.module('myApp')
.controller('CameraController', function($scope, CameraService) {
$scope.hasUserMedia = CameraService.hasUserMedia;
})
Now that we have a hold of the userMedia object, we can request access to it.
Since we’re going to place this element on the page, it’s a good idea to write it
as a directive. The non-camera-specific directive code:
angular.module('myApp')
.directive('camera', function(CameraService) {
return {
restrict: 'EA',
replace: true,
transclude: true,
scope: {},
template: '<div class="camera"><video class="camera" autoplay="" />\
<div ng-transclude></div></div>',
link: function(scope, ele, attrs) {
var w = attrs.width || 320,
h = attrs.height || 200;
if (!CameraService.hasUserMedia) return;
var userMedia = CameraService.getUserMedia(),
82 CHAPTER 16. HTML5 API: CAMERA
videoElement = document.querySelector('video');
// We'll be placing our interaction inside of here
}
}
});
We can now request access to the camera inside of our link function in the
directive. This simply means that we’ll set up the two callback functions that
will be called from the getUserMedia() method call and then make the request
for permission:
scope.w = w;
scope.h = h;
Once we’ve asked permission, the video element will get a src url and start
playing. For instance:
83
<div ng-controller="CameraController">
<div ng-if="hasUserMedia">
<a class="btn btn-info" ng-click="enabled=!enabled">Demo</a>
<camera ng-if="enabled"></camera>
</div>
<div ng-if="!hasUserMedia" class="sad">
Your browser does not support the camera API
</div>
</div>
This directive is pretty simple and reasonably so. However, what if we want to
make a call to say, create a snapshot of the current frame. For instance, to allow
our users to take a photo of themselves for a profile photo. This is pretty easy
to do as well. We’ll simply add a function on our scope that will handle taking
the photo for us.
Note, that we will create a way for us to extend the directive with new controls,
each we’ll create in the similar fashion. First, to implement this pattern, we’ll
need to add a controller to our parent directive (camera):
// scope: {},
controller: function($scope, $q, $timeout) {
this.takeSnapshot = function() {
var canvas = document.querySelector('canvas'),
ctx = canvas.getContext('2d'),
videoElement = document.querySelector('video'),
d = $q.defer();
canvas.width = $scope.w;
canvas.height = $scope.h;
$timeout(function() {
ctx.fillRect(0, 0, $scope.w, $scope.h);
ctx.drawImage(videoElement, 0, 0, $scope.w, $scope.h);
d.resolve(canvas.toDataURL());
}, 0);
return d.promise;
}
},
// ...
Now we can safely create camera controls with this parent DOM element. For
instance, the snapshot controller:
84 CHAPTER 16. HTML5 API: CAMERA
.directive('cameraControlSnapshot', function() {
return {
restrict: 'EA',
require: '^camera',
scope: true,
template: '<a ng-click="takeSnapshot()">Take snapshot</a>',
link: function(scope, ele, attrs, cameraController) {
scope.takeSnapshot = function() {
cameraController.takeSnapshot()
.then(function(image) {
// data image here
});
}
}
}
})
<camera>
<camera-control-snapshot></camera-control-snapshot>
</camera>
Now that you know how to handle the camera inside Angular, put on your best
face and start taking some photos!
The source for the article can be found at the jsbin example.
Chapter 17
In this recipe, we’re looking at how to connect our application to the great deals
offered by the Groupon API.
The Groupon API requires a an api_key to authenticate the requesting user.
Luckily, getting a Groupon api_key is really easy to do.
Head over to the Groupon API docs page at http://www.groupon.com/pages/
api and click on the giant Get My Api Key button.
After we sign up, we get our API key. Let’s hold on to this key for the time
being.
Anytime that we are building a service that we’ll want to configure, for example
with an API key we’ll want to use the .provider() function to create a service.
This will allow us to craft a service that holds on to our key for future requests.
We’ll write the service to be able to be configured with an api key. This can be
accomplished like so:
angular.module('alGroupon', [])
.provider('Groupon', function() {
// hold on to our generic variables
var apiKey = '',
baseUrl = '//api.groupon.com/v2';
85
86 CHAPTER 17. GOOD DEALS – ANGULAR AND GROUPON
this.setApiKey = function(key) {
apiKey = key || apiKey;
}
this.$get = [function() {
// Service definition
}]
});
In our app, we’ll use this module by setting it as a dependency and then config-
uring our service inside of a .config() function. For example:
angular.module('myApp', ['alGroupon'])
.config(function(GrouponProvider) {
GrouponProvider
.setApiKey('XXXXXXXXXXXXXXXXXXX');
})
Now we haven’t actually created any functionality for the Groupon service. Let’s
go ahead and create a function that will find some of the latest deals.
The API method to get the latest deals (can be found in the docs page) is at the
route /deals.json. Luckily for us, the Groupon API supports JSONP fetching
which allows us to fetch the deals without needing to concern ourselves with
CORS.
Inside of our requests, we’ll need to pass in two parameters for each one of our
requests:
• callback
• client_id
With that set, let’s create the function that gets some deals. This simply is a
function that we can set up with the standard $http request:
Now, we can simply inject this into our controller and call getDeals on the
service as we would any other service.
angular.module('myApp', ['alGroupon'])
.controller('GrouponController',
88 CHAPTER 17. GOOD DEALS – ANGULAR AND GROUPON
function($scope, Groupon) {
$scope.getDeals = function() {
Groupon.getDeals()
.then(function(deals) {
$scope.deals = deals;
});
}
});
<div ng-controller='GrouponExampleController'>
<a ng-click="getDeals()" class="btn btn-warning">Get deals</a>
<div ng-show="showing">
<a ng-click="showing=!showing" class="btn btn-primary">Hide</a>
<span ng-bind-html="deals"></span>
</div>
</div>
Notice in our function we allow the user pass in configuration for the requests.
We can use this configuration object to customize our request to the Groupon
API.
That’s it. Connecting with Groupon is that easy with our configurable service.
/*
* ngRepeat triggers three animation events: enter, leave and move.
89
90 CHAPTER 18. STAGGERING ANIMATIONS WITH NGANIMATE
*/
.repeat-animation.ng-enter,
.repeat-animation.ng-leave,
.repeat-animation.ng-move {
-webkit-transition:0.5s linear all;
transition:0.5s linear all;
}
.repeat-animation.ng-enter,
.repeat-animation.ng-leave,
.repeat-animation.ng-move {
-webkit-transition:0.5s linear all;
transition:0.5s linear all;
}
.repeat-animation.ng-enter-stagger,
.repeat-animation.ng-leave-stagger,
.repeat-animation.ng-move-stagger {
/* 50ms between each item being animated after the other */
-webkit-transition-delay:50ms;
transition-delay:50ms;
And voila! We have a nice delay gap between each item being entered. To see
this demo in action, click on the link below.
http://d.pr/l4ln
.repeat-animation.ng-enter {
-webkit-animation: enter_animation 0.5s;
animation: enter_animation 0.5s;
}
.repeat-animation.ng-leave {
-webkit-animation: leave_animation 0.5s;
animation: leave_animation 0.5s;
}
@-webkit-keyframes enter_animation {
from { opacity:0; }
to { opacity:1; }
}
@keyframes enter_animation {
from { opacity:0; }
to { opacity:1; }
}
@-webkit-keyframes leave_animation {
from { opacity:1; }
to { opacity:0; }
}
@keyframes leave_animation {
from { opacity:1; }
92 CHAPTER 18. STAGGERING ANIMATIONS WITH NGANIMATE
to { opacity:0; }
}
(click here to get learn the basics of ngAnimate with keyframe animations).
So far so good. Now let’s enhance that yet again using some stagger CSS code
.repeat-animation.ng-enter-stagger,
.repeat-animation.ng-leave-stagger,
.repeat-animation.ng-move-stagger {
/* notice how we're using animation instead of transition here */
-webkit-animation-delay:50ms;
animation-delay:50ms;
Keep in mind, however, that CSS Keyframe animations are triggered only after
the “reflow” operation has run within an animation triggered by ngAnimate.
This means that, depending on how many animations are being run in parallel,
the element may appear in a pre-animated state for a fraction of a second.
Why? Well this is a performance boost that the $animate service uses internally
to queue up animations together to combine everything to render under one
single reflow operation. So if our animation code starts off with the element
being hidden (say opacity:0) then the element may appear as visible before the
animation starts. To inform $animate to style the element beforehand, we can
just place some extra CSS code into our setup CSS class (the same CSS class
where we reference the actual keyframe animation).
.repeat-animation.ng-enter {
/* pre-reflow animation styling */
opacity:0;
And finally, here’s a link to our amazing, stagger-enhanced, CSS keyframe ani-
mation code: http://d.pr/dmQk.
Ahh this is sweet isn’t it?
WHAT DIRECTIVES SUPPORT THIS? 93
One of the fundamental reasons for choosing Angular is cited as that it is built
with testing in mind. We can build complex web applications using various
popular frameworks and libraries with features as tall as the sky and as equally
complex. As with anything of increasing complexity and density, this can quickly
grow out of hand.
Testing is a good approach to keep code maintainable, understandable, debug-
able, and bug-free. A good test suite can help us find problems before they rise
up in production and at scale. It can make our software more reliable, more
fun, and help us sleep better at night.
There are several schools of thought about how to test and when to test. As
this snippet is more about getting us to the tests, we’ll only briefly look at the
different options. In testing, we can either:
For the application that we’re working on, it’s really up to us to determine
what style makes sense for our application. In some cases, we operate using
TDD style, while in others we operate with WBT.
95
96 CHAPTER 19. GETTING STARTED UNIT-TESTING ANGULAR
Getting started
If we’re using the yeoman generator with our apps, this is already
set up for us.
It will ask us a series of questions and when it’s done, it will create a configu-
ration file. Personally, we usually go through and answer yes to as many which
are asked (except without RequireJS). We like to fill in the files: section
manually (see below).
When it’s done, it will create a file in the same directory where we ran the
generator that looks similar to the following:
// Karma configuration
module.exports = function(config) {
config.set({
// base path, that will be used to resolve files and exclude
basePath: '',
97
// level of logging
// possible values:
// LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG
logLevel: config.LOG_INFO,
This karma.conf.js file describes a simple unit test that karma will load when
we start writing tests. We can also tell it to build an e2e, or end-to-end test
98 CHAPTER 19. GETTING STARTED UNIT-TESTING ANGULAR
that is specifically intended for building black-box style testing, but that’s for
another snippet/article.
Now that we have our karma.conf.js file generated, we can kick off karma by
issuing the following command:
If we haven’t run it before, it’s likely going to fail or at least report errors of
files not being found. Let’s start writing our first test.
In Write-Behind development, we’re going to test the following controller:
angular.module('myApp', [])
.controller('MainController', function($scope) {
$scope.name = "Ari";
$scope.sayHello = function() {
$scope.greeting = "Hello " + $scope.name;
}
})
Inside our freshly created file, let’s add the test block:
Great! Now we need to do a few things to tell our tests what we are test-
ing. We need to tell it what module we are testing. We can do this by us-
ing the beforeEach() function to load the angular module that contains our
MainController (in this case, it’s just myApp):
99
Next, we’re going to pretend that we’re angular loading the controller when it
needs to be instantiated on the page. We can do this by manually instantiating
the controller and handing it a $scope object. Creating it manually will also
allow us to interact with the scope throughout the tests.
Now, we have access to both the controller as well as the scope of the controller.
Writing a test
Now that everything is all set up and ready for testing, let’s write one. It’s
always a good idea to test functionality of code that we write. Anytime that
variables can be manipulated by the user or we’re running any custom actions,
it’s usually a good idea to write a test for it.
In this case, we won’t need to test setting the name to “Ari” as we know that
will work (it’s JavaScript). What we would like to know, however is that the
sayHello() function works as-expected.
The sayHello() method simply preprends the $scope.name to a variable called
$scope.greeting. We can write a test that verifies that $scope.greeting is
undefined before running and then filled with our expected message after we
run the function:
100 CHAPTER 19. GETTING STARTED UNIT-TESTING ANGULAR
We have access to all different parts of the controller through the scope of it
now. Feel the power of testing? This is only the beginning. For a deeper dive
into testing, check out the expansive testing chapter in ng-book.
Chapter 20
Build a Real-Time,
Collaborative Wishlist with
GoAngular v2
In this snippet, we’re going to look at how to use GoInstant and GoAngular to
create the collaborative wishlist you see below. Check out the final version of
the app here.
We only need NodeJS and npm installed to get our wishlist app up
and running. For more information on installing NodeJS, check out
the nodejs.org page.
101
102CHAPTER 20. BUILD A REAL-TIME, COLLABORATIVE WISHLIST WITH GOANGULAR V2
SIGN UP FOR GOINSTANT 103
Before we can get started, we’ll need to sign up for GoInstant and create an
app. Let’s call it wishlist.
angular.module('WishList', []);
Next we’ll add our external dependencies: GoInstant and GoAngular are avail-
able on bower or via their CDN.
In our index.html, we’ll need to make sure the following lines are added:
<script
src="https://cdn.goinstant.net/v1/platform.min.js">
</script>
<script
src="https://cdn.goinstant.net/integrations/goangular/v2.0.0/goangular.min.js">
</script>
Let’s hit two birds with one stone: We’ll declare dependencies for our app
module and controller, and configure our GoInstant connection, with the URL
we retrieved in step one.
angular.module('WishList', ['goangular'])
.config(function(goConnectionProvider) {
// Connect to our GoInstant backend url
goConnectionProvider.set(CONFIG.connectUrl);
})
After we’ve included goangular as a dependency for our app, we can inject
the goConnection into our angular objects, which we’ll use to connect to our
GoInstant back-end:
angular.module(‘WishList’, [‘goangular’])
.controller('ListController', function($scope, goConnection) {
var itemsKey;
goConnection.ready()
.then(function(goinstant) {
// create a room, join it and return a promise
return goinstant.room('a-list').join().get('room');
});
});
Now we can use the room from the previous promise inside the next promise. In
this case, we’re trickling information down from the room, which in turn gets
used to fetch the list of items inside the room, which finally gets resolved onto
the $scope object as $scope.items.
goConnection.ready().then(function(goinstant) {
return goinstant.room('a-list').join().get('room');
}).then(function(room) {
return room.key('items').get();
}).then(function(result) {
$scope.items = result.value || {};
}).finally($scope.$apply);
This interaction is actually quite performant and also makes it incredibly easy
to test the features of our app.
WATCH OUR WISHES, SO WE KNOW WHEN THEY CHANGE 105
var itemsKey;
goConnection.ready().then(function(goinstant) {
return goinstant.room('a-list').join().get('room');
}).then(function(room) {
itemsKey = room.key('items');
return itemsKey.get();
}).then(function(result) {
$scope.items = result.value || {};
$scope.addWish = function() {
itemsKey.add($scope.wish);
};
itemsKey.on('add', {
local: true,
listener: function(value, context) {
$scope.$apply(function() {
var itemKey = context.addedKey.split('/')
$scope.items[itemKey[itemKey.length -1 ]] = value;
});
}
});
}).finally($scope.$apply);
Immediately satisfying
users with a splash page
107
108CHAPTER 21. IMMEDIATELY SATISFYING USERS WITH A SPLASH PAGE
We can use the fact that the browser starts to render elements as soon as it
encounters them to show a splash screen while the rest of the page is loading.
A splash screen is simply a full-screen block of HTML that we will show when
the page is loading.
To implement a splash screen, we’ll need to strip all of the loading of any external
files OUTSIDE of the <head> element. This includes any external CSS and
JavaScript. The only elements we want in the <head> element are meta tags,
the <title> element, and any in-line styles that our splash screen will use (as
we’ll see shortly).
Secondly, the first element that we want in our <body> tag is the splash screen
head element. This way, once the browser starts parsing the <body> tag it will
lay out the splash screen elements and style them before it starts to load any
other files.
<html>
<head>
<title>My Angular App</title>
<style type="text/css">
.splash {}
</style>
</head>
<body ng-app="myApp">
<!-- The splash screen must be first -->
<div id="splash" ng-cloak>
<h2>Loading</h2>
</div>
<!-- The rest of our app -->
<script
src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.11/angular.min.js">
</script>
<div ng-controller="MainController">
</div>
<script src="js/app.js"></script>
</body>
</html>
Laying out our page like this will give us the most optimal appearing loading
of our app. What goes inside our splash screen element is really up to us. The
more we can inline the elements that appear in the splash screen, the faster it
will run.
109
Using base64 encoding for our images or SVG elements are easy methods of
making our splash screen appear to be part of the normal, non-static flow of
a splash screen. The more external elements we place inside of the top-most
element, the slower it will appear.
Now, once we have our HTML setup like what we have above, we’ll need to
style our <div id="splash"> element. We can do this by placing the style
definitions in the sole <style> element in the <head> tag of our page.
<head>
<title>My Angular App</title>
<style type="text/css">
/* some naive styles for a splash page */
.splash {
background: blue;
color: white;
}
.splash h2 {
font-size: 2.1em;
font-weight: 500;
}
</style>
</head>
Lastly, we’ll need to make sure that our splash screen goes away at the end of
the loading process and let our app reclaim the page.
We can do this using the ngCloak directive that is built into Angular itself. The
ngCloak directive’s entire responsibility is to hide uncompiled content from the
browser before the angular app has been loaded.
The ngCloak directive works by adding a CSS class to the element that sets a
display: none !important; to the element. Once the page has been loaded
and angular is bootstrapping our app, it will remove the ng-cloak class from
the element which will remove the display: none and show the element.
We can hijack this ngCloak directive for the splash page and invert the style for
the splash element only. For example, in the CSS in our <head> element, we
can place the following CSS definitions:
[ng-cloak].splash {
display: block !important;
}
[ng-cloak] {display: none;}
110CHAPTER 21. IMMEDIATELY SATISFYING USERS WITH A SPLASH PAGE
With this in place, our splash screen will load and be shown in our app before
anything else is shown and we made NO changes to the functionality of our
app.
A Few of My Favorite
Things: Isolated Expression
Scope
We are going to create a vote directive that has an vote-up and vote-down
method. Isolated expression scope comes into play the moment we decide that
we want to be able to increment and decrement the vote count by varying
denominations. In our case, we want one vote directive instance to increment
and decrement the vote count by 100, another to increment and decrement the
vote count by 10 and so on.
111
112CHAPTER 22. A FEW OF MY FAVORITE THINGS: ISOLATED EXPRESSION SCOPE
We will use the HTML as a starting point and walk through how the vote
directive actually comes to together. We have a MainController that has a
property of votes that we are going to use to keep track of our total vote count.
We also have three vote components on the page and in each one we are defining
label, vote-up and vote-down differently.
Notice that the label corresponds with the denomination that the vote directive
increments or decrements the vote count. The directive with the label 100 has a
method of incrementHundred and decrementHundred which does exactly that
to the votes property.
Now that we have created some AngularJS requirements by defining them in
our HTML, let us fulfill those in our JavaScript. First up, we have created the
MainController with the votes property defined on scope as well as all of the
increment and decrement properties we declared.
Just below that we are going to declare the vote directive.
angular.module('website', [])
.controller('MainController', ['$scope',
function($scope) {
$scope.votes = 0;
$scope.incrementOne = function() {
$scope.votes += 1;
};
$scope.decrementOne = function() {
$scope.votes -= 1;
};
$scope.incrementTen = function() {
113
$scope.votes += 10;
};
$scope.decrementTen = function() {
$scope.votes -= 10;
};
$scope.incrementHundred = function() {
$scope.votes += 100;
};
$scope.decrementHundred = function() {
$scope.votes -= 100;
};
}
])
.directive('vote', function() {
return {
restrict: 'E',
templateUrl: 'vote.html',
scope: {
voteUp: '&',
voteDown: '&',
label: '@'
}
};
});
And for our directive markup, we have an anchor tag that calls voteUp on
ng-click, a div that displays the label value and another anchor tag to call
voteDown.
<div class="vote-set">
<a href="#" class="icon-arrow-up" ng-click="voteUp()"></a>
<div class="number">{{label}}</div>
<a href="#" class="icon-arrow-down" ng-click="voteDown()"></a>
</div>
And now we have created a vote directive that we are using in three places to
increment the vote count with different denominations.
vote-down="decrementVote(unit)"></vote>
</div>
</div>
And in the template markup below, we are going to call voteUp and voteDown
and with a value for the parent controller.
<div class="vote-set">
<a href="#" class="icon-arrow-up"
ng-click="voteUp({unit:label})"></a>
<div class="number">{{label}}</div>
<a href="#" class="icon-arrow-down"
ng-click="voteDown({unit:label})"></a>
</div>
We are going to use the value we defined for the label property as our unit
value. And in the MainController, we will take the the unit value and cast it
to an integer with parseInt(unit,10) and add or subtract it to $scope.votes.
angular.module('website', [])
.controller('MainController', ['$scope',
function($scope) {
$scope.votes = 0;
// ...
$scope.incrementVote = function(unit) {
$scope.votes += parseInt(unit,10);
};
$scope.decrementVote = function(unit) {
$scope.votes -= parseInt(unit,10);
};
}
])
For the sake of illustration, we are going to add a vote directive that increments
and decrements the vote count by 1000. When this happens, we want to call a
second method called alertAuthorities because clearly something is amiss!
angular.module('website', [])
.controller('MainController', ['$scope',
function($scope) {
$scope.votes = 0;
// ...
$scope.incrementVote = function(unit) {
$scope.votes += parseInt(unit,10);
};
$scope.decrementVote = function(unit) {
$scope.votes -= parseInt(unit,10);
};
$scope.alertAuthorities = function() {
$scope.alert = 'The authorities have been alerted!';
};
}
])
In Conclusion
We started out with a simple isolated expression scope example and then ex-
tended it to pass values from the directive to the parent controller. And then to
wrap things up, we learned how we could actually call more than one method
on a parent controller using expression isolated scope.
This snippet was written by Lukas Ruebbelke (aka @simpulton) and edited by
Ari Lerner (aka @auser).
118CHAPTER 22. A FEW OF MY FAVORITE THINGS: ISOLATED EXPRESSION SCOPE
Chapter 23
Conclusion
We hope you enjoyed our mini Angular cookbook and can’t wait to see what
you create with these snippets!
For more in-depth Angular coverage, check out our 600+ page ng-book. It
covers these type of snippets and much much more, including authentication,
optimizations, validations, building chrome apps, security, SEO, the most de-
tailed testing coverage available, and much much more!
Stay tuned to ng-newsletter for our new screencast series where we’ll feature
highly detailed angular recipes with both back-end support and detailed walk-
throughs of complete, professional applications.
Finally, enjoy our weekly newsletter: the best, hand-curated Angular informa-
tion delivered to your inbox once a week.
Cheers!
119