Friday, 1 November 2013

Using Breeze JS to Consume ASP.NET Web API OData in an Angular JS Application

In last post, we saw how Breeze JS eases the job of querying OData services. It will be a lot of fun to use this great library with our favourite SPA framework, Angular JS. In this post, we will see how to hook up these two libraries to create data rich applications.

As stated in previous post, Breeze required data.js for to understand OData conventions. All functions performing CRUD operations in Breeze return a Q promise. Any changes made to properties of $scope inside then method of Q are not watched automatically by Angular, as 
callbacks hooked up to Q are executed in non-angular world. If the same thing can be done using $q, we don’t have to call $scope.$apply to make the changes visible to Angular’s dirty checking. For this purpose, Breeze team has created a module (use$q). This module can be installed in the project via NuGet: Breeze.Angular.Q. This package adds a JavaScript file to the application, breeze.angular.q.js. Once this file is included and the module is loaded, we don’t need q.js anymore.

Following is the list of scripts to be included on the page:

<script src="Scripts/angular.js"></script>
<script src="Scripts/datajs-1.1.1.js"></script>
<script src="Scripts/breeze.min.js"></script>
<script src="Scripts/breeze.angular.q.js"></script>

We don’t need jQuery anymore as Breeze detects presence of Angular and configures its AJAX adapter to use $http. (Check release notes of Breeze 1.4.4)

As both Angular and Breeze are JavaScript libraries, they can be easily used together. But we can’t enjoy the usage unless we follow the architectural constraints of Angular JS. The moment we include Breeze.js in an HTML page, the JS object “breeze” is available in the global scope. It can be used directly in any of the JavaScript components, Angular controllers and services are no exceptions. Best way to use an object in any Angular component is through Dependency Injection. Any global object can be made injectable by creating a value.
var app=angular.module(‘myApp’, []);
app.value(‘breeze’, breeze);
app.service(‘breezeDataSvc’, function($q, breeze){
 //logic in the service
});

We need to ask breeze to use $q as soon as the Angular JS application kicks off. For this, we need to register the following run block:
app.run(['$q','use$q', function ($q, use$q) {
    use$q($q);
}]);
Breeze has to be configured to work with OData service and use Angular’s AJAX API instead of jQuery. It is done by the following statement:
breeze.config.initializeAdapterInstances({ dataService: "OData" });

Now all we need to do is instantiate an EntityManager and start querying. Following is the complete implementation of the service including a function that does a basic OData request:
app.service('breezeDataSvc', function (breeze, $q) {
    breeze.config.initializeAdapterInstances({ dataService: "OData" });
            
    var manager = new breeze.EntityManager("/odata/");
    var entityQuery = new breeze.EntityQuery();
            
    this.basicCustomerQuery = function () {
        var deferred = $q.defer();
        manager.executeQuery(entityQuery.from("Customers").where("FirstName", "contains", "M")) .then(function (data) {
            deferred.resolve(data.results);
        }, function (error) {
            deferred.reject(error);
        });
        return deferred.promise;
    };
});

Following is a simple controller that uses the above service and sets the obtained results to a property in scope:
app.controller('SampleCtrl', function ($scope, breezeDataSvc) {
    function initialize() {
        breezeDataSvc.basicCustomerQuery().then(function (data) {
            $scope.customers = data.results;
        }, function (err) {
            alert(err.message);
        });
    };

    initialize();
});

Run this page on browser and see the behaviour.

Update: This post was updated on 11th January 2014 as per Breeze Angular Q-Promises page in documentation of Breeze

Happy coding!

8 comments:

  1. Hi Ravi, great helpfull posts. But can you explain if and how you can dynamicaly filter by country?
    If I have for instance a input with ng-model=countryFilter

    How can I get it in the where clause on the basicCustomerQuery?

    Thanx in advance

    ReplyDelete
    Replies
    1. Aarjan,

      Good question. The above function basicCustomerQuery is pretty straight forward. To make it behave dynamically, you need to pass in the field and search pattern as parameters and call the function whenever the text in the textbox changes using ng-change. Hope you get the idea.

      Delete
  2. Hi Ravi, great posts! I have just started with Breeze with Angular and I am having the issue of not being able to get at navigation properties because a) I cannot use expand because it requires datajs > 1.0.3 and b) I cannot currently use Includes in my controller as they do not seem to work (see http://stackoverflow.com/questions/20869089/breeze-navigation-properties) Have you been able to get around this issue?

    ReplyDelete
    Replies
    1. Mike,

      I tried and it worked for me. I see a typo in the snippet. I posted an answer to the question. Check the answer and comment there if it doesn't solve your issue.

      Delete
    2. Hi Ravi, Any plans to write a similar post dealing with adding/editing/deleting data using Breeze/Angular/OData?

      Delete
    3. Sure. Breeze didn't have support for CUD with OData, not sure if the current version has it. I will check it and will write if the feature is supported.

      Delete
  3. Hi, I could not get it working with some very simple web api method (without EF and its context). So I don't have metadata method aswell, and I am getting js side error after successful call to web api... it drives me crazy :-(((

    [System.Web.Mvc.HttpGet]
    [Queryable]
    public IEnumerable GetTest()
    {
    return new string[]{"aaa","bbb", "ccc"};
    }


    and

    var dataService = new breeze.DataService({
    serviceName: "http://localhost:55556/api/wizardgridapi/",
    hasServerMetadata: false,
    useJsonp: true, // request data using the JSONP protocol
    jsonResultsAdapter: jsonResultsAdapter //my custom adapter code never is hit
    });
    var manager = new breeze.EntityManager({ dataService: dataService });
    var entityQuery = new breeze.EntityQuery();

    this.basicCustomerQuery = function () {
    var deferred = $q.defer();
    manager.executeQuery(entityQuery.from("GetTest")).then(function (data) {
    deferred.resolve(data.results);
    }, function (error) {
    //always it gets here, with error.message = '; ' (!!!!!!!!!!!!!!!!!!!!!!)
    deferred.reject(error);
    });
    return deferred.promise;
    };

    ReplyDelete
    Replies
    1. Ričardas,

      I didn't try this approach before. I need sometime to give it a shot. Can you shoot me a mail using the contact form so that we can discuss about this more effectively and I can also ask what all issues you are facing?

      Delete