docs

Synchronizing Properties via a $this Model

An alternative to writing setter wrappers in your composite control: expose its state via a model, and let the inner controls bind to it. OpenUI5 propagates property changes for you.


Why a Model?

When a composite control has only one or two properties, overriding their dedicated setters (setMyValue, setLabel, etc.) to forward values to the inner controls is straightforward — see Building Standard Composite Controls. For composite controls with many properties, or with several inner controls reacting to the same property, those per-property setter overrides become repetitive.

An alternative is to expose the composite’s state via a model and let the inner controls bind to it. This shifts the synchronization out of imperative code into declarative bindings, and OpenUI5 handles the updates automatically. By convention — inherited from the XMLComposite era — the model is registered under the name $this, so that any reader of an inner control’s binding can tell at a glance: “this binding pulls from the surrounding composite”.

Note:

Use plain setter forwarding when you have one or two properties; reach for the model pattern when you have many, or when migrating from XMLComposite.


The $this Binding Pattern

The following helper is a pattern that application teams can copy into their codebase and adapt as needed. It is small, self-contained, and has no dependencies beyond sap.ui.core.Control and sap.ui.model.json.JSONModel. The code snippets below use CompositeHelper as the file and function name — this is just a naming convention for the example; you can choose any name that fits your codebase.

The pattern has three responsibilities, all wired through a single JSONModel registered under $this:

  1. Create the model and register it under the name $this on the control.
  2. Inner control → outer control: When a bound inner control changes a value, the model fires a propertyChange. Listen for it and write the value back into the composite via setProperty(..., true).
  3. Outer control → model: Wrap the composite’s setProperty, so that programmatic changes from outside (composite.setMyValue("x")) also update the model.

These two directions share a re-entry guard, so that one does not trigger the other in a loop.

sap.ui.define([
    "sap/ui/core/Control",
    "sap/ui/model/json/JSONModel"
], function(Control, JSONModel) {
    "use strict";

    /**
     * Activates the $this-model pattern on a composite control instance.
     *
     * @param {sap.ui.core.Control} oControl - the composite control instance (typically `this` from init)
     */
    return function CompositeHelper(oControl) {
        const oModel = new JSONModel({});
        oControl.setModel(oModel, "$this");

        let bSuppressUpdate = false;

        // Inner control -> outer control: a bound inner control changed something.
        oModel.attachPropertyChange(function(oEvent) {
            if (bSuppressUpdate) {
                return;
            }
            const sProperty = oEvent.getParameter("path").slice(1); // "/myValue" -> "myValue"
            const vValue = oEvent.getParameter("value");
            Control.prototype.setProperty.call(oControl, sProperty, vValue, true);
        });

        // Outer control -> model: programmatic setter calls reach the model.
        oControl.setProperty = function(sName, vValue, bSuppressInvalidate) {
            bSuppressUpdate = true;
            try {
                oModel.setProperty("/" + sName, vValue);
            } finally {
                bSuppressUpdate = false;
            }
            return Control.prototype.setProperty.call(oControl, sName, vValue, bSuppressInvalidate);
        };
    };
});

Note:

The helper assumes the composite inherits directly from sap.ui.core.Control. If your composite extends a more specialized base class (for example, sap.m.InputBase), replace Control.prototype.setProperty with the corresponding base class’s prototype so that the framework’s setter logic for that base class still runs.

Three notes on the implementation:


Using the Pattern in Your Composite

This is a LabeledInput composite — a label, an input, and a status text below it, all bound to three properties of the composite (label, value, status):

sap.ui.define([
    "sap/ui/core/Control",
    "sap/m/Label",
    "sap/m/Input",
    "sap/m/Text",
    "my/app/util/CompositeHelper"
], function(Control, Label, Input, Text, CompositeHelper) {
    "use strict";

    const LabeledInput = Control.extend("my.app.LabeledInput", {
        metadata: {
            properties: {
                "label":  { type: "string", defaultValue: "" },
                "value":  { type: "string", defaultValue: "" },
                "status": { type: "string", defaultValue: "" }
            },
            aggregations: {
                "_label":  { type: "sap.m.Label", multiple: false, visibility: "hidden" },
                "_input":  { type: "sap.m.Input", multiple: false, visibility: "hidden" },
                "_status": { type: "sap.m.Text",  multiple: false, visibility: "hidden" }
            }
        },

        init() {
            Control.prototype.init.apply(this, arguments);
            CompositeHelper(this);

            this.setAggregation("_label", new Label({
                text: "{$this>/label}"
            }));
            this.setAggregation("_input", new Input({
                value: "{$this>/value}"
            }));
            this.setAggregation("_status", new Text({
                text: "{$this>/status}"
            }));
        },

        renderer: {
            apiVersion: 4,
            render(oRm, oControl) {
                oRm.openStart("div", oControl);
                oRm.class("LabeledInput");
                oRm.openEnd();
                oRm.renderControl(oControl.getAggregation("_label"));
                oRm.renderControl(oControl.getAggregation("_input"));
                oRm.renderControl(oControl.getAggregation("_status"));
                oRm.close("div");
            }
        }
    });

    return LabeledInput;
});

Two things to note:

Note:

If you override a setter on the composite to do additional work (for example, a setEditable that also clears the validation state), the override must end with this.setProperty(...), not by writing the model directly. The wrapped setProperty keeps the model in sync; writing the model directly skips the framework’s setter logic.

// Side-effect setter — composite's own setEditable does additional cleanup.
setEditable(bEditable) {
    this.clearValidationState(); // composite-specific side effect
    return this.setProperty("editable", bEditable);
}

Migrating from XMLComposite

XMLComposite automatically registers a $this backing model for property bindings, so that fragment bindings such as {$this>/text} work without any explicit setup. The pattern on this page reconstructs that behavior with a JSONModel. Property bindings stay character-identical — only the model is now activated by your code in init.

One important difference: the $this model in XMLComposite could also expose aggregations. The pattern on this page covers properties only. For aggregations, see Forwarding Aggregations.

`XMLComposite` feature What is lost during migration Compensation
Automatic `$this` model registration Bidirectional property bindings between inner controls and outer control The `$this` binding pattern on this page

Related Information

Composite Controls

Building Standard Composite Controls

Forwarding Aggregations

API Reference: sap.ui.model.json.JSONModel