Introduction
In this article, we will discuss about the services in Angular. We will see how and why services are useful. We will try to use some built in services and create our own service in Angular.
Link to complete series:
- Angular Tutorial - Part 1: Introduction to Angular.js
- Angular Tutorial - Part 2: Understanding Modules and Controllers
- Angular Tutorial - Part 3: Understanding and using Directives
- Angular Tutorial - Part 4: Understanding and Implementing Filters
- Angular Tutorial - Part 5: Understanding and Implementing Services
- Angular Tutorial - Part 6: Building and Validating Data Entry Forms
- Angular Tutorial - Part 7: Understanding Single Page Applications and Angular Routing
Background
In our web application, we will always have some business logic and server communication to fetch/save the data to the server. What is the best place to do such kind of things. We could write this code in controller and have a working application but should we write this code in the controller is the question.
There are two major problems if we put our business logic and server communication in the controllers. The first problem is that this piece of functionality will not be reusable, i.e., If we want to reuse some behaviour in other controllers, we will have to rewrite this code. The second problem is from the best practices perspective. If we put all this code in the controllers, then effectively we are violating the single responsibility principle as our controller should only be responsible for populating the $scope
properties that are needed by the associated view. Also, there is no separation of concern if we put all the business logic on controller, we will end up having spaghetti code again in our controllers.
So how can we write our code in such a manner that we have a good separation of concerns, i.e., every business logic component is separate from each other and from the controller. Also, how can we use and reuse business logic components from the other parts of the application. The answer to this question is Angular services.
Angular lets us create reusable stateless components that can be used from other parts of the application. These are called services in Angular. A service is a singleton object that can be defined to contain any piece of business logic. They can then be used from other part of the application like controllers, directives, filters and perhaps other services.
So to look at the concept of the services more closely and understand the topic in a much better manner, let us introduce a service in our application. So far, we have created an application that shows a list of books on click of a button.
The controller for this app looks as follows:
(function () {
var booksController = function ($scope, $filter) {
$scope.message = "List of books";
$scope.books = [];
$scope.fetchBooks = function () {
$scope.books = [
{ ID: 1, BookName: "4 Test Books", AuthorName: "5 Test Author", ISBN: "5 TEST" },
{ ID: 2, BookName: "5 Test Books", AuthorName: "4 Test Author", ISBN: "1 TEST" },
{ ID: 3, BookName: "1 Test Books", AuthorName: "3 Test Author", ISBN: "2 TEST" },
{ ID: 4, BookName: "2 Test Books", AuthorName: "2 Test Author", ISBN: "4 TEST" },
{ ID: 5, BookName: "3 Test Books", AuthorName: "1 Test Author", ISBN: "3 TEST" }
];
}
}
angular.module('myAngularApplication').controller('booksController',
["$scope", "$filter", booksController]);
}());
What we are doing in the above controller is that we are populating the list of books with some hard coded values. But ideally, they should come from server. Let's say we want to introduce Angular service in this application. In fact, we will create two services for this application. One to get this hard coded list of sample books. The second service will fetch the similar list of books from server using a RESTful API. So let's get started.
Using the Code
Before we start creating the service for our application, let us understand a few basic things.
How to Create a Service
The first thing to remember is that there are four ways to create a service in Angular. Using any of these ways will give us a stateless singleton object that can be used across the application. Which method should be used largely depends on the pattern that we want to follow, what exactly this service is meant for and how we would want to use this service. We will not be discussing or comparing these approaches as that would be digressing from this article (perhaps a later article could cover this). In this article, we will be using the factory
method to create our service which is the Angular implementation of factory pattern to create the service object.
What we need to do to create our service class is to use the factory method on the angular module and associate the service function with it. Let's create the skeleton for localBooksService
which will return a hard coded list of books to our controller.
(function () {
var localBooksService = function () {
}
angular.module('myAngularApplication').factory('localBooksService', [localBooksService]);
}());
Now before we could go and write the internal implementation of this service, perhaps we should look at a JavaScript pattern that is used for implementation. It called the Revealing Module Pattern.
Understanding Revealing Module Pattern
From the best practices perspective (also the oops fundamentals), we should always encapsulate the internal implementation into a class and let the user see only the public abstraction. But since we are talking JavaScript here, there is no way we can put access specifier type of things in our service implementation. So how can this be achieved. This can be achieved by using a JavaScript pattern called revealing module pattern.
In this pattern, what we do is that we let the function return an object. So the caller of this function will get the instance of this object. So let's say we will return an object that will contain the list of books.
var localBooksService = function () {
return {
books: []
};
}
Right now, this is just an empty array that we are returning. How can we have the actual list of books associated. Since inside this function, we can access the internal variables and functions, why not have this list of books as a function level variable and then return it to the user.
var localBooksService = function () {
var _books = [];
return {
books: _books
};
}
On the same lines, we can also have a function defined inside which can be revealed outside using this return
object.
var localBooksService = function () {
var _books = [];
var _someFunction = function(){
console.log("This is just a sample function");
};
return {
books: _books,
someFunction: _someFunction
};
}
Implementing Our Service
Now with this revealing module pattern, we have achieved some level of encapsulation as the function will only be exposing the object that is being returned and can remain shielded from actual implementation. Let us now move the list of these hard coded books in our localBooksService
and look at the implementation of our service.
(function () {
var localBooksService = function () {
var _books = [
{ ID: 1, BookName: "4 Test Books", AuthorName: "5 Test Author", ISBN: "5 TEST" },
{ ID: 2, BookName: "5 Test Books", AuthorName: "4 Test Author", ISBN: "1 TEST" },
{ ID: 3, BookName: "1 Test Books", AuthorName: "3 Test Author", ISBN: "2 TEST" },
{ ID: 4, BookName: "2 Test Books", AuthorName: "2 Test Author", ISBN: "4 TEST" },
{ ID: 5, BookName: "3 Test Books", AuthorName: "1 Test Author", ISBN: "3 TEST" }
];
return {
books: _books
};
}
angular.module('myAngularApplication').factory('localBooksService', [localBooksService]);
}());
Using the Service
Now that we have the service ready, let's see how we can use it from our controller. The first thing that we need to do is to pass the dependency of this service on our controller in a minification safe manner (this we saw in previous article). And then, we need to use the service to get the list of books instead of hard coded books. Let us see how our controller looks after these changes.
(function () {
var booksController = function ($scope, $filter, localBooksService) {
$scope.message = "List of books";
$scope.books = [];
$scope.fetchBooks = function () {
$scope.books = localBooksService.books;
}
}
angular.module('myAngularApplication').controller('booksController',
["$scope", "$filter", "localBooksService", booksController]);
}());
Using Angular Built In Service
Angular also comes with a lot of built in services that we can use. A few of them are $location, $window, $resource
and $http
. We can look at Angular documentation to see what a specific service provides and how we can use them in our application. For this example, let us use the $http
service. What we will do is that we will implement another service called remoteBooksService
and will use this $http
service inside that to fetch the books from the server.
Note: What I have done is that I have created an ASP.NET Web API project that can be used to perform the CRUD operations on a book
database located on the server. Please find the API project attached to see the Web API details. Alternatively, we can create a simple REST based service using node or any other technology and in our Angular application, we just need to refer to the URL where that service is hosted.
Let's see how we can use the $http
service in our own service. The first thing we need to do is to inject this service in our service. Once this is injected, we can use the get
method of $http
to retrieve the result from the server. My server API is running at: http://localhost:57386/api/Books
.
(function () {
var remoteBooksService = function ($http) {
var _fetchBooks = function () {
return $http.get('http://localhost:57386/api/Books');
};
return {
fetchBooks: _fetchBooks
};
}
angular.module('myAngularApplication').factory('remoteBooksService', ["$http", remoteBooksService]);
}());
Now let's inject this service in our controller and add one more function in our controller that will use this service to get the remote list of books. $http.get
returns a promise and since we are directly returning that promise from our service, our controller needs to handle that promise and populate the list of books.
(function () {
var booksController = function ($scope, $filter, localBooksService, remoteBooksService) {
$scope.message = "List of books";
$scope.books = [];
$scope.fetchBooks = function () {
$scope.books = localBooksService.books;
}
$scope.fetchBooksFromServer = function () {
remoteBooksService.fetchBooks()
.success(function (data, status, headers, config) {
$scope.books = data;
})
.error(function (data, status, headers, config) {
$scope.books = [];
$scope.error = "Failed to retrieved items from server";
});
};
}
angular.module('myAngularApplication').controller('booksController',
["$scope", "$filter", "localBooksService", "remoteBooksService", booksController]);
}());
Now if we run the application and click on the fetch from remote, we will get the books list from the server.
Note: The above shown approach to use the $http
service is rather crude as our main focus was on looking at how we can use the service and create our own service that is using other services. The ideal solution for using $http
would involve using the $Q
defer API which I strongly recommend reading about. We will not be talking about it here as it's not in the scope of this article.
Point of Interest
In this article, we looked at the concept behind services in angular. We looked at how we can implement our own service and how we can reuse the built in Angular service by looking at an example usage of $http
service. This has been written from a beginner's perspective. I hope this has been informative.
History
- 10th July, 2015: First version