Appearance
UI5 Design Patterns
This page contains some Design Pattern that are productively used inside UDINA UI5 apps.
Controller Extensions
Controller extensions allow you to add functionality to existing applications while implementing the Mix-in design pattern.
They can be used for extensibility purposes, for example by a customer wishing to extend SAP-delivered applications, or as a reusable part that is added to the original application (Using Controller Extension).
Flexible Programming Model
Working with sap.fe
and the Flexible Programming Model, you cannot use a custom BaseController
class anymore and instead have to decouple reused functionality using controller extensions!
WARNING
Usage examples will follow, after SAP fixed current transpiler issues!
Service Factory
If the app landscape is growing, at some point there is a need to be able to create reuseable services.
For this reason, SAPUI5 offers a factory method pattern that allows to declaratively define sap.ui.core.Component
service dependencies.
This feature is not officially supported!
SAPUI5 does not made this feature public available and it is even not documented in the SDK, but SAP internally uses the functionality inside sap.ushell.services
and sap.fe.core.services
.
Because the API is not public, no types will be generated for TypeScript TypeScript Support
Use is at the risk, that the API will change in the future.
File Hierarchy
The following files are relevant:
├── webapp │ ├── services │ │ ├── MyServiceFactory.ts │ │ ├── enhanceService.ts │ │ ├── registerServices.ts │ │ ├── types.ts │ ├── Component.ts │ ├── manifest.json
Component Service Dependencies
All services dependencies needs to be defined in the manifest.json
file:
json
{
[...]
"sap.ui5": {
"services": {
"myLocalServiceAlias": {
"factoryName": "my.ServiceFactory",
["startup": "waitFor"],
["optional": true],
["settings": {
"modelName": "my-model"
}]
}
}
}
[...]
}
Property | Description |
---|---|
myLocalServiceAlias | Service alias to used for retrieving a service instance using Component.getService("myLocalServiceAlias") . |
factoryName | The qualified name of the service factory that is creating the service instance. |
startup | Services can be optionally instanciated during component start using waitFor option. |
optional | The optional property defines that the service is not mandatory and there will be no error in the log if the service is not available. |
settings | The settings property allows to pass-in additional information that will be accessible through the service context. |
Service Factory Registry
All service dependencies needs to be registered using the sap/ui/core/service/ServiceFactoryRegistry
:
registerServices.js
ts
import ServiceFactoryRegistry
from "sap/ui/core/service/ServiceFactoryRegistry";
ServiceFactoryRegistry.register(
"ns.MyService",
new MyServiceFactory()
);
[...]
There is an issue with the TypeScript and the returned service interface.
With the following prototype, Component.getService()
will return the relevant service instance instead of the service interface for further usage:
enhanceService.ts
ts
/*
* Protoype Service.getInterface to return
* instance instead interface.
* Reason: returned interface is missing
* public functions (TypeScript issue)
*/
import Service from "sap/ui/core/service/Service";
Service.prototype.getInterface = function () {
// return instance instead interface
return this;
}
Component Usage
The app component needs to register all service factories:
Component.ts
ts
// Register all services declared in the descriptor
// for components (manifest.json)
import "./services/registerServices";
// Import relevant prototype to fix TS issue for the service
import "./services/enhanceService";
[...]
Example Service Usage
The following is an example, how to define and implement a custom service by exporting the service and the corresponding service factory, which creates the service instance.
MyServiceFactory.ts
ts
// core dependencies
import Service from "sap/ui/core/service/Service";
import ServiceFactory, { ServiceContext }
from "sap/ui/core/service/ServiceFactory";
// additional dependencies
import AppComponent from "../Component";
import XMLView from "sap/ui/core/mvc/XMLView";
import JSONModel from "sap/ui/model/json/JSONModel";
import Log from "sap/base/Log";
// default settings type used by settings context
type MySettings = {
modelName: "my-model";
};
export class MyService extends Service<MySettings> {
initPromise!: Promise<MyService>;
appComponent!: AppComponent;
rootView: XMLView;
model: JSONModel;
init() {
// handle init lifecycle
const context = this.getContext();
if (context.scopeType === "component") {
const settings = context.settings;
// shortcut to parent compomnent
this.appComponent = context.scopeObject;
// create an internally used service model
this.model = new JSONModel({
myProperty: "value"
});
// register service model for public usage
this.appComponent.setModel(
this.model,
settings.modelName // manifest injected
);
// use component rootView for ux dependencies
this.appComponent.rootControlLoaded()
.then((rootControl) => {
this.rootView = rootControl as XMLView;
// eg. add ux controls to hierarchy
// this.rootView.addDependent(this.dialog);
}
);
// initialize inter service dependency
this.appComponent.getService("dependendService")
.then((dependendService) => {
this.startDependendFunction(
dependendService.getProperty()
);
}).catch(function (e) {
Log.error(e);
});
}
// fullfill service for usage
this.initPromise = Promise.resolve(this);
}
exit() {
// handle exit lifecycle
}
startDependendFunction(dependendProperty: string) {
// some functionality
}
}
export default class MyServiceFactory
extends ServiceFactory<MySettings> {
createInstance(serviceContext: ServiceContext<MySettings>)
: Promise<MyService> {
const myService = new MyService(serviceContext);
return myService.initPromise;
}
}
TypeScript Support
Using TypeScript, transpiler will crash!
Since the relevant classes are not public, sap will not generate the relevant types.
To fix this in such cases, you have to manually add the relevant types.
types.d.ts
ts
// only used and relevant stuff, manually declared!
declare module "sap/ui/core/service/Service" {
import BaseObject from "sap/ui/base/Object";
export default class Service<T> extends BaseObject {
constructor(
oServiceContext: object
);
getContext(): ServiceContext;
}
}
declare module "sap/ui/core/service/ServiceFactory" {
import BaseObject from "sap/ui/base/Object";
export type ServiceContext<T> = {
scopeObject: Component,
scopeType: "component",
settings: any
}
export default class ServiceFactory<T> extends BaseObject {
constructor(
vService?: string
);
}
}
declare module "sap/ui/core/service/ServiceFactoryRegistry" {
export default class ServiceFactoryRegistry {
static register(
sServiceFactoryName: string,
oServiceFactory: ServiceFactory
): ServiceFactoryRegistry;
}
}