14 KiB
TypeScript Support in SAPUI5
Source: Official SAP SAPUI5 Documentation Documentation: https://github.com/SAP-docs/sapui5/tree/main/docs/02_Read-Me-First Last Updated: 2025-11-21
Overview
TypeScript enhances JavaScript development by adding type information, enabling early error detection and improved code assistance. SAPUI5 provides dedicated type definitions for fully-typed application development.
Benefits:
- Early error detection during development
- Better code completion and IntelliSense
- Improved refactoring support
- Enhanced documentation through types
- Safer code through compile-time checks
Production Status: TypeScript support is production-ready and actively maintained by the SAPUI5 team.
Type Definition Packages
@sapui5/types (Recommended)
Officially maintained by the SAPUI5 development team:
npm install --save-dev @sapui5/types
Features:
- Official type definitions
- Regular updates with SAPUI5 releases
- Full framework coverage
- Maintained by SAP
Configuration (tsconfig.json):
{
"compilerOptions": {
"module": "es2022",
"moduleResolution": "node",
"target": "es2022",
"lib": ["es2022", "dom"],
"types": ["@sapui5/types"],
"skipLibCheck": true,
"strict": true
}
}
@types/openui5 (Community)
Community-maintained via DefinitelyTyped:
npm install --save-dev @types/openui5
Use Case: Alternative for OpenUI5 projects or when specific versions needed.
Project Setup
Basic TypeScript SAPUI5 Project
package.json:
{
"name": "my-sapui5-ts-app",
"version": "1.0.0",
"scripts": {
"build": "tsc && ui5 build",
"start": "ui5 serve",
"watch": "tsc --watch"
},
"devDependencies": {
"@sapui5/types": "^1.120.0",
"@ui5/cli": "^3.0.0",
"typescript": "^5.0.0"
}
}
tsconfig.json:
{
"compilerOptions": {
"target": "es2022",
"module": "es2022",
"moduleResolution": "node",
"lib": ["es2022", "dom"],
"types": ["@sapui5/types"],
"outDir": "./webapp",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"declaration": true,
"sourceMap": true
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}
ui5.yaml:
specVersion: "3.0"
metadata:
name: my.sapui5.ts.app
type: application
framework:
name: SAPUI5
version: "1.120.0"
libraries:
- name: sap.m
- name: sap.ui.core
- name: sap.f
TypeScript Component
src/Component.ts:
import UIComponent from "sap/ui/core/UIComponent";
import models from "./model/models";
import Device from "sap/ui/Device";
import JSONModel from "sap/ui/model/json/JSONModel";
/**
* @namespace my.sapui5.ts.app
*/
export default class Component extends UIComponent {
public static metadata = {
manifest: "json"
};
private contentDensityClass: string;
public init(): void {
// Call parent init
super.init();
// Set device model
this.setModel(models.createDeviceModel(), "device");
// Create router
this.getRouter().initialize();
}
/**
* Get content density CSS class
*/
public getContentDensityClass(): string {
if (this.contentDensityClass === undefined) {
// Check if touch device
if (!Device.support.touch) {
this.contentDensityClass = "sapUiSizeCompact";
} else {
this.contentDensityClass = "sapUiSizeCozy";
}
}
return this.contentDensityClass;
}
}
TypeScript Controller
src/controller/Main.controller.ts:
import Controller from "sap/ui/core/mvc/Controller";
import MessageToast from "sap/m/MessageToast";
import MessageBox from "sap/m/MessageBox";
import JSONModel from "sap/ui/model/json/JSONModel";
import Filter from "sap/ui/model/Filter";
import FilterOperator from "sap/ui/model/FilterOperator";
import Event from "sap/ui/base/Event";
import ResourceBundle from "sap/base/i18n/ResourceBundle";
import ResourceModel from "sap/ui/model/resource/ResourceModel";
import Table from "sap/m/Table";
import SearchField from "sap/m/SearchField";
/**
* @namespace my.sapui5.ts.app.controller
*/
export default class Main extends Controller {
private viewModel: JSONModel;
/*eslint-disable @typescript-eslint/no-empty-function*/
public onInit(): void {
// Create view model
this.viewModel = new JSONModel({
busy: false,
selectedItemsCount: 0
});
this.getView()?.setModel(this.viewModel, "view");
}
/**
* Search handler
*/
public onSearch(event: Event): void {
const searchField = event.getSource() as SearchField;
const query = searchField.getValue();
const filters: Filter[] = [];
if (query && query.length > 0) {
filters.push(new Filter("Name", FilterOperator.Contains, query));
}
const table = this.byId("productsTable") as Table;
const binding = table.getBinding("items");
binding?.filter(filters);
}
/**
* Button press handler
*/
public onPress(): void {
const resourceBundle = this.getResourceBundle();
MessageToast.show(resourceBundle.getText("buttonPressed") as string);
}
/**
* Delete confirmation
*/
public onDelete(): void {
const resourceBundle = this.getResourceBundle();
MessageBox.confirm(
resourceBundle.getText("deleteConfirm") as string,
{
onClose: (action: string) => {
if (action === MessageBox.Action.OK) {
this.performDelete();
}
}
}
);
}
/**
* Get resource bundle
*/
private getResourceBundle(): ResourceBundle {
const model = this.getView()?.getModel("i18n") as ResourceModel;
return model.getResourceBundle() as ResourceBundle;
}
/**
* Perform delete operation
*/
private performDelete(): void {
// Delete logic here
MessageToast.show("Item deleted");
}
}
TypeScript Model/Formatter
src/model/formatter.ts:
import DateFormat from "sap/ui/core/format/DateFormat";
import NumberFormat from "sap/ui/core/format/NumberFormat";
export default {
/**
* Format date to localized string
*/
formatDate(date: Date | string | null): string {
if (!date) {
return "";
}
const dateFormat = DateFormat.getDateInstance({
pattern: "dd.MM.yyyy"
});
return dateFormat.format(new Date(date));
},
/**
* Format currency
*/
formatCurrency(amount: number | null, currency: string): string {
if (amount === null || amount === undefined) {
return "";
}
const currencyFormat = NumberFormat.getCurrencyInstance();
return currencyFormat.format(amount, currency);
},
/**
* Format status
*/
formatStatus(status: string): string {
const statusMap: Record<string, string> = {
"A": "Approved",
"R": "Rejected",
"P": "Pending"
};
return statusMap[status] || status;
},
/**
* Format status state
*/
formatStatusState(status: string): string {
const stateMap: Record<string, string> = {
"A": "Success",
"R": "Error",
"P": "Warning"
};
return stateMap[status] || "None";
}
};
Custom Control in TypeScript
src/control/CustomButton.ts:
import Button from "sap/m/Button";
import RenderManager from "sap/ui/core/RenderManager";
/**
* @namespace my.sapui5.ts.app.control
*/
export default class CustomButton extends Button {
public static metadata = {
properties: {
customProperty: {
type: "string",
defaultValue: ""
}
},
events: {
customPress: {
parameters: {
value: { type: "string" }
}
}
}
};
// Property getter/setter
public getCustomProperty(): string {
return this.getProperty("customProperty") as string;
}
public setCustomProperty(value: string): this {
this.setProperty("customProperty", value);
return this;
}
// Custom renderer
public static renderer = {
...Button.renderer,
render: function(rm: RenderManager, control: CustomButton): void {
// Custom rendering logic
Button.renderer.render(rm, control);
}
};
/**
* Custom method
*/
public customMethod(): void {
this.fireEvent("customPress", {
value: this.getCustomProperty()
});
}
}
Type Definitions for Custom Types
src/types/index.d.ts:
declare namespace my.sapui5.ts.app {
// Custom types
interface Product {
id: string;
name: string;
price: number;
currency: string;
category: string;
status: "A" | "R" | "P";
}
interface AppConfiguration {
apiUrl: string;
timeout: number;
debug: boolean;
}
type StatusType = "A" | "R" | "P";
type ViewMode = "display" | "edit" | "create";
}
Best Practices
1. Use Strict Mode
Enable strict TypeScript checking:
{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true
}
}
2. Type Assertions
Use type assertions when type cannot be inferred:
const table = this.byId("productsTable") as Table;
const model = this.getView()?.getModel() as JSONModel;
3. Optional Chaining
Use optional chaining for nullable values:
const view = this.getView();
view?.setModel(model);
4. Generics with Models
Type your model data:
interface Product {
id: string;
name: string;
price: number;
}
const model = new JSONModel<Product[]>([]);
const data = model.getData(); // Type: Product[]
5. Event Typing
Type event handlers properly:
import Event from "sap/ui/base/Event";
import Input from "sap/m/Input";
public onChange(event: Event): void {
const input = event.getSource() as Input;
const value = input.getValue();
}
Common Issues
Issue: Cannot find module 'sap/...'
Solution: Install type definitions:
npm install --save-dev @sapui5/types
Add to tsconfig.json:
{
"compilerOptions": {
"types": ["@sapui5/types"]
}
}
Issue: Type errors with metadata
Solution: Use static metadata property:
export default class MyController extends Controller {
public static metadata = {
// metadata here
};
}
Issue: 'this' implicitly has type 'any'
Solution: Use arrow functions or bind 'this':
// Arrow function
.then((data) => {
this.processData(data);
});
// Bind
.then(function(data) {
this.processData(data);
}.bind(this));
Compatibility
TypeScript Version: 4.0+ (5.0+ recommended) SAPUI5 Version: 1.60+ (1.120+ recommended) Node.js Version: 16+ (18+ recommended)
Migration from JavaScript
Step 1: Rename Files
Rename .js files to .ts:
mv Component.js Component.ts
mv controller/Main.controller.js controller/Main.controller.ts
Step 2: Add Type Annotations
Add types gradually:
// Before (JS)
onPress: function(oEvent) {
var sValue = oEvent.getSource().getValue();
}
// After (TS)
public onPress(event: Event): void {
const source = event.getSource() as Input;
const value: string = source.getValue();
}
Step 3: Fix Type Errors
Address compilation errors one by one:
- Add missing imports
- Type function parameters
- Use type assertions where needed
- Enable strict mode gradually
Testing with TypeScript
QUnit Test (test/unit/model/formatter.ts):
import formatter from "my/sapui5/ts/app/model/formatter";
QUnit.module("Formatter Tests");
QUnit.test("Should format date correctly", (assert) => {
const date = new Date(2025, 0, 21);
const result = formatter.formatDate(date);
assert.ok(result.includes("21"), "Date formatted correctly");
});
QUnit.test("Should format currency", (assert) => {
const result = formatter.formatCurrency(100, "EUR");
assert.ok(result.includes("100"), "Currency formatted");
});
Official Resources
- TypeScript Documentation: https://github.com/SAP-docs/sapui5/tree/main/docs/02_Read-Me-First (search: typescript)
- UI5 TypeScript Tutorial: https://github.com/SAP-samples/ui5-typescript-tutorial
- TypeScript Guidelines: Official SAPUI5 TypeScript guidelines
- Sample Applications: TypeScript To-Do List demo
Advanced TypeScript Topics
For advanced TypeScript patterns including:
- Metadata-Driven Controls (MDC) with TypeScript
- Control library development in TypeScript
- Using
@ui5/ts-interface-generatorfor control APIs - Enum registration and library.ts patterns
See references/mdc-typescript-advanced.md for detailed implementation guides.
Note: This document covers TypeScript support in SAPUI5. The framework actively maintains type definitions, making TypeScript a viable choice for new projects and migrations.