Over a million developers have joined DZone.

Developing with AngularJS - Part III: Services

DZone's Guide to

Developing with AngularJS - Part III: Services

· Web Dev Zone
Free Resource

Learn how to build modern digital experience apps with Crafter CMS. Download this eBook now. Brought to you in partnership with Crafter Software

AngularJS This is the 3rd article in a series on my experience developing with AngularJS. I used AngularJS for several months to create a "My Dashboard" feature for a client and learned a whole bunch of Angular goodness along the way. For previous articles, please see Part I: The Basics and Part II: Dialogs and Data.

Angular offers several ways to interact with data from the server. The easiest way is to use the $resource factory, which lets you interact with RESTful server-side data sources. When we started the My Dashboard project, we were hoping to interact with a REST API, but soon found out that it didn't have all the data we needed. Rather than loading the page and then making another request to get its data, we decided to embed the JSON in the page. For communication back to the server, we used our tried-and-true Ajax solution: DWR.

In Angular-speak, services are singletons that carry out specific tasks common to web apps. In other words, they're any $name object that can be injected into a controller or directive. However, as a Java Developer, I tend to think of services as objects that communicate with the server. Angular's documentation on Creating Services shows you various options for registering services. I used the angular.Module api method.

When I last worked on the project, there were only two services in My Dashboard: Widget and Preferences.

Widget Service

The Widget service is used to retrieve the visible widgets for the user. It has two functions that are exposed to controllers: getUserWidgets(type) and getHiddenWidgets(type). The former function is used at the top of WidgetController, while the latter is used for the configuration dialog mentioned in the previous article.

The code for this service is in services.js. The bulk of the logic is in its filterData() function, where it goes through a 4-step process:

  1. Get all the widgets by type, ensuring they're unique.
  2. Remove the widgets that are hidden by the user's preferences.
  3. Build an array that's ordered by user's preferences.
  4. Add any new widgets that aren't hidden or ordered.

The code for the Widget object is as follows:

