Files
gh-secondsky-sap-skills-ski…/references/typescript-support.md
2025-11-30 08:55:36 +08:00

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

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



Advanced TypeScript Topics

For advanced TypeScript patterns including:

  • Metadata-Driven Controls (MDC) with TypeScript
  • Control library development in TypeScript
  • Using @ui5/ts-interface-generator for 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.