One final example will actually cover a multitude of scenarios, most or all of them linked with the $http resource. In our experience, the $http service is one of the core services in AngularJS. But it can be extended to do a lot of the common requirements of a web app, including:
• Having a common error-handling point
• Handling authorization and login redirects
• Working with servers that don’t understand or speak JSON
• Talking with external services (outside the same origin) via JSONP
So in this (slightly contrived) example, we will have the skeleton of a full-fledged app that will:
1. Show all unrecoverable errors (Non 401s) in a butterbar directive that gets shown on all screens only when an error exists.
2. Have an ErrorService which will be used for communicating between the direc‐
tive, the view, and the controllers.
3. Fire an event (event:loginRequired) whenever the server responds with a 401.
This will then get handled by a root controller that oversees the entire application.
4. Handle requests that need to be made to the server with some authorization headers that are specific to the current user.
We will not go over the entire application (the routes, the templates, and so on), as most of those are fairly straightforward. We will highlight only the pieces that are relevant (so you can copy and paste that into your codebase and get started right away). These will be fully functional. If you want to revisit defining Services and Factories, jump to Chapter 7. If you want to take a look at how to work with servers, you can refer to Chapter 5.
Let us first take a look at the Error service:
var servicesModule = angular.module('myApp.services', []);
servicesModule.factory('errorService', function() {
Working with Servers and Login | 171
return {
Our error message directive, which is actually independent of the Error service, would just look for an alert message attribute, and then bind to it. It would conditionally show itself when the alert message is present.
// USAGE: <div alert-bar alertMessage="myMessageVar"></div>
angular.module('myApp.directives', []).
directive('alertBar', ['$parse', function($parse) { return {
restrict: 'A',
template: '<div class="alert alert-error alert-bar"' + 'ng-show="errorMessage">' +
'<button type="button" class="close" ng-click="hideAlert()">' + 'x</button>' +
'{{errorMessage}}</div>',
link: function(scope, elem, attrs) {
var alertMessageAttr = attrs['alertmessage'];
scope.errorMessage = null;
scope.$watch(alertMessageAttr, function(newVal) { scope.errorMessage = newVal;
$parse(alertMessageAttr).assign(scope, null);
};
} };
}]);
We would then add the alert bar to the HTML as follows:
<div alert-bar alertmessage="errorService.errorMessage"></div>
We need to ensure that the ErrorService is saved on the scope of the controller as
“errorService” before we add the preceding HTML. That is, if RootController was the controller responsible for having the AlertBar, then:
app.controller('RootController',
['$scope', 'ErrorService', function($scope, ErrorService) { $scope.errorService = ErrorService;
});
That gives us a decent framework to show and hide errors and alerts. Now let us see how we can tackle the various status codes that the server can throw at us, through the use of an interceptor:
servicesModule.config(function ($httpProvider) {
$httpProvider.responseInterceptors.push('errorHttpInterceptor');
});
// register the interceptor as a service // intercepts ALL angular ajax HTTP calls servicesModule.factory('errorHttpInterceptor',
function ($q, $location, ErrorService, $rootScope) { return function (promise) {
return promise.then(function (response) { return response;
}, function (response) {
if (response.status === 401) {
$rootScope.$broadcast('event:loginRequired');
} else if (response.status >= 400 && response.status < 500) {
Now all that needs to happen is for some controller somewhere to listen for a loginRe quired event, and redirect to the login page (or do something more complex, like display a modal dialog with login options).
$scope.$on('event:loginRequired', function() { $location.path('/login');
});
That just leaves requests that will need authorization. Let us just say that all requests that require authorization will need a header—“Authorization”—which will have a value that is specific for the current user that is logged in. Since this will change every time, we cannot use default transformRequests, as those are config level changes. We will instead wrap the $http service, and create our own AuthHttp service.
We will also have an Authentication service that is responsible for storing the user’s auth information (fetched however you want, normally as part of the login process). The AuthHttp service will refer to this Authentication service and add the necessary headers to authorize the requests.
Working with Servers and Login | 173
// This factory is only evaluated once, and authHttp is memorized. That is, // future requests to authHttp service return the same instance of authHttp servicesModule.factory('authHttp', function($http, Authentication) { var authHttp = {};
// Append the right header to the request var extendHeaders = function(config) { config.headers = config.headers || {};
config.headers['Authorization'] = Authentication.getTokenType() + ' ' + Authentication.getAccessToken();
};
// Do this for each $http call
angular.forEach(['get', 'delete', 'head', 'jsonp'], function(name) { authHttp[name] = function(url, config) {
config = config || {};
extendHeaders(config);
return $http[name](url, config);
};
});
angular.forEach(['post', 'put'], function(name) { authHttp[name] = function(url, data, config) { config = config || {};
extendHeaders(config);
return $http[name](url, data, config);
};
});
return authHttp;
});
Any request that requires authorization will be made via authHttp.get() instead of
$http.get(). As long as the Authentication service is set with the right information, your calls will fly through with ease. We use a service for Authentication as well, so that the information is available throughout the app, without having to refetch it every time the route changes.
That pretty much covers all the pieces we would need for this application. You should be able to just copy the code right out of here, paste into your application, and make it work for you. Good luck!
Conclusion
While this brings us to the end of our book, we are nowhere near close to covering everything about AngularJS. Our aim with this book was to provide a solid foundation from which one can begin her explorations and become comfortable with developing in AngularJS. To this extent, we covered all the basics (and some advanced topics), while providing as many examples as we could along the way.
Are we done? No. There is still a great amount to learn about how AngularJS operates under the covers. We didn’t touch upon creating complex, interdependent directives, for example. There is so much more out there, that three or even four books wouldn’t be enough. But we hope that this book gives you the confidence to be able to tackle much more complex requirements head on.
We had a great time writing this book, and hope to see some amazing applications written in AngularJS out on the Internet.
Conclusion | 175
We’d like to hear your suggestions for improving our indexes. Send email to [email protected].
Model View Controller (MVC) in, 3 vs. other apps, 2
debugging, 59
native speed, 31
manipulation in Angular, 3, 5, 79, 132 unit tests and, 21
domReady, 69
doSomething() function, 20
double-curly syntax interpolation, 12, 16, 24
E
Origin null is not allowed, 125 eval() function, 27
Google’s content delivery network (CDN), 11 GutHub, 77
HTML validation, 119
item property, in shopping cart example, 7
J
model properties, binding elements to, 16 model variables, 12
organizing dependencies with, 33 working example of, 91, 92, 94 ng-show, 23, 92
recipe management applications, 77
single-page applications, Angular vs. other apps, singleton objects, 352
text, displaying/updating of, 15
user input, validation of, 45, 94 username requirement, enforcing, 95
changing with routes and $location, 38–41 creation of, 12
XSRF (Cross-Site Request Forgery) attacks, 116
Y
Yeoman, 64 overview of, 47
starting web servers in, 51
Index | 183