angular.module('dashboard.services', []).
    factory('Widget',function ($filter, Preferences) {
        var filter = $filter('filter');
        var unique = $filter('unique');
        function filterData(array, query) {
            // get all possible widgets for a particular type
            var data = filter(array, query);
            data = unique(data);
            // remove widgets that are hidden by users preference
            var hidden = Preferences.getHiddenWidgets(query.type);
            for (var i = 0; i < hidden.length; i++) {
                var w = filter(data, {id: hidden[i]});
                $.each(w, function (index, item) {
                    var itemId = item.id;
                    if (hidden.indexOf(itemId) > -1) {
                        data.splice(data.indexOf(item), 1);
            // build an array that's ordered by users preference
            var ordered = [];
            var visible = Preferences.getUserWidgets(query.type);
            for (var j = 0; j < visible.length; j++) {
                var v = filter(data, {id: visible[j]});
                $.each(v, function (index, item) {
                    var itemId = item.id;
                    if (visible.indexOf(itemId) > -1) {
            // loop through data again and add any new widgets not in ordered
            $.each(data, function (index, item) {
                if (ordered.indexOf(item) === -1) {
            return ordered;
        return {
            getUserWidgets: function (type) {
                return filterData(widgetData, {type: type})
            getHiddenWidgets: function (type) {
                var hidden = Preferences.getHiddenWidgets(type);
                var widgetsForType = filter(widgetData, {type: type});
                widgetsForType = unique(widgetsForType);
                var widgets = [];
                for (var j = 0; j < hidden.length; j++) {
                    var v = filter(widgetsForType, {id: hidden[j]});
                    $.each(v, function (index, item) {
                        if (widgetsForType.indexOf(item) > -1) {
                return widgets;

Once you have a service configured like this, you can inject it by name. For example, WidgetController has Widget injected into its constructor:

function WidgetController($dialog, $scope, Widget, Preferences) {

Preferences Service

The Preferences service is used to get and save user preferences. It's pretty straightforward and the bulk of its code is interacting with DWR. This service has 5 methods:

  1. getHiddenWidgets(type) - used by Widget service
  2. getUserWidgets(type) - used by Widget service
  3. saveBarOrder(bars) - called from WidgetController
  4. saveWidgetOrder(type, widgets) - called from WidgetController
  5. saveWidgetPreferences(type, widgets) - called from WidgetController

First, let's take a look at the save*Order() functions. There are two parts of the page that use the ui-sortable directive to initialize drag-and-drop functionality. The first is on the main <ul> that holds the 3 bars on the left.

<ul class="widgets" ui-sortable="{handle:'.heading', update: updateBars}">

The "update" property in the configuration JSON indicates which method to call in the controller. Similarly, the tasks and summary items call an updateOrder function.

<ul class="summary-items" ng-model="summaryWidgets" ui-sortable="{update: updateOrder}">
<ul class="task-items" ng-model="taskWidgets" ui-sortable="{update: updateOrder}">

These functions are in WidgetController and build an array of widget ids to pass to the Preferences service.

$scope.updateBars = function(event, ui) {
    var bars = [];
    $.each($(ui.item).parent().children(), function (index, item) {
        bars.push(item.id.substring(0, item.id.indexOf('-')))
$scope.updateOrder = function(event, ui) {
    var parentId = $(ui.item).parent().parent().attr('id');
    var type = parentId.substring(0, parentId.indexOf('-'));
    var items = [];
    $.each($(ui.item).parent().children(), function (index, item) {
        items.push(item.id.substring(item.id.indexOf('-') + 1))
    Preferences.saveWidgetOrder(type, {items: items});

The bar order is used when the page is loaded. The following scriptlet code exists at the bottom of the app's page, in its $(document).ready:

<%  String barOrder = user.getDashboardBarSortOrder();
    if (barOrder != null) { %>
    sortBars(['<%= barOrder %>']);
<% } %>

The sortBars() function is in a dashboard.js file (where we put all non-Angular functions):

function sortBars(barOrder) {
    // Sort bars according to user preferences
    $.each(barOrder, function(index, item) {
        var bar = $('#' + item + '-bar');
        if (bar.index() !== index) {
            if (index === 0) {
            } else if (index === (barOrder.length - 1)) {
            } else {
                bar.insertBefore($('.widgets>li:eq(' + index + ')'));

Now that you've seen where Preferences is called from, let's take a look at the code for the service.

The checks for undefined and uniqueness in the code below shouldn't be necessary, but I prefer defensive coding.
factory('Preferences', function ($filter) {
    var unique = $filter('unique');
    return {
        // Get in-page variable: hiddenWidgets
        getHiddenWidgets: function (type) {
            var items = hiddenWidgets[type];
            return (angular.isUndefined(items) ? [] : unique(items));
        // Get in-page variable: userWidgets
        getUserWidgets: function (type) {
            var items = userWidgets[type];
            return (angular.isUndefined(items) ? [] : unique(items));
        // Save main bar (task, summary, chart) order
        saveBarOrder: function (bars) {
            DWRFacade.saveDashboardBarSortOrder(bars, {
                errorHandler: function (errorString) {
        // Save order of widgets from sortable
        saveWidgetOrder: function (type, widgets) {
            userWidgets[type] = widgets.items;
            DWRFacade.saveDashboardWidgetPreference(type, widgets, {
                errorHandler: function (errorString) {
        // Save hidden and visible (and order) widgets from config dialog
        saveWidgetPreferences: function (type, widgets) {
            // widgets is a map of hidden and visible
            var hiddenIds = [];
            $.each(widgets.hidden, function (index, item) {
            var visibleIds = [];
            $.each(widgets.items, function (index, item) {
            var preferences = {
                hidden: hiddenIds,
                items: visibleIds
            // reset local variables in page
            hiddenWidgets[type] = hiddenIds;
            userWidgets[type] = visibleIds;
            DWRFacade.saveDashboardWidgetPreference(type, preferences, {
                errorHandler: function (errorString) {

Using $http and Receiving Data

In this particular application, we didn't do any reading from the server with Angular. We simply wrote preferences to the server, and updated embedded variables when data changed. Real-time functionality of the app wouldn't be noticeable if a write failed.

In my current Angular project, it's more of a full-blown application that does as much reading as writing. For this, I've found it useful to either 1) pass in callbacks to services or 2) use Angular's event system to publish/subscribe to events.

The first method is the easiest, and likely the most familiar to JavaScript developers. For example, here's the controller code to remove a profile picture:

Profile.removePhoto($scope.user, function (data) {
    // close the dialog
    // success message using toastr: http://bit.ly/14Uisgm
    Flash.pop({type: 'success', body: 'Your profile picture was removed.'});

And the Profile.removePhoto() method:

removePhoto: function (user, callback) {
    $http.post('/profile/removePhoto', user).success(function (response) {
        return callback(response);

The second, event-driven method works equally as well, but can easily suffer from typos in event names.

// controller calling code
// service code
getUser: function () {
    $http.get('/profile').success(function (data) {
        if (data.username) {
            $log.info('Profile for ' + data.username + ' retrieved!');
            $rootScope.$broadcast('event:profile', data);
// controller receiving code
$rootScope.$on('event:profile', function (event, data) {
    $scope.user = data;

I like both methods, but the event-driven one seems like it could offer more extensibility in the future.


Using in-page variables and DWR doesn't seem to be recommended by the Angular Team. However, it worked well for us and seems like a good way to construct Angular services. Even if a REST API becomes available to get all the data, I think using in-page variables to minimize requests is a good idea.

When retrieving data, you can use callbacks or Angular's pub/sub event system ($broadcast and $on) to get data in your controllers. If you want to learn more about this technique, see Eric Terpstra's Communicating with $broadcast. In his article, Eric mentions Thomas Burleson's pub/sub module that acts as a message queue. If you've used Thomas's MessageQueue (or something similar) with Angular, I'd love to hear about your experience.

In the next article, I'll talk about how we redesigned My Dashboard and used CSS3 and JavaScript to implement new ideas.

Crafter is a modern CMS platform for building modern websites and content-rich digital experiences. Download this eBook now. Brought to you in partnership with Crafter Software.


Published at DZone with permission of Matt Raible, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.


Dev Resources & Solutions Straight to Your Inbox

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.


{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}