Search code examples
data-bindingsapui5lifecycle

UI5 - control formatter is executed too soon, before the data is loaded into model


I have a web application which, after login, displays landing screen with the navigation pane on the left. Also, during the onInit event, I call the getUserData() which collects additional data about the user (such as roles and saves them into the model). This navigation is of the type sap.tnt.NavigationListItem and is loaded from the model (data are hardcoded in App.controller.js). In App.view.xml, it looks like this

<tnt:NavigationListItem text="{appView>title}" 
items="{path: 'appView>items', templateShareable: true}"
visible="{path: 'appView>neededRole', formatter:'.myFormatter'}"> 

Now, I would like to make an improvement - to show some items in the navigation list only to the users which have sufficient roles. As you can see above, I set the formatter for the 'visible' property of the NavigationListItem. It checks the role necessary to display the NavigationListItem ('needed role'), compares it with the array of roles assigned to the user and if there is a match, shows the menu item

myFormatter: function(role) {
    const oModel = this.getView().getModel('appView');
    return oModel.oData.userData.roles.some(x => x.roleId === role);
    }

The problem is that when the myFormatter function is running, getUserData() hasn't finished yet and the model doesn't yet contain necessary roles array of the user - as as reason, all menu items are hidden. What I need to achieve is to make sure that MyFormatter runs ONLY AFTER the getUserData() has finished (and while myFormatter will run repeatedly, getUserData must run only once). How can I achieve it? getUserData() is asynchronous and no matter if I place it into onInit or beforeRendering, it finishes only after myFormatter collected the empty array from the model.

Thanks a million


Solution

  • Your formatter will run first when the view gets initialized, this is part of the lifecycle. Then it will run each time the 'needRole' entry is explicitly (via model.setProperty) modified

    It seems in your code that your formatter actually uses another data from the model: 'roles' So you could just bind your formatter to both model entries like that:

    <tnt:NavigationListItem
      text="{appView>title}" 
      items="{
        path: 'appView>items',
        templateShareable: true
      }"
      visible="{
        parts: ['appView>neededRole', 'appView>/userData/roles'],
        formatter:'.myFormatter'
      }"> 
    

    and modify your formatter to

    myFormatter: function(role, roles) {
      return (roles || []).some(x => x.roleId === role);
    }
    

    Then your formatter will trigger when role or roles get modified in the model.

    As a side note : a formatter is meant to format data, not compute things. A better option would be to directly create an 'entryVisible' entry in the model that you could then bind on your NavigationListItem (I know formatters do the job, but they also trigger a lot of rerenders you dont need)