Integrating AngularJS with Azure Active Directory Services and Office 365/SharePoint, Part 6

Building the Client with AngularJS

Dan Wahlin

by Dan Wahlin on 4/16/2015

Share this:
Print

Article Details

Date Revised:
4/17/2015

Applies to:
AngularJS, CORS, javascript, Office 365, RESTful API, SharePoint, SharePoint API, Single Page Application (SPA) framework


Up to this point in the article series you’ve seen an overview of the Expense Manager application and the technologies it relies on, learned how to register an application with Azure Active Directory (AAD), seen the necessary NuGet assemblies that are required, walked-through code to authenticate users into AAD, and seen how code can handle working with different types of tokens. If you missed any of these topics and want more information, here’s a complete list of the previous articles:

Integrating AngularJS with Azure Active Directory and Office 365/SharePoint Series

  1. Application and technology overview
  2. Registering a custom app with AAD
  3. Adding AAD configuration and assemblies into an application
  4. Adding AAD and OWIN code to handle user authentication
  5. Communicating with Office 365/SharePoint APIs through an HttpHandler
  6. Building the Client with AngularJS (this article)

In this final article in the series, we’re going to switch gears and move from the server down to the client. Now that all of the proper configuration is in place in Azure and the server-side code is ready to go, we can use AngularJS on the client side to communicate with Office 365 and retrieve data. Let’s get started by taking a quick look at what AngularJS is and what it offers.

Quick introduction to AngularJS

AngularJS is a Single Page Application (SPA) framework that can be used to build robust and flexible client-centric applications. SPAs are HTML/JavaScript/CSS applications capable of running in the browser as well as on phones, tablets and other devices. I like to think of them as applications that have a “desktop-like feel” but with a Web deployment model.

SPA screens (called “views”) load quickly since the entire page isn’t reloaded as the user interacts with various elements on the page. When a user navigates from one view to another, only the new view changes in the overall page. The main page stays in place (headers, footers, menus, etc.), which saves a lot of bandwidth and makes applications more “snappy” from an end-user perspective. This allows SPAs to have that “desktop-like feel” which works well in many scenarios such as Line of Business (LOB) applications.

Although I won’t be able to cover all of the ins and outs of AngularJS and SPAs in this article, I created a video titled AngularJS Fundamentals in 60-ish Minutes that covers all of the key concepts that you’d need to know to get started using it. The goal of this article is to provide an overview of some of the key components available in AngularJS and show how they can be used to communicate with Office 365 services.

Figure 1 shows a diagram of the key components/building blocks that are available in AngularJS.

Key components/building blocks available in AngularJS

Figure 1. AngularJS Key Components

The Expense Manager application discussed throughout this article series (refer to Part 1 for an overview of the application and some of the screens it provides) uses the following AngularJS components:

  1. One of more AngularJS modules can be defined in an application. Modules act as containers for other components (think of them as being similar to namespaces in .NET). The Expense Manager application registers an AngularJS module named expenseApp.
  2. Application factories are used to communicate with Office 365 using Ajax/XHR requests.
  3. Application controllers call into factories to retrieve data and then place it in a special object called $scope. Controllers act as the “brain” for application screens which are referred to as “Views.They can perform business rules, retrieve data from other components, read and write to/from the $scope object, plus more.
  4. The $scope (often called a ViewModel) contains all of the data that is needed by a given view.
  5. Application views bind to the $scope that they’re passed from a controller. They do this by using specialized components in the views referred to as “Directives.”
  6. Directives teach HTML new tricks. Think of them as custom tags that you can add into your HTML to perform features like iterating through and writing out data, displaying a calendar, rendering a grid, handling different events, and much more.

While there’s much more to AngularJS than can be covered in this article, the video mentioned earlier covers all of the key concepts if you’re interested in diving deeper into the framework. You’ll also find many more details on http://angularjs.org, on my blog, and in an AngularJS FlipBoard Magazine I curate that focuses 100% on AngularJS articles, blog posts, and code. In the remainder of this article you’ll see how several of the AngularJS components are used in the Expense Manager application.

Application modules, routes and factories

