In one of my old tutorials I have explained how to implement custom controls in SAPUI5/OpenUI5. There, you have learned that properties, associations, and aggregations are major parts of controls. I also described the difference between associations and aggregations. Besides that, I covered some of the important lifecycle methods available for every control, i.e. init(), onAfterRendering(), and exit(). These lifecyce APIs are called by the runtime, for example whenever a new instance of a control is created its init() method is called. When a control instance is destroyed the exit() lifecycle API method is called. For associations and aggregations of a control the lifecycle is defined clearly. The API docs clearly state the following (see sap.ui.base.ManagedObject):
Aggregations
"Managed aggregations can store one or more references to other ManagedObjects. They are a mean to control the lifecycle of the aggregated objects: one ManagedObject can be aggregated by at most one parent ManagedObject at any time. When a ManagedObject is destroyed, all aggregated objects are destroyed as well and the object itself is removed from its parent. That is, aggregations won't contain destroyed objects or null/undefined."
Associations
"Managed associations also form a relationship between objects, but they don't define a lifecycle for the associated objects. They even can 'break' in the sense that an associated object might have been destroyed already although it is still referenced in an association. For the same reason, the internal storage for associations are not direct object references but only the IDs of the associated target objects."
With this piece of information, the lifecycle of associations and aggregations is clear. In other words, if a control is destroyed, then
Using data binding features of SAPUI5/OpenUI5 allows developers to bind data from models to properties or aggregations of a given control. Keep in mind that associations are not bindable, see the API docs of sap.ui.base.ManagedObject for more information. Binding aggregations is very common in UI5, just think about the list items of an instance of sap.m.List, or the rows of an sap.ui.table.Table instance. To bind aggregations you can simply call the bindAggregation() API method of the corresponding control (inherited from sap.ui.base.ManagedObject, see sap.ui.base.ManagedObject.bindAggregation(...) for details). However, you could also call the generated APIs for a specific aggregation directly, i.e. bindItems(…) for the items aggregations of the sap.m.List. Here is a small code snippet to illustrate aggregation binding in UI5:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Understanding templateShareable 1 | nabisoft</title> <script src="https://openui5.hana.ondemand.com/1.36.12/resources/sap-ui-core.js" id="sap-ui-bootstrap" data-sap-ui-theme="sap_bluecrystal" data-sap-ui-libs="sap.m" data-sap-ui-bindingSyntax="complex" data-sap-ui-compatVersion="edge" data-sap-ui-preload="async"></script> <!-- XMLView --> <script id="myXmlView" type="ui5/xmlview"> <mvc:View xmlns="sap.m" xmlns:core="sap.ui.core" xmlns:mvc="sap.ui.core.mvc"> <VBox items="{/employees}"> <items> <Text text="{firstName} {lastName}" /> </items> </VBox> </mvc:View> </script> <script> sap.ui.getCore().attachInit(function () { "use strict"; sap.ui.define([ "sap/ui/model/json/JSONModel" ], function(JSONModel) { "use strict"; var oModel = new JSONModel({ employees : [ {firstName:"John1", lastName : "Doe1", kids : [{firstName:"Michael1"}, {firstName:"Maria1"}] }, {firstName:"John2", lastName : "Doe2", kids : [{firstName:"Michael2"}, {firstName:"Maria2"}] }, {firstName:"John3", lastName : "Doe3", kids : [{firstName:"Michael3"}, {firstName:"Maria3"}] }, {firstName:"John4", lastName : "Doe4", kids : [{firstName:"Michael4"}, {firstName:"Maria4"}] }, {firstName:"John5", lastName : "Doe5", kids : [{firstName:"Michael5"}, {firstName:"Maria5"}] } ]} ); sap.ui.getCore().setModel(oModel); sap.ui.xmlview({ viewContent : jQuery("#myXmlView").html() }).placeAt("content"); }); }); </script> </head> <body class="sapUiBody"> <div id="content"></div> </body> </html>
The example above creates a simple sap.m.VBox with some data and it uses an sap.m.Text as the template. The template defines how each item in the VBox should look like. The question of the questions is the following: What happens to the template if our VBox instance is destroyed? Or in other words: How does the lifecycle of a control instance used as template for data binding look like? To illustrate this, let's add a simple button, which checks the state of the template inside its press event handler.
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Understanding templateShareable 2 | nabisoft</title> <script src="https://openui5.hana.ondemand.com/1.36.12/resources/sap-ui-core.js" id="sap-ui-bootstrap" data-sap-ui-theme="sap_bluecrystal" data-sap-ui-libs="sap.m" data-sap-ui-bindingSyntax="complex" data-sap-ui-compatVersion="edge" data-sap-ui-preload="async"></script> <!-- XMLView --> <script id="myXmlView" type="ui5/xmlview"> <mvc:View xmlns="sap.m" xmlns:core="sap.ui.core" xmlns:mvc="sap.ui.core.mvc"> <VBox id="myVBox" items="{/employees}"> <items> <Text text="{firstName} {lastName}" /> </items> </VBox> </mvc:View> </script> <script> sap.ui.getCore().attachInit(function () { "use strict"; sap.ui.define([ "sap/ui/model/json/JSONModel" ], function(JSONModel) { "use strict"; var oModel = new JSONModel({ employees : [ {firstName:"John1", lastName : "Doe1", kids : [{firstName:"Michael1"}, {firstName:"Maria1"}] }, {firstName:"John2", lastName : "Doe2", kids : [{firstName:"Michael2"}, {firstName:"Maria2"}] }, {firstName:"John3", lastName : "Doe3", kids : [{firstName:"Michael3"}, {firstName:"Maria3"}] }, {firstName:"John4", lastName : "Doe4", kids : [{firstName:"Michael4"}, {firstName:"Maria4"}] }, {firstName:"John5", lastName : "Doe5", kids : [{firstName:"Michael5"}, {firstName:"Maria5"}] } ]} ); sap.ui.getCore().setModel(oModel); var oView = sap.ui.xmlview({ viewContent : jQuery("#myXmlView").html() }); oView.placeAt("content"); var oBtn = new sap.m.Button({ text : "check binding", press : function () { var oVBox, oBindingInfo, oTemplate; oVBox = oView.byId("myVBox"); oBindingInfo = oVBox.getBindingInfo("items"); console.log(oBindingInfo); // note ==> templateShareable: 1 oTemplate = oBindingInfo.template; console.log(oTemplate); oVBox.destroy(); //oVBox.placeAt("content"); //==> ERROR: After an element has been destroyed, it can no longer be used in the UI! console.log("oVBox.bIsDestroyed = " + oVBox.bIsDestroyed); console.log("oTemplate.bIsDestroyed = " + oTemplate.bIsDestroyed); // There is no better API for this, i.e. getter oTemplate.unbindText(); oTemplate.setText("Hard coded text"); oTemplate.placeAt("content"); // does not cause an error, because it's not destroyed! //conclusion: we destroyed the VBox, but its template is not destroyed } }); oBtn.placeAt("contentButton"); }); }); </script> </head> <body class="sapUiBody"> <div id="content"></div> <div id="contentButton"></div> </body> </html>
When the button is pressed our event handler is called. In the handler we retrieve a reference to the VBox on the view in order to get the BindingInfo of its items aggregation. The BindingInfo is basically what we have defined in the XMLView for the binding of the items aggregation of our VBox. It's worth to note that the BindingInfo has a property templateShareable which is set to the value 1. So although we did not set this templateShareable it's there. Furthermore, the BindingInfo has a property template, which is the template that we have specified in the XMLView. In line 66 we finally do something that the lifecycle management of UI5 does in certain cases as well: we call oVBox.destroy() to destroy the VBox. After an element has been destroyed, it can no longer be used in the UI! That means calling oVBox.placeAt("content"); would cause an error.
We just destroyed the VBox, but how does that affect the template that we used for the VBox? Let's have a look at oTemplate to find out more. First of all, it seems that destroyed controls have a property bIsDestroyed with the boolean value true (unfortunately, there is no getter). In our case oTemplate.bIsDestroyed is not available, and this is the first hint for us that the template was not destroyed by the runtime. Next we want to prove that this assumption is true by setting the text property of the template (an instance of an sap.m.Text) and placing it on the view afterwards. As you can see it works perfectly, and there is not error at all! We have now verified that although we destroyed the VBox its template was not destroyed automatically by the runtime!
This fact is related to the templateShareable property that we found on the BindingInfo. In our case its value was 1 which stands for MAYBE_SHAREABLE_OR_NOT (see the code of sap.ui.base.ManagedObject on Github). Although we have not set the value the runtime made a guess. In case a template is marked as shareable (templateShareable=true) or as "Maybe Shareable Or Not" it will not be destroyed by the UI5 runtime. It also means that the developer has to take care of destroying the template(s) in order to release unneeded resources. However, this could lead to memory leaks in case developers forget to call destroy() manually on the templates. By the way – the default value for templateShareable is true (see sap.ui.base.ManagedObject.html.bindAggregation) and this is equl to the "old" behavior (before templateShareable was introduced). When setting templateShareable:false the "new" behavior if turned on, which means that the runtime will take care of destroying the template for us.
templateShareable was introduced after the UI5 team detected that there is actually no clear definition for the lifecycle of templates used for data binding. When calling bindAggregation(...) you always have to pass a template. But what happens to the old template if you are "re-binding"? In other words, what happens to the old template if the binding is updated by calling bindAggregation(...) again? The answer is simple: the template is not destroyed per default! The runtime can't just destroy() the old template because applications often keep a reference to the template in a private variable to reuse the template later. Therefore, the UI5 team has introduced templateShareable. The value false basically tells the runtime that the lifecycle of the parent control can be used for the template as well, i.e. if the parent is destroyed its template can be destroyed as well. In such cases the developer does not have to think of destroying the template. When templateShareable is set to true (default + old behavior) the runtime does not care about the lifecycle of the template and the developer has to handle it instead. However, many developers don't maintain templateShareable and it wasn't even available in older versions of UI5. In case it's not set the runtime tries to detect the most probable value for it by using some kind of a smart heuristic. If the runtime thinks that the template is MAYBE_SHAREABLE_OR_NOT during operations that imply shareable templates (i.e. when the BindingInfo is cloned) you will see the famous error message in your console:
A shared template must be marked with templateShareable:true in the binding info
This error message tells you that you should check you code and handle the lifecycle of the template. In fact, internally when you see this message you can be sure that you have a memory leak because internally templateShareable is set to true right after this message! Check sap.ui.base:ManagedObject on github in case you want a prove for this.
In apps it's not always obvious why you actually get the error messsage mentioned above. Have a look at the following code:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Understanding templateShareable 3 | nabisoft</title> <script src="https://openui5.hana.ondemand.com/1.36.12/resources/sap-ui-core.js" id="sap-ui-bootstrap" data-sap-ui-theme="sap_bluecrystal" data-sap-ui-libs="sap.m" data-sap-ui-bindingSyntax="complex" data-sap-ui-compatVersion="edge" data-sap-ui-preload="async"></script> <!-- XMLView --> <script id="myXmlView" type="ui5/xmlview"> <mvc:View xmlns="sap.m" xmlns:core="sap.ui.core" xmlns:mvc="sap.ui.core.mvc"> <VBox items="{/employees}"> <items> <HBox items="{kids}"> <items> <Text text="{firstName}," /> </items> </HBox> </items> </VBox> </mvc:View> </script> <script> sap.ui.getCore().attachInit(function () { "use strict"; sap.ui.define([ "sap/ui/model/json/JSONModel" ], function(JSONModel) { "use strict"; var oModel = new JSONModel({ employees : [ {firstName:"John1", lastName : "Doe1", kids : [{firstName:"Michael1"}, {firstName:"Maria1"}] }, {firstName:"John2", lastName : "Doe2", kids : [{firstName:"Michael2"}, {firstName:"Maria2"}] }, {firstName:"John3", lastName : "Doe3", kids : [{firstName:"Michael3"}, {firstName:"Maria3"}] }, {firstName:"John4", lastName : "Doe4", kids : [{firstName:"Michael4"}, {firstName:"Maria4"}] }, {firstName:"John5", lastName : "Doe5", kids : [{firstName:"Michael5"}, {firstName:"Maria5"}] } ]} ); sap.ui.getCore().setModel(oModel); sap.ui.xmlview({ viewContent : jQuery("#myXmlView").html() }).placeAt("content"); }); }); </script> </head> <body class="sapUiBody"> <div id="content"></div> </body> </html>
As you can see we use VBox that uses an HBox as its template. The HBox itself uses a simple sap.m.Text as its template. This example will cause the templateShareable error message being printed to the console. Can you tell why we get the error message and how to get rid of it? Obviously, UI5 was not smart enough to guess that in this case we actually would prefer to have templateShareable=false. To fix the error message you need to set templateShareable to either false of true in the BindingInfo (see code below). In case you set it to true make sure you also handle the lifecycle of the template (I leave this task to you).
<mvc:View xmlns="sap.m" xmlns:core="sap.ui.core" xmlns:mvc="sap.ui.core.mvc"> <VBox items="{/employees}"> <items> <HBox items="{path:'kids', templateShareable:false}"> <!-- if using true make sure to destroy manually --> <items> <Text text="{firstName}," /> </items> </HBox> </items> </VBox> </mvc:View>
I hope this little explanation helps you to better understand what's behind templateShareable. Now you should know what to do when you see the error message on the console.