Skip to Content

Introduction

In complex UI5 projects you won’t be able to do everything by using bindings. You will probably need to do some OData requests manually in JavaScript. This can be due to several reasons, like in case you want to do a deep create (which is not supported with “createEntry”; https://ui5.sap.com/#/api/sap.ui.model.odata.v2.ODataModel/methods/createBindingContext ) or you want to manipulate data before showing. No matter what reason, you can’t avoid this 😊

For these manual OData requests, we can use the default functions from the OData model in UI5 (nothing new here):

  • Read: for fetching data from the backend
  • Create: for sending new data to the backend
  • Update: for updating data on the backend
  • Remove: for deleting data on the backend

You can find all the details of these functions on the UI5 API Reference: https://ui5.sap.com/#/api/sap.ui.model.odata.v2.ODataModel/constructor

If you look at these functions, you’ll notice that they use a success and error callback functions to get a response from the backend. Nothing wrong with these functions, but I prefer working with promises… (read more about promises here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise )

For that reason, I’ve created a wrapper that uses the OData model with Promises. I call it my “CoreService” object 😊

OData with Promises

The “CoreService” object is based on the example from MDN web docs on using XMLHttpRequest:

http://www-lia.deis.unibo.it/materiale/JS/developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise.html

( I don’t think that’s the original url, but I was not able to find it here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise#Example_using_new_XMLHttpRequest()  )

It exists out of two parts:

  • One part for normal HTTP calls (that I copied from MDN)
  • Another part for OData calls

I’m going to focus on the “odata” function. The “odata” function in the “CoreService” returns a function for each CRUD operation which calls the internal “core.ajax” function. Each CRUD operation will call the “core.ajax” function but will use other parameters. What do the argument mean?

  • Type:
    • This defines the function that will be used in the UI5 ODataModel object (read,update,create or remove)
    • Will be different for each CRUD operation
  • url:
    • Url to the entity or entityset
  • Data:
    • JS object that needs to be send to the backend.
    • Will only be used for “post” and “put”
  • Parameters:

The “core.ajax” function will return a promise object that will be resolved in the success function and rejected in the error function.

The full function

odata: function (url) {
	var me = this;
	var core = {
		ajax: function (type, url, data, parameters) {
			var promise = new Promise(function (resolve, reject) {
				var args = [];
				var params = {};
				args.push(url);
				if (data) {
					args.push(data);
				}
				if(parameters){
					params = parameters;
				}
				params.success = function (result, response) {
					resolve({
						data: result,
						response: response
					});
				};
				params.error = function (error) {
					reject(error);
				};
				args.push(params);
				me.model[type].apply(me.model, args);
			});
			return promise;
		}
	};

	return {
		'get': function (params) {
			return core.ajax('read', url, false, params);
		},
		'post': function (data, params) {
			return core.ajax('create', url, data, params);
		},
		'put': function (data, params) {
			return core.ajax('update', url, data, params);
		},
		'delete': function (params) {
			return core.ajax('remove', url, false, params);
		}
	};
}


The full “CoreService” object:
https://github.com/lemaiwo/PromiseDemo/blob/master/webapp/service/CoreService.js

 

How to use it

I would suggest to not use this “CoreService” object directly. What I mostly do is, I extend the “CoreService” object with a more precise object with a more meaningful name and only related functions in it. Let me explain this by applying on the Northwind Service as an example.

Create a second object that extends from “CoreService” (assuming CoreService” is added to the project) and give it the name “NorthwindService”. Now, we can use the “NorthwindService” to create more specific functions that apply on the Northwind service. For example, we can add a function “getSuppliers”. This function will call the get function of the “odata” function in the “CoreService” object for the entity “/Suppliers”, like this.

sap.ui.define([
	"./CoreService",
	"sap/ui/model/Sorter"
], function (CoreService,Sorter) {
	"use strict";

	var NorthwindService = CoreService.extend("be.wl.PromisesDemo.service.NorthwindService", {
		getSuppliers: function () {
			return this.odata("/Suppliers").get();
		}
	});
	return new NorthwindService();
});


We can also use predefined filters, like this:

Also look at the last line: “return new NorthwindService()” . This allows us to access one and the same instance of the “NorthwindService” from everywhere in the project.

getSuppliersWithFilter: function (aFilters) {
	var mParameters = {
		filters:aFilters
	};
	return this.odata("/Suppliers").get(mParameters);
},


Full code with more examples with filter, parameters and a create:
https://github.com/lemaiwo/PromiseDemo/blob/master/webapp/service/NorthwindService.js
Later in this blog I have an example wilt a sorter and top.

 

One more thing before we can use the “NortwindService” in the project. We need to register the ODataModel to the “NortwindService”. This is something I do in the component:

NorthwindService.setModel(this.getModel());


Just call the “getSuppliers” function and fill the model with the response in the “then” function. You probably think why I don’t just use the OData model read function. It will show it’s benefit in a more complex example. Take for example that we want to do multiple requests in sequence like this:

Now, we’re ready to use the “NorthwindService” object in our project. I can for example use this in my Main controller like this:

  • Check if the supplier with ID 20 exists
    • This could fail but should not block the next request. For that reason, I’ve added a catch function after it.
  • Find out how many suppliers exists in the city “Redmond”
  • And finally get all the suppliers

After each request I show a message and update a progress bar to show the progress to the end-user:

It’s not the best example but it show how promises work. In this case you could also do this with Promise.All to run all request at the same time in a batch request, find more information about Promise.all here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all

A more realistic example would be in case you create a new Supplier. Then we would need to know the next available ID. After creating the object, we should also update the list. This means we would have requests that need to wait for each other like this:

  • Get Max ID
  • Create new Supplier
  • Refresh supplier list

In code, it looks like this:

It could happen that the create fails, therefore I put a catch after the second then. In case the create fails, it will skip the second then and directly go to the catch. Everything after the first catch, will still be executed and update my list.

The function “getSupplierNextID” will get the Supplier with the highest ID by doing a $top = 1 and $orderby = ID in the OData request. In the result of the promise, it will add 1 to the highest found ID. We can assume that this ID will be unique. The function looks like this:

Example

I have a full example of the code on GitHub: https://github.com/lemaiwo/PromiseDemo

You can import this in the SAP Web IDE and you’ll be able to try it out.

The result should look like this:

With the “add” button, you can generate a new Supplier.

 

Conclusion

I think this can help you managing your OData calls to the backend in complex and big projects.

It’s not that complex but very useful. I use it in many of my project and hope it can help others too.

To report this post you need to login first.

2 Comments

You must be Logged on to comment or reply to a post.

  1. Nabheet Madan

    Very nice and useful!  CoreService is a cool name resonating with CDS:)

    For that reason, I’ve created a wrapper that uses the OData model with Promises. I call it my “CoreService” object 😊

    (1) 
  2. Dominique Pierre

    Hi Wouter,

    Interesting approach, I also use promises but I usually just encapsulate the code in a promise:

    return new Promise(function(resolve, reject) {
    	oModel.read(sPath, {
    		success: resolve,
    		error: function(oError) {
    			reject(oError);
    		}
    	});
    });

    Cheers,

    Pierre

    (3) 

Leave a Reply