Technology Blogs by SAP
Learn how to extend and personalize SAP applications. Follow the SAP technology blog for insights into SAP BTP, ABAP, SAP Analytics Cloud, SAP HANA, and more.
cancel
Showing results for 
Search instead for 
Did you mean: 
Former Member
Building large web applications can get complex pretty fast. Especially managing the state within the application is not always easy. Which buttons or fields should I show based on the users previous actions? Where do I get the data from that is currently displayed? Questions like this and others will arise during the development process.

Currently SAPUI5 offers different models to store data (OData, JSON, XML). These models use two-way-binding to update properties and objects. However, this can get complex if you get your data from different models, you have controllers update data, but controls can update data as well. In such a non-deterministic system it can get increasingly difficult to reproduce errors or add new features.

This can be changed by using a predictable state container like Redux. Redux attempts to make state mutations predictable by imposing certain restrictions on how and when updates can happen. These restrictions are reflected in the three principles of Redux. The Redux documentation gives a pretty good overview (just follow the link).

The three principles are:

  1. Single source of truth: The state of your whole application is stored in an object tree within a single store.

  2. State is read-only: The only way to change the state is to emit an action, an object describing what happened.

  3. Changes are made with pure functions: To specify how the state tree is transformed by actions, you write pure reducers.


So how can we integrate this concept in SAPUI5? I created a custom One-Way model implementation (ReduxModel) which builds the bridge between Redux and SAPUI5 applications. It works similar to the UI5 JSON Model so you can use the same syntax to bind properties.

Installing the ReduxModel:


This guide assumes that you build your application as described in the openui5-sample-app, using Grunt and Bower to manage dependencies and build the application. The process of including the new library might be different if you consume UI5 in a different way.

First you need to install the redux model and add it to your bower dependencies:
bower install openui5-redux-model --save

Make sure to also update the settings of the server you are using so the files are included in the resources folder.

Then you can add the redux model as an additional dependency in the SAPUI5 bootstrap tag:
<script id="sap-ui-bootstrap"
src="resources/sap-ui-core.js"
data-sap-ui-libs="redux">
</script>

You also need to integrate redux somewhere in your application. It is not included with this library. You can do this by loading it from a CDN via a <script> tag or by including the redux.js file directly somewhere in your application. By default redux comes as a AMD module which makes it also available in a global variable. However, you can also include it via the UI5 module system. To do that you need to register it with the UI5 core.
jQuery.sap.registerModuleShims({
'app/thirdparty/redux.min': {
exports: 'Redux'
}
});

Setting up the model


First we need to create a redux store and add one or more reducers to it. You can find more information about this in the redux documentation. It makes sense to create a separate module for the redux store, so you can require it in different parts of the application (for example to dispatch actions in a controller). In this example I also included the redux logger middleware, which makes it possible to see all redux actions logged in the console.
sap.ui.define([
'../reducers/index',
'../ext/redux.min',
'../ext/redux-logger.min',
], function(fnReducers, Redux, createLogger) {
var oStore = Redux.createStore(
fnReducers,
Redux.applyMiddleware(
createLogger()
)
);

return oStore;
});

Wherever you setup your model you can require the store and instantiate the ReduxModel.
sap.ui.define([
'redux/ReduxModel',
'./store',
], function(ReduxModel, oStore) {
return {
createReduxModel: function() {
const model = new ReduxModel(oStore);
return model;
}
}
});

Setup Bindings


Bindings can be setup the same way like you are used to from other models in SAPUI5. If you have a reducer foo which has a property bar you can just define a binding as simple as:
<Button text="{redux>/foo/bar}" />

Dispatching actions


Because of the fact that the redux model is a one-way model you cannot update bindings by calling setValue or similar functions. You have to dispatch actions, which are then processed by the reducers to update the state. After that all bindings will be updated (if they have changed).

For this example we have a controller App.controller.js which implements a function handleButtonClick that should trigger a state change of the application.
sap.ui.define([
'sap/ui/core/mvc/Controller',
'../store'
], function (Controller, oStore) {
Controller.extend('my.controller.App', {
handleButtonClick: function() {
oStore.dispatch({
type: 'MY_ACTION',
meta: {},
payload: {}
});
}
});
});

Selectors


It is not a best practice to access data in your store directly because the structure of how your data is stored can easily change as you develop your application. The better approach is to use selectors with a defined API.

Selectors are also supported by the ReduxModel. You have to use a special syntax in the binding to access them. All selector binding must start with /selector. The second part of the binding in the class in which the selector is defined. It will be loaded via the UI5 module system.
<Button text="{/selector/my.selectors.SelectorClass/selectorFunction}" />

You can specify a prefix for all bindings as second argument when you create the ReduxModel.
var oStore = Redux.createStore(fnReducer);
var oModel = new ReduxModel(oStore, 'my.selectors');
sap.ui.getCore().setModel(oModel);

By doing that you can leave out the prefix when you define the binding:
<Button text="{/selector/SelectorClass/selectorFunction}" />

All selector functions will receive the current state as a first and an optional context as second argument.

Summary


The blog post shows how to integrate a predictable state container in a SAPUI5 application. By doing so, there is only one central place where all data which is required to render the application is stored. This makes it much easier to handle data within the application and also enables easier testing and problem solving.

Please share your thoughts if you find this post helpful and if the ReduxModel can be further improved.
9 Comments