Utility files in SAPUI5

Utility files in SAPUI5
UI5 logo (Water)

Something I see a lot in UI5 applications is chunks of code being repeated either within the same application or within the same suite of applications. Using a utility file to manage this code separately from your business logic (A.K.A your controllers) means that the separation of concern is much more clear, especially for developers new to the codebase.

When I start building a new application, I don't create any utility files. I wait until my code starts to become less manageable and then I start to abstract whatever is getting in the way. One of my most common utility files is a data wrapper, which is something my friend and colleague Nathan Hand has been doing for a very long time.

Data Wrapper utility file

This utility file houses all of the oData calls for an application and wraps them in a Promise, making it much easier and more readable when you're picking through a tricky bug in your controller.

Creating a utility file

In your UI5 application, create a folder under webapp/utils and then create a JS file named dataWrapper.js.

utils folder and datawrapper file in VS Code

Now that we have our utility file, we can use the below snippet as the template for the file

sap.ui.define([], function () {
	"use strict";
	return {
		// put your data functions here
	};
});
dataWrapper.js template

Now we can work on adding our data functions, I'm going to write some examples of CRUD operations for todo items, like a todo list.

Wrapping oData in a Promise

You should be quite familiar with how to read an entity set from a service model.

var oModel = oController.getOwnerComponent().getModel("myService");
oModel.read("/ToDoListSet", {
	success: function (oData) {
		// do some stuff with your data
	},
	error: function (oError) {
		// oh no, something has broken. print an error!
	}
});
Example function to read data from an entity set using a service model

You will notice in the example above that I've only got a comment for the success and error callbacks. Typically this is where we bind a function or write some code that will handle the returning data/error. However, we're going to wrap this piece of code in a Promise and this will make a really clear distinction between our definition (where and how we get the data) vs what we do once we have our answer from the service.

In our dataWrapper.js we will add a function to read some data from our service. We're going to write some additional code that wraps the above example in a Promise too.

returnTodoItems: function (oController) {
	return new Promise(function (resolve, reject) {
		var oModel = oController.getOwnerComponent().getModel("myService");
		oModel.read("/ToDoListSet", {
			success: function (oData) {
				resolve(oData);
			},
			error: function (oError) {
				reject(oError);
			}
		});
	});
}
A function named returnTodoItems, returns a Promise for an oData read request

The above example will make an oData read request to the /ToDoListSet of the MyService model, upon receiving a response it will resolve the Promise with either; the oData response, upon success, or the error, upon failure.

You can create a function for every operation you want to run. For this example, we only have one entity set so we would have the following functions:

  • Read (returnTodoItems)
  • Create (createTodoItem)
  • Update (updateTodoItem)
  • Delete (deleteTodoItem)

This is where the value of this approach helps, what we have done in our data wrapper is define a function to fetch data. The implementation of this function (or entire utility file) is a separate concern.

Using the utility file

Now that we have a data wrapper utility, we need to import it into our controller to use it. So from your controller, you need to import the utility file. My example is using the application namespace Leon.Hassan.

Importing the utility to a controller

sap.ui.define(
	[
		"Leon/Hassan/controller/baseController",
		"Leon/Hassan/utils/dataWrapper",
		"sap/m/MessageToast",
		"sap/ui/model/json/JSONModel",
		"sap/m/MessageBox"
	],
	function (baseController, dataWrapper, MessageToast, JSONModel, MessageBox) {
		"use strict";

		return baseController.extend("Leon.Hassan.controller.mainView", {
			// here is my controller code!
		});
	}
);
Minimal example of a controller importing the utility file, using the app namespace Leon.Hassan

In the above example, lines 4 and 9 are the salient points. I have imported the data wrapper utility and included it in my controller function signature. Now that I have successfully imported the utility, we can use it in our controller.

Invoking the utility

In the example above, the utility is imported as dataWrapper so I can call any functions in the utility from this object.

onInit: function () {
	dataWrapper
		.returnTodoItems(this)
		.then(
			function (oData) {
				var oDataModel = new JSONModel(oData);
				this.getView().setModel(oDataModel, "oDataModel");
			}.bind(this)
		)
		.catch(
			function (oError) {
				MessageBox.error(oError);
			}
		);
}
Calling the 

In the above example, I call the returnTodoItems function from the data wrapper. Once a response has been received, it will resolve the promise and trigger the promise chain in our controller. The promise chain is the then and catch functions chained from the promise, they will not be triggered until a response is received and the promise is fulfilled.

If the service returns an error, it will skip straight to the catch function and render the error in a message box component.

If the service returns data, it will create a JSON model from the data and bind it to the view. If an error occurs in your then function, it will skip to the catch function and behave in the same manner as if the service had returned an error. This can happen if you write bad code when receiving your oData.

Other use cases for utility files

In my experience, Utility files are underused in favour of throwing all your code into the controller. It's also my experience that trying to get to grips with a codebase that has controllers with upwards of 10k lines of code is; time-consuming, frustrating and is usually endemic of a wider problem.

I've found that for most applications, I create utility files for data operations, fragment management and data exports. Utility files don't just have to be for bits of code you want to use more than once, they're also very useful for separating your definition from your implementation.

Leave a comment

Let me know what you think of this approach. Have you used it before, did you love it or hate it? Have you got a better way of managing your code? Let me know in the comments, or tweet me.