Angularxsd-ui

xsd-ui

Schema-driven Angular forms for ISO 20022 message creation.

xsd-ui is an Angular enterprise library that builds a user interface directly from a JSON schema, with pre-built form components that comply with ISO 20022. It renders nested message structures recursively, applies the schema’s own validation rules, and hands back either JSON or XML — so adding a new message type is a schema change rather than a development project.

What it does

  • Schema-driven rendering for nested ISO 20022 structures
  • Reactive Forms architecture with recursive field generation
  • Built-in validation from schema rules — length, pattern, numeric constraints, enum
  • XML and JSON patching APIs (setXmlForm, setJsonForm)
  • Output shaping with optional empty-field omission
  • Event-based API access (onFormReadyEvent) without ViewChild
  • Standalone Angular components, ready for library consumption

Install

Peer dependency
ng add @angular/material@21
npm
npm install xsd-ui

Before you start

xsd-ui does not execute XSD directly. Convert the schema to JSON first — with the xsd-json-converter npm package, or the ISO20022.XSD .NET library.

Register the licence once

xsd-ui is commercially licensed. Register it at application startup rather than per-component, so every form inherits the same entitlement.

tsapp.config.ts
import {
  configureXsdUiLicense,
  XsdLicensePayload,
  XsdLicenseValidationResult,
  XsdLicenseValidator,
} from 'xsd-ui';

const validateLicenseOnServer: XsdLicenseValidator = async (
  licenseKey: string,
  payload: XsdLicensePayload,
): Promise<XsdLicenseValidationResult> => {
  const response = await fetch('/api/licenses/validate', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ licenseKey, payload }),
  });

  if (!response.ok) {
    return { valid: false, reason: 'License API unavailable.' };
  }

  return response.json() as Promise<XsdLicenseValidationResult>;
};

configureXsdUiLicense({
  licenseKey: '<your-license-key>',
  licenseValidator: validateLicenseOnServer,
  requireServerValidation: true,
  offlineFallbackOnServerError: true,
});

Use the component

tspayment-form.component.ts
import { Component } from '@angular/core';
import {
  XsdFormComponent,
  XsdLicenseValidationResult,
  XsdFormPublicApi,
  XsdFormSchema,
} from 'xsd-ui';

@Component({
  selector: 'app-payment-form',
  standalone: true,
  imports: [XsdFormComponent],
  template: `
    <xsd-form
      [schema]="schema"
      (onFormReadyEvent)="onFormReady($event)"
      (licenseValidationChange)="onLicenseValidationChange($event)"
      (change)="onFormChange()"
    />
    <button type="button" (click)="submit()">Submit</button>
  `,
})
export class PaymentFormComponent {
  schema!: XsdFormSchema;
  formApi: XsdFormPublicApi | null = null;

  onFormReady(api: XsdFormPublicApi): void {
    this.formApi = api;
  }

  onFormChange(): void {
    // Live updates, autosave, previews, etc.
  }

  onLicenseValidationChange(result: XsdLicenseValidationResult): void {
    if (!result.valid) {
      console.error(result.reason);
    }
  }

  submit(): void {
    if (!this.formApi) return;
    if (!this.formApi.isValid()) return;

    const payload = this.formApi.getForm(true);
    console.log(payload);
  }
}

Public API

Exposed through onFormReadyEvent — no ViewChild required.

ts
export interface XsdFormPublicApi {
  isValid(): boolean;                // Schema-aware validity (optional empty branches handled)
  getForm(omitEmpty?: boolean): any; // Output JSON in root-keyed structure
  setJsonForm(json: any): void;      // Patch using full root-keyed JSON or inner object
  setXmlForm(xml: string): void;     // Parse and patch ISO 20022 XML payloads
  getNamespace(): string;            // Schema namespace
  clearForm(): void;                 // Reset form values
  disableForm(): void;               // Disable all controls
  enableForm(): void;                // Enable all controls
}