AngularJS relies on modules to organize code within an application. You can think of a module as being somewhat analogous to a namespace in the .NET world. The module for the Expense Manager application is named expenseApp and is defined in app/expenseApp/app.js. In addition to creating the module, the file also defines several routes as well that determine which views and controllers to use as users interact with the application. Listing 1 shows the app.js code.

 (function () {

    var app = angular.module('expenseApp',
        ['ngRoute', 'ngAnimate', 'wc.directives', 'ui.bootstrap']);

    app.config(['$routeProvider', function ($routeProvider) {
        var viewBase = '/app/expenseApp/views/';

        $routeProvider
            .when('/employees', {
                controller: 'EmployeesController',
                templateUrl: viewBase + 'employees/employees.html',
                controllerAs: 'vm'
            })
            .when('/employeeExpenses/:employeeId', {
                controller: 'EmployeeExpensesController',
                templateUrl: viewBase + 'employees/employeeExpenses.html',
                controllerAs: 'vm'
            })
            .when('/employeeEdit/:employeeId', {
                controller: 'EmployeeEditController',
                templateUrl: viewBase + 'employees/employeeEdit.html',
                controllerAs: 'vm'
            })
            .when('/expenses', {
                controller: 'ExpensesController',
                templateUrl: viewBase + 'expenses/expenses.html',
                controllerAs: 'vm'
            })
            .when('/about', {
                controller: 'AboutController',
                templateUrl: viewBase + 'about.html'
            })
            .otherwise({ redirectTo: '/employees' });
    }]);

}());

Listing 1. Creating a module and defining routes.

Now that the expenseApp module has been defined, an AngularJS factory can be created to handle interacting with Office 365 services. AngularJS factories provide a great place to put reusable code that may be shared across controllers and other parts of an application. The Expense Manager application uses a factory named employeesSharePointService.js to communicate with Office 365 services using an Ajax/XHR object built-into AngularJS named $http. By putting this type of code in one place we can simplify maintenance and get excellent re-use out it.

Note: You may wonder why employeesSharePointService.js is referred to as a “factory” but has the word “service” in its file name and AngularJS name. I and many others in the AngularJS world like to use the word “service” instead of “factory” when naming factory files and adding them into AngularJS modules. AngularJS factories and services look slightly different from a code syntax perspective but ultimately accomplish the same end goal. By consistently naming a factory as “xxxService” and a service as “xxxService” it makes code more predictable and easier to work. I’ll still refer to it as a factory throughout the article (since it is) but from a naming standpoint the term “service” is quite common.

Listing 2 shows the shell code for the employees factory. Looking through the code you’ll see that it includes an outer wrapper function to pull variables and functions out of the global scope. It also defines a method named employeesFactory that is registered with AngularJS factory() function. This function returns an object literal named factory.

 (function () {

    var employeesFactory = function ($http, $q, $window, $location, $timeout) {
        var serviceBase = '/Handlers/WebProxy.ashx?url=',
            refreshUrlBase = '/home/refreshtoken?returnUrl=',
            baseSPUrl = expenseManager.baseSPUrl,
            baseSPListsUrl = baseSPUrl + 'web/lists/';

        factory = {
            itemCount: 0,
            expenses: null
        },

        //functions go here

        return factory;
    };

    employeesFactory.$inject = ['$http', '$q', '$window', '$location', '$timeout'];

    angular.module('expenseApp').factory('employeesSharePointService',
        employeesFactory);

}());

Listing 2. The shell code for the employees factory.

This code locates the expenseApp module shown in Listing 1 and then adds the factory into it. As a review, notice that the name added into the module is “employeesSharePointService” for the reasons outlined earlier.

Several dependencies that the factory has are also defined such as $http, $q, $window, $location and $timeout. These dependencies will be injected dynamically into the factory at runtime by AngularJS. One of the key dependencies is $http since it’s used to make calls to Office 365.

Now that the factory object is defined and returned from within the employeesFactory() method, additional code needs to be added to officially call Office 365 services. As mentioned earlier in the article series, the Expense Manager application was built to demonstrate how existing data in SharePoint lists can be pulled into an application. The $http object injected into the factory can be used to retrieve data from SharePoint lists and even manipulate them.

Listing 3 shows a function named getEmployeesSummary() that is responsible for getting the data that’s displayed when the application first loads (see Figure 2).

