<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Demo</title>
    <script id="sap-ui-bootstrap"
      src="https://sdk.openui5.org/nightly/resources/sap-ui-core.js"
      data-sap-ui-onInit="module:sap/ui/core/ComponentSupport"
      data-sap-ui-async="true"
      data-sap-ui-resourceRoots='{ "my.demo": "./" }'
      data-sap-ui-compatVersion="edge"
      data-sap-ui-excludeJQueryCompat="true"
      data-sap-ui-logLevel="INFO"
      data-sap-ui-xx-componentPreload="off"
      data-sap-ui-xx-waitForTheme="init"
    ></script>
  </head>
  <body id="content" class="sapUiBody">
    <div style="height: 100%;"
      data-sap-ui-component
      data-id="rootComponentContainer"
      data-name="my.demo"
      data-height="100%"
      data-settings='{"id": "rootComponent"}'
    ></div>
  </body>
</html>
sap.ui.define([
  "sap/ui/core/mvc/ControllerExtension",
  "sap/ui/core/mvc/OverrideExecution",
], (ControllerExtension, OverrideExecution) => {
  "use strict";

  return ControllerExtension.extend("my.demo.controller.Dialog", {
    metadata: {
      methods: { // Extensions can also allow overriding by the base
        "blaFromExtension": {
          public: true,
          final: false, // default
          overrideExecution: OverrideExecution.Before,
        },
        "hookFromExtension": {
          public: true,
          final: false, // default
          overrideExecution: OverrideExecution.After,
        },
      },
    },

    // Property `override(s)` mandatory for extension implementation:
    // `overrides` (with 's') preferred since https://github.com/SAP/openui5/commit/167251ea3cfb98ce7b20a671810dd6814cdd70fe
    // For UI5 versions lower than 1.112, use either `override` or enable `overridesToOverride` in TS projects: https://github.com/ui5-community/babel-plugin-transform-modules-ui5/pull/89
    overrides: {
      // See also https://stackoverflow.com/a/78352624/5846045 (this.base)
      onInit: function() {
        alert("onInit of extension");
      },
      onBeforeRendering: function() {
        alert("onBeforeRendering of extension");
      },
      onAfterRendering: function() {
        alert("onAfterRendering of extension");
      },
      onExit: function() {
        alert("onExit of extension");
      },
      doSomething: function() { // only if doSomething is called NOT from here
        alert("doSomething from the extension");
      },
      myBaseHook: function() {
        alert("myBaseHook from the extension");
      },
    },

    onClosePressed: function() {
      // Handler registered from the Dialog with press=".myReuse.onClosePressed"
      this.getView().byId("myDialog").close();
    },

    blaFromExtension: function() {
      alert("bla original");
    },

    /**
     * @abstract
     */
    hookFromExtension: function() {},

    notAFunction: {}, // non-function members won't be available at runtime.
  });
});
sap.ui.define([
  "sap/ui/core/mvc/Controller",
  "./extension/Dialog",
  "sap/ui/core/mvc/OverrideExecution",
], (Controller, DialogExtension, OverrideExecution) => {
  "use strict";

  return Controller.extend("my.demo.controller.Root", {
    metadata: {
      methods: { // Interface from the perspective of the extension:
        "myBaseHook": {
          public: true,
          final: false, // default
          overrideExecution: OverrideExecution.Instead, // default
        },
        "doSomething": {
          public: true,
          final: false, // default
          // Extension-`doSomthing` should be called BEFORE this `doSomething`
          overrideExecution: OverrideExecution.Before,
        },
      }
    },

    // Lifecycle events
    onInit: function() {
      alert("onInit of the base Controller");
      // this.myReuse.blaFromExtension();
    },
    onBeforeRendering: function() {
      alert("onBeforeRendering of the base Controller");
    },
    onAfterRendering: function() {
      alert("onAfterRendering of the base Controller");
    },
    onExit: function() {
      alert("onExit of the base Controller");
    },

    // `myReuse: DialogExtension` is sufficient, but it's also possible to:
    myReuse: DialogExtension.override({ // provide implementation of the (abstract) hook methods of the extension.
      blaFromExtension: function() { // Won't override if `final: true`
        alert("Overridden blaFromExtension called");
      },
      hookFromExtension: function() {
        alert("Reuse hook implementation");
      },
    }),

    onPress: async function() {
      this.byId("myDialog").open();
      this.doSomething(); // By calling its method, the extension method will be also evaluated as defined in the `/metadata/methods/doSomething`.
    },

    /**
     * @abstract
     */
    myBaseHook: function() {},

    doSomething: function() {
      alert("doSomething from the base");
    },

  });
});
<Dialog xmlns="sap.m"
  id="myDialog"
  title="Dialog Title"
  class="sapUiResponsiveContentPadding"
>
  <Text text="Content" />
  <beginButton>
    <Button id="myButton"
      text="Close"
      press="
        .doSomething;
        .myBaseHook;
        .myReuse.blaFromExtension;
        .myReuse.hookFromExtension;
        .myReuse.onClosePressed;
      "
    />
  </beginButton>
</Dialog>
<mvc:View controllerName="my.demo.controller.Root"
  xmlns:mvc="sap.ui.core.mvc"
  xmlns="sap.m"
  displayBlock="true"
  height="100%"
>
  <App autoFocus="false">
    <Page
      showHeader="false"
      class="sapUiResponsiveContentPadding"
    >
      <Button
        text="Open Dialog"
        type="Emphasized"
        press=".onPress"
      />
    </Page>
    <dependents>
      <core:Fragment xmlns:core="sap.ui.core"
        fragmentName="my.demo.view.fragment.Dialog"
        type="XML"
      />
    </dependents>
  </App>
</mvc:View>
{
  "_version": "1.68.0",
  "start_url": "index.html",
  "sap.app": {
    "id": "my.demo",
    "type": "application",
    "title": "Demo",
    "description": "Sample Code",
    "applicationVersion": {
      "version": "1.0.0"
    }
  },
  "sap.ui": {
    "technology": "UI5",
    "deviceTypes": {
      "desktop": true,
      "tablet": true,
      "phone": true
    }
  },
  "sap.ui5": {
    "dependencies": {
      "minUI5Version": "1.92.0",
      "libs": {
        "sap.ui.core": {},
        "sap.m": {},
        "sap.ui.unified": {},
        "sap.ui.layout": {}
      }
    },
    "contentDensities": {
      "compact": true,
      "cozy": true
    },
    "rootView": {
      "viewName": "my.demo.view.Root",
      "id": "rootView",
      "type": "XML",
      "async": true,
      "height": "100%"
    }
  }
}
sap.ui.define([
  "sap/ui/core/UIComponent",
], (UIComponent) => {
  "use strict";

  return UIComponent.extend("my.demo.Component", {
    metadata: {
      manifest: "json",
      interfaces: [
        "sap.ui.core.IAsyncContentCreation",
      ],
    },

  });
});
For the older approach, see [Extending Controller via "Component Configuration"](https://embed.plnkr.co/7jnWdezkHueq3esS?sidebar&show=manifest.json,controller%2FMain.controller.js,controller%2Fextension%2FCustomMain.controller.js,preview:%3Fsap-ui-xx-componentPreload%3Doff)