interface XsdFormSchema {
  namespace: string;
  schemaElement: SchemaElement | SchemaElement[];
}

Core components

  • XsdFormComponent (xsd-form) — root orchestration component
  • XsdFieldComponent (xsd-field) — recursive field renderer

Inputs

InputTypeRequiredDescription
schemaXsdFormSchemaYesRoot schema including namespace and schemaElement.
licenseKeystringNoOverride for the globally registered key.
licenseValidatorXsdLicenseValidatorNoOverride for the global server validator.
requireServerValidationbooleanNoOverride for global server-validation enforcement.
offlineFallbackOnServerErrorbooleanNoAllow offline fallback when server validation errors out.
translationsTranslationConfigNoLabel and error message overrides.

Outputs

OutputPayloadDescription
onFormReadyEventXsdFormPublicApiEmits when the form API is ready, and on schema rebuild.
licenseValidationChangeXsdLicenseValidationResultEmits licence validation state and failure reason.
changevoidEmits on value changes and programmatic updates.

Validations supported

  • Required checks from minOccurs
  • String length — minLength, maxLength
  • Regex pattern
  • Numeric boundaries — minInclusive, maxInclusive, minExclusive, maxExclusive
  • fractionDigits and totalDigits
  • Enumerations (values)
  • Choice structures and unbounded arrays

XML and JSON patching

Patch an existing message into the form from either representation.

tsJSON patch
formApi.setJsonForm({
  FIToFICstmrCdtTrf: {
    GrpHdr: {
      MsgId: 'MSG-001',
      CreDtTm: '2026-04-25T12:41:52.673+08:00',
      NbOfTxs: '1'
    }
  }
});
tsXML patch
formApi.setXmlForm(`
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:pacs.008.001.08">
  <FIToFICstmrCdtTrf>
    <GrpHdr>
      <MsgId>XML-PATCH-001</MsgId>
      <NbOfTxs>1</NbOfTxs>
    </GrpHdr>
  </FIToFICstmrCdtTrf>
</Document>`);

Licensing best practice

For browser libraries, client-side checks alone are not tamper-proof. The recommended production setup:

When implementing licenseValidator, return { valid: false } for explicit entitlement rejection — revoked, over-limit, wrong tenant — and throw for network or server failures. That is how xsd-ui tells a deny apart from a fallback.

  • Register licensing once at app startup with configureXsdUiLicense(...)
  • Keep signed offline key verification enabled — licenseKey plus RSA signature
  • Add backend entitlement validation using licenseValidator
  • Set requireServerValidation to true in production
  • Keep offlineFallbackOnServerError enabled for graceful offline operation
  • Return short-lived entitlements from your backend, and rotate or revoke there

Schema element shape

ts
export interface SchemaElement {
  id: string;
  name: string;
  dataType: string;
  minOccurs: string;
  maxOccurs: string;
  minLength: string;
  maxLength: string;
  pattern: string;
  fractionDigits: string;
  totalDigits: string;
  minInclusive: string;
  maxInclusive: string;
  values: string[];
  isCurrency: boolean;
  xpath: string;
  expanded: boolean;
  elements: SchemaElement[];
}

Translation support

Translations key off the name and id properties of a SchemaElement. Declare all rules under an "iso" object.

json
{
  "iso": {
    "BkToCstmrStmt": {
      "label": "Bank To Customer Statement"
    },
    "GrpHdr": {
      "label": "Group Header"
    },
    "Document_BkToCstmrStmt_GrpHdr_CreDtTm": {
      "label": "Create Datetime",
      "general": {
        "format": "YYYY-MM-DDThh:mm:ss.sss+/-"
      },
      "error": {
        "required": "This field is required"
      }
    }
  }
}
Next step

Running this in production?

We wrote these libraries and we run them. If you are planning an ISO 20022 migration, we can tell you quickly where the hard parts actually are.