factory.getEmployeesSummary = function (pageIndex, pageSize) {
    var url = serviceBase + encodeURIComponent(baseSPListsUrl + 
         "getByTitle('Employees')/items?$select=ID," + 
         "FirstName,LastName,Address,City,State,Zip,Email,Gender" +
         "&$orderby=LastName,FirstName");
    return getPagedResource(url, pageIndex, pageSize);
};

function getPagedResource(baseResource, pageIndex, pageSize) {
    var url = baseResource;

    //Server-side paging not currently implemented due to lack of paging support
    //in SharePoint OData/REST api so doing it here.
    var deferred = $q.defer();
    var countPromise = $http.get(serviceBase + encodeURIComponent(baseSPListsUrl +
       "getByTitle('Employees')/itemcount"));
    var empPromise = $http.get(url);

    $q.all([countPromise, empPromise])
        .then(function (results) {
            //Get countPromise data
            var custCount = (results[0].data.d) ? results[0].data.d.ItemCount : 0;
            var custs = (results[1].data.d) ? caseProps(results[1].data.d.results,
                propStyleEnum.camelCase) : []; //Get empPromise data

            //extendEmployees(custs);
            var custData = {
                totalRecords: custCount,
                results: custs
            };

            deferred.resolve(custData);
        },

        function (error) {
            if (error.status === 302) {
                deferred.resolve(null);
                $window.location.href = getRedirectUrl();
            }
        });

    return deferred.promise; //Return promise to caller
}

Listing 3. Retrieving employee data from Office 365 using AngularJS and $http.

Employee data returned by the getEmployeesSummary() function within the AngularJS factory

Figure 2. An example of the employee data returned by the getEmployeesSummary() function within the AngularJS factory.

This function first creates the URL that’s used to call into the Office 365/SharePoint RESTful API. The URL is specific to Office 365 SharePoint APIs and defines the resource to retrieve using getByTitle(‘Employees’). It also defines the specific columns to return from the list using $select and the way the data should be sorted by using $orderby.

It then passes the URL to a function named getPagedResource() that handles calling into Office 365 and retrieving the data. The getPagedResource() function handles getting the employee count as well as the actual employee data using the AngularJS $http object’s get() function. Promises are used throughout the code to handle resolving the asynchronous data calls. While Listing 3 shows only a small portion of the factory, you can view the complete code here.

If you looked closely at the code from the previous two listings you may notice that the $http object isn’t calling directly to Office 365 APIs. Instead, it’s calling an HttpHandler back on the server (discussed in a previous article). Why is that? While Office 365 recently added support for cross-domain calls using Cross-Domain Resource Sharing (CORS), that feature wasn’t available when the Expense Manager application was written so any calls directly from the browser to Office 365 will fail due to browser security restrictions. That’s why the HttpHandler proxy was created and why the $http object calls into it. The HttpHandler forwards the call to Office 365 and everything works as desired.

Note: If you’re interested in a modified version of the application that leverages the new Office 365 CORS functionality (and integrates OneDrive for Business) visit this Github repository.

While we’ve only scratched the surface of the employeesSharePointService.js file you can view the complete code here. Now that the factory has been introduced, it’s time to move on to controllers and views.

Controllers and views

The employees factory discussed previously knows how to retrieve employee and expense data from the Office 365 but it doesn’t know anything about converting the data to HTML. That’s where controllers and views come into play.

Controllers act as the “brain” for a view in an AngularJS application. They know how to get data either directly or by calling a factory/service, can handle data validation, include custom business rules and perform many other tasks related to a view.

To render the homepage of the application shown earlier in Figure 2, a controller named employeesController.js was created. It’s responsible for retrieving data from the factory, storing the data internally and handling any user interactions that occur in the view. Listing 4 shows the shell code for the controller.

(function () {

    var EmployeesController = function ($location, $filter, $window, $timeout,
       dataService, modalService) {
        var vm = this;

        //functions go here

    };

    EmployeesController.$inject = ['$location', '$filter', '$window', '$timeout',
       'dataService', 'modalService'];

    angular.module('expenseApp').controller('EmployeesController',
      EmployeesController);

}());

Listing 4. Shell code for the employees controller.

The controller relies on several external objects that are injected into it at runtime by AngularJS including one called dataService. The dataService object wraps the employees factory shown earlier in Listing 2 (a discussion for a different article, but in a nutshell it abstracts the service from the controller so that it can easily be changed out if needed). In addition to the injected objects, you’ll also notice that “this” is assigned to a variable named vm. The controller is using a technique referred to as “controller as” that is used to abstract the $scope (the view’s data or ViewModel) and allow it to be accessed through the JavaScript “this” keyword. You can find more details on “controller as” here if interested.

Listing 5 shows an example of the controller making a call to the factory to retrieve employee data. You can view the complete code for the controller here.

function getEmployeesSummary() {
    dataService.getEmployeesSummary(vm.currentPage - 1, vm.pageSize)
    .then(function (data) {
        vm.totalRecords = data.totalRecords;
        vm.employees = data.results;
        filterEmployees(''); //Trigger initial filter

        $timeout(function() {
            vm.cardAnimationClass = ''; //Turn off animation
        }, 1000);

    }, function (error) {
        $window.alert('Sorry, an error occurred: ' + error.data.message);
    });
}

Listing 5. Calling into a factory from a controller.

The getEmployeesSummary() function is called when the homepage view is first loaded. It then calls into the employees factory’s getEmployeesSummary() shown earlier in Listing 3 to retrieve the employee data from Office 365 services. When the call returns, the data is added into a property named employees which is bound into the view using AngularJS data binding techniques.

Listing 6 shows a portion of the homepage view (named customers.html) that handles binding the employee data and displaying it.

<div class="row cardContainer show-hide-animation"
     data-ng-hide="vm.listDisplayModeEnabled">
    <div class="col-sm-6 col-md-4 col-lg-3" data-ng-class="cardAnimationClass"
            data-ng-repeat="employee in vm.pagedEmployees | orderBy:'lastName'">
        <div class="card">
            <button class="btn close cardClose" title="Delete Employee"
              data-ng-click="vm.deleteEmployee(employee.id)">×</button>
            <div class="cardHeader"><a href="#/employeeEdit/{{employee.id}}"
                class="white">{{employee.firstName + ' ' + employee.lastName}}
                <i class="icon-edit icon-white editIcon"></i></a></div>
            <div class="cardBody">
                   <!-- remaining data binding code goes here -->
            </div>
        </div>
    </div>
</div>

Listing 6. Iterating through employee data and binding it using directives and binding expressions.

This code relies on a directive built-into AngularJS named ng-repeat to iterate through the employee data:

<div class="col-sm-6 col-md-4 col-lg-3" data-ng-class="cardAnimationClass"
         data-ng-repeat="employee in vm.pagedEmployees | orderBy:'lastName'">

The ng-repeat directive loops through a property called pagedEmployees that was created in the controller and places each employee into a variable named employee. It then orders the employees by their lastName property.

As each employee object is encountered during the looping process, it’s individual information is written out into the view using data bind expression syntax that looks like the following:

{{ propertyName }}

Any time AngularJS encounters data binding expressions it handles looking up the property in the controller (which is technically stored in a $scope object behind the scenes) and then updating the DOM with the value in the appropriate location. You can see the following data binding expressions in the code shown in Listing 6 are used to write out the employee’s first name and last name.

{{employee.firstName + ' ' + employee.lastName}}

You can view the complete code for the customers.html view here. The application has several other controllers and views as well to handle editing employee information, displaying expense data and more. You can find the complete application at https://github.com/OfficeDev/SP-AngularJS-ExpenseManager-Code-Sample.

Conclusion

The Expense Manager application contains more AngularJS code than I can cover in this article but you’ve now seen several of the key components that can be used to build a Single Page Application (SPA) that integrates with AAD and Office 365 APIs. You can use factories or services for reuseable code, controllers act as the “brain” for views, and a view relies on directives to manipulate and update the DOM. The end result of these components working together is an application that responds quickly to user input in the browser or on other devices.

That’s a wrap! I hope you’ve enjoyed this article series on integrating Azure Active Directory and Office 365 into a Web/SPA application and I appreciate that you took time to read through the articles.


Topic: Development

Sign in with

Or register