Breeze Angular Service

Breeze and Angular work well together. They work better together when you configure Breeze to use Angular promises ($q) and Angular's ajax component ($http). The Breeze Angular Service performs this configuration for you in an "ngNatural" way.

Please keep in mind that Breeze Angular Service is a Breeze Lab library and not part of core Breeze.

We think it is important and we're committed to making your Breeze + Angular development a first class experience. But this particular implementation may change and it be replaced by something better or something in core Breeze itself.

Breeze+Angular is great for modern browsers (ECMAScript 5+) that support Object.defineProperty. If your app must run in IE8 or earlier, the Breeze/Angular combination is not for you. You might consider Breeze+Durandal (Knockout).

How to install it

  1. acquire the Breeze Angular Service JavaScript file (breeze.angular.js) as described below.

  2. include it among your scripts after you load breeze.

  3. add "breeze.angular" to your application module's list of dependencies

  4. let any one of you app components depend upon the "breeze" service before invoking a breeze feature; such a dependence declaration triggers configuration.

You no longer need the Q.js script which is installed by the 'breeze.client' package. You should remove it from your index.html and you can delete that script file as well.

Where to get the Breeze Angular Service

You can download the raw JavaScript file from github.

Visual Studio users can install it with NuGet: Install-Package Breeze.Angular

The nuget package depends on breeze.client and the breeze.angular.directives package which provides the "zValidate" validation directive. You may be able to install everything you need for Breeze+Angular client development with this one package.

A bower and npm package are on the way.

Examples

Example #1: Configure when your application boots

var app = angular.module('app', [
    // ... other dependencies ...
    'breeze.angular' // the breeze service module
]);

// Ensure that breeze is minimally configured by loading it when the app runs
app.run(['breeze', function (breeze) { }]); // doing nothing at the moment

Example #2: Configure upon your first use of Breeze

var app = angular.module('app', [
    // ... other dependencies ...
    'breeze.angular' // the breeze service module
]);

// Any first use of breeze would likely involve the breeze.EntityManager.
// Apps often combine EntityManager and breeze configuration in a "factory".
// This 'entityManagerFactory' creates a new EntityManager
// configured for a specific remote service.
angular.module('app')
       .factory('entityManagerFactory', ['breeze', emFactory]);

function emFactory(breeze) {
  // Convert properties between server-side PascalCase and client-side camelCase
  breeze.NamingConvention.camelCase.setAsDefault();

  // Identify the endpoint for the remote data service
  var serviceRoot = window.location.protocol + '//' + window.location.host + '/';
  var serviceName = serviceRoot + 'breeze/breeze'; // breeze Web API controller

  // the "factory" services exposes two members
  var factory = {
    newManager: function() {return new breeze.EntityManager(serviceName);},
    serviceName: serviceName
  };

  return factory;
}

The Breeze Angular Service is not clairvoyant. It can't configure Breeze for everything your app requires. The second example illustrates configuration of the NamingConvention and the remote service endpoint (the serviceName), both specific to your application.

The Breeze service instance

The 'breeze' service that Angular injects is Breeze itself, identical to window.breeze. Whether you use that service object or refer to the global breeze object is a matter of style.

In a future version, we are likely to refactor Breeze into more granular services according to "the Angular way". For now, the "Breeze Angular Service" simply configures Breeze to use

  • Angular for data binding
  • the $q service for promises
  • the $http service for ajax calls

The balance of this documentation provides more details about promises and the ajax service.

Promises

A promise is a pledge to tell you when an asynchronous activity completes.

Breeze and Angular rely on promises to manage chaining of asynchronous method calls such as a sequence of data queries.

Every Breeze async method returns a promise object. Initially the asynchronous activity is incomplete and the promise object is "unfullfilled". Your code continues without knowing the outcome of that activity. The promise object has a then() method. You supply success and failure callbacks as parameters to the then(). When the asynchronous activity completes, the promise is "fullfilled" and it invokes either your success or your failure callback as appropriate.

Read more about "Thenable Promises" from the author of the Q.js library, Kris Kowal. The Angular $q implementation adheres to his description in all essential respects.

Breeze promises

Out of the box, a Breeze asynchronous method returns a Q.js promise, not an AngularJS $q promise. Breeze also assumes that you included the Q.js library in your client stack.

While the Q.js default makes good sense in other environments, it is not Angular friendly. First you have to load the Q library. Then you'll find that you often have to convert a Q promise to a $q promise because many Angular components don't understand a Q promise. Because the Angular dirty-checking "Digest" cycle knows nothing of Q, you'll probably have to call $scope.$apply. Finally, it's extremely difficult to test with ng-mocks when you have a mix of $q and Q promises.

