better odata handing in ui5 using promises part 2
Introduction
In part 1 we made use of promises and made use of promise encapsulation. This is a great start to making use of promises and makes for a much nicer experience in our code and gives us more control over what we do and when.
However I want to take that a step further and make a class (sorta, not a 'real' class like in Java) and use that for all of our oData calls and never see the callback again.
odataAPI.js
To do this we need to make a new file, I've decided for now to call this "odataAPI" and this file is going to take in a parameter of our oData service and we're going to essentially implement our CRUD calls and encapsulate them inside of a promise.
So rather than needing to define a function like we did in our previous post such as 'returnToDoItems" and then call that function when needed we will be able to make use of promises inline our functions and make everything cleaner and hopefully simpler.
I quite like this approach compared to my promise encapsulation method because it allows me to stop jumping between functions too much when it's unnecessary.
sap.ui.define([
"sap/ui/base/Object"
], function (Object) {
"use strict";
return Object.extend("com.nathan.hand.training.model.odataAPI", {
constructor: function (model) {
this.model = model;
},
returnData: function(resolve, reject, mParameters){
return {
parameters : !!mParameters ? mParameters.parameters : '',
filters: !!mParameters ? mParameters.filters : '',
sorters: !!mParameters ? mParameters.sorters : '',
success: function(data) {
resolve(data);
},
error: function(oError) {
reject(oError);
}
};
},
create: function(sPath, payload){
return new Promise(function(resolve, reject){
this.model.create(sPath, payload, this.returnData(resolve, reject));
}.bind(this));
},
read: function(sPath, mParameters) {
return new Promise(function (resolve, reject) {
this.model.read(sPath, this.returnData(resolve, reject, mParameters));
}.bind(this));
},
update: function(sPath, payload){
return new Promise(function(resolve, reject){
this.model.update(sPath, payload, this.returnData(resolve, reject));
}.bind(this));
},
delete: function(sPath){
return new Promise(function(resolve, reject){
this.model.remove(sPath, this.returnData(resolve, reject));
}.bind(this));
}
});
});
Understanding odataAPI.js
odataAPI.js is essentially a wrapper on top of our oData model where we've re-implemented CRUD but encapsulating each of the calls inside of a promise.
In UI5 to make a "class" we need to extend the base object found at 'sap.ui.base.object' which you can see defined at the top, and as this is a class we have a 'constructor' method which is exactly as you'd think about in other OO languages for defining properties.
Our constructor is super simple and we will be instantiating our oDataAPI class with our oData model which I go into below.
Otherwise the class simply encapsulates our oData calls inside of a promise and returns that promises results in either a resolve or reject method.
Using our new class
First of all we need to import our new file at the top of our controller, this would look something like the following:
sap.ui.define([
"sap/ui/core/mvc/Controller",
"com/nathan/hand/training/model/odataAPI"
], function(Controller, odataAPI) {
"use strict";
...
where you would replace 'com/nathan/hand/training' with your applications namespace, make note of the fact that I placed this inside of my 'model' folder which you might also change but seen as we're essentially re-writing our oData model I don't see a problem with it living in that folder.
Second of all we need to instantiate our new class with our oData model, this looks like the below:
new odataAPI(this.getModel("myService"));
The above works where 'myService' is the oData service defined in my manifest.json so yours might vary slightly. Taking this further I actually placed the following in my baseController:
getOdataModel: function () {
if (!this._odata) {
this._odata = new odataAPI(this.getModel("myService"));
}
return this._odata
},
^^The above is a rather lazy way of checking if we've created our class yet, similar to what we usually see in defining dialog fragments.
This allows us to do something like the following:
var createRequest = this.getOdataModel().create("/ToDoListSet", payload);
where createRequest will be a promise and we can interact with the response using the standard .then() and .catch() methods.
The same applies to all of the other methods .read, update and .delete (changed from .remove because I like this name better).
One small difference to note is that I have to pass in my sorters, filters or url parameters using a parameter object which would look like the following:
var parameters = {
filters : myFilters,
sorters : [],
parameters : {expand : "items"}
};
var readRequest = this.getOdataModel().read("/ToDoListSet", parameters);
conclusion
In this post we covered the creation of a class to take encapsulating our oData calls to the next level and making a completely reusable solution. This odataAPI.js file is very basic for this blog and doesn't take into account every possible scenario that's possible when using the oData model but It covers most of the options and you could always tweak it should you have more complex requirements.
In the next post in this series I will go over the use of promise.all, how to use it and why it's important in the context of our ui5 applications. Follow me on Twitter where I'll be sure to post when I make the next blog.
UPDATE:
Link to Part 3
···