Angular developers should switch to $q promises and this Breeze Angular Service does that for you automatically.

$q usage

There's nothing to it. Breeze async methods now return Angular $q promises. Append promise callbacks to those promises per the $q API.

var promise = entityManager
       .executeQuery(query)
       .then(successCallback, failCallback); // not preferred; see next.

exceptions

What if one of the callbacks throws an exception? Per the specification, if either the successCallback or failCallback throws an exception, the promise returned from then(...) is rejected. Don't expect a failed successCallback to propagate its error to the sibling failCallback.

Because the successCallback is often fragile, especially in tests, we often move the failCallback to a separate then(null, failCallback) so that it can catch failures either of the original promise or of the "success path" promise.

The $q promise sports a "sugar" method, .catch(failCallback), which is the same as .then(null, failCallback).

You may also need cleanup logic that should run whether the original promise succeeds or fails.

While you could append .then(wrapUp, wrapUp), we prefer another bit of sugar, .finally(wrapUp).

Putting these thoughts together we might write something like this:

var promise = entityManager
       .executeQuery(query)
       .then(successCallback)
       .catch(failCallback) 
       .finally(wrapUp); 

We encourage you to review the $q promises documentation for details.

AJAX

The Breeze EntityManager makes HTTP calls to a remote server via an "ajax" adapter. While Breeze ships with both a 'jQuery' and an 'angular' ajax adapter, it defaults to the 'jQuery' adapter which wraps jquery.ajax. This Breeze Angular Service re-configures breeze to use the 'angular' adapter which wraps $http, ensuring that Breeze receives the specific $http service instance that Angular injects into your app module.

Speaking of service instances ...

Multiple $q and $http instances

There is a nuance you may discover in extraordinary circumstances: Angular creates a new $q and a new $http for each application module.

Rare is the application that has multiple app modules. But if you did have multiple app modules, each would have its own $q and $http instance.

Breeze expects exactly one promise and ajax implementation across the entire application. That might become a problem if you toggled between multiple app modules. You could workaround it by switching the Breeze promise and ajax implementations every time you switch app modules. The specifics of this technique are beyond the scope of this topic.

You're more likely to become aware of multiple $q and $http instances during testing. In fact, you can count on getting a new instance for each and every test (known as a "spec" in Jasmine).

Fortunately, you get a new instance of the app module too. So when your app module and services load under test, they create a fresh instance of the Breeze Angular service at the same time ... and that new instance will configure Breeze with the current $q and $http services for each executing test (or "spec").

Here's an example of a Jasmine "beforeEach" test setup:

beforeEach(function () {
    module('app'); // new instance of the 'app' module
    inject(function(breeze){ }); // just to be sure.
})

Deprecating breeze.angular.q and other configurations

This library replaces our previously recommended Breeze Labsbreeze.angular.q.js.

As noted earlier, we are deprecating breeze.angular.q.js and intend to remove it from Breeze Labs in the first quarter of 2014. We encourage you to transition to the Breeze Angular Service library as soon as possible.

Migration is pretty painless.

  1. Remove the breeze.angular.q.js script from your project.

    Uninstall-Package Breeze.Angular.Q if you used NuGet.

  2. Install breeze.angular.js as explained above.

  3. Update your index.html, changing breeze.angular.q.js to breeze.angular.js.

  4. Update your app module to depend on "breeze.angular".

  5. Find the one place in your code where you call "use$q" and replace it with the "breeze" dependency.

For example, you might go from this:

var app = angular.module('app', [
   // ... other dependencies ...
   'breeze.angular.q' // tells breeze to use $q instead of Q.js
]);

app.run(['$q','use$q', function ($q, use$q) {
       use$q($q);
}]);

to this:

var app = angular.module('app', [
   // ... other dependencies ...
   'breeze.angular'
]);

app.run(['breeze', function () { }]);

You should also track down and eliminate code that configures Breeze to use the "backingStore" model library adapter and $http. For example, you could go from this:

function configBreeze($q, $http, use$q) {
    // use $q for promises
    use$q($q);

    // use the current module's $http for ajax calls
    var ajax = breeze.config.initializeAdapterInstance('ajax', 'angular');
    ajax.setHttp($http);

    // the native Breeze 'backingStore' works for Angular
    breeze.config.initializeAdapterInstance('modelLibrary', 'backingStore', true);

    breeze.NamingConvention.camelCase.setAsDefault();
}

to this:

function configBreeze() {
    breeze.NamingConvention.camelCase.setAsDefault();
}

You might not need a configBreeze method at all!