Angular  

Creating a Domain-Specific UI DSL in Angular (define screens using JSON → auto-generate component)

1. Introduction

A Domain-Specific Language (DSL) is a mini-language created for a specific domain. In our case, the domain is “UI screen design”. Instead of writing:

  • HTML

  • TypeScript

  • Angular components

  • Services

  • FormGroups

Developers write

{
  "title": "Create Employee",
  "type": "form",
  "fields": [
    { "label": "First Name", "type": "text", "model": "firstName", "required": true },
    { "label": "Age", "type": "number", "model": "age" }
  ],
  "actions": [
    { "label": "Save", "action": "saveEmployee" }
  ]
}

Angular interprets this JSON and constructs a fully working screen.

This approach has become common in low-code platforms and internal tooling frameworks. With Angular 17’s standalone APIs and improved dynamic component capabilities, building DSL-driven screens is easier than ever.

2. Why a UI DSL Is Needed

2.1 Repeated UI Patterns

Most enterprise screens follow similar structures: forms, tables, sections, tabs, filters, detail panes. A DSL allows defining these structures once and reusing them everywhere.

2.2 Faster Development

New screens can be created in minutes just by writing JSON configuration. No new Angular components are needed.

2.3 Centralised Governance

Validation, accessibility rules, grid styling, date formats, and error messages are all governed centrally. Changes apply to all screens instantly.

2.4 Business Users Can Contribute

A non-developer can create screens without Angular expertise, as long as they follow the JSON schema.

2.5 Easier Maintenance

Instead of tracking many components, you track one renderer engine and many JSON configurations.

3. Architecture Overview

A UI DSL system for Angular has four major layers:

1. DSL Definition Layer

Defines the JSON schema: field types, layout instructions, actions, validation structure, and events.

2. Parser and Validator

Reads the JSON, validates it, and converts it into an internal meta-model.

3. Angular Renderer Engine

Creates UI dynamically using Angular’s component factories, templates, and dynamic bindings.

4. Data and Event Engine

Handles

  • form binding

  • event triggers

  • API communication

  • conditional visibility

  • computed expressions

This architecture separates “what to build” (JSON) from “how to build it” (Angular engine).

4. Workflow Diagram

(Plain text format suitable for articles)

                   +----------------------+
                   |    JSON UI Schema    |
                   +----------+-----------+
                              |
                              v
              +---------------+---------------+
              |     DSL Parser & Validator    |
              +---------------+---------------+
                              |
                              v
              +---------------+---------------+
              |     Meta-Model Generator      |
              +---------------+---------------+
                              |
                              v
              +---------------+---------------+
              |  Angular Renderer Engine      |
              +---------------+---------------+
                              |
                              v
                 +------------+-------------+
                 |   Dynamic UI Component   |
                 +--------------------------+

5. Flowchart

(Explains runtime behaviour)

                 Start
                   |
                   v
        Load JSON configuration
                   |
                   v
        Validate configuration?
           /             \
         Yes              No
         |                |
         v                v
   Generate meta-model   Throw error
         |
         v
  Render components dynamically
         |
         v
 Bind FormGroups and events
         |
         v
   User interacts with UI
         |
         v
 Trigger events → call API or logic
         |
         v
                End

6. Designing the JSON DSL

Designing the DSL is the most important step. It determines the flexibility of your system.

Below is a practical JSON DSL structure.

6.1 Screen-Level Structure

{
  "title": "Employee Form",
  "type": "form",
  "layout": "two-column",
  "fields": [...],
  "actions": [...],
  "api": {
    "load": "/api/employee/:id",
    "submit": "/api/employee"
  }
}

6.2 Field Definition

{
  "label": "Email Address",
  "type": "email",
  "model": "email",
  "required": true,
  "placeholder": "Enter email",
  "validationMessage": "Valid email required"
}

6.3 Supported Field Types

Keep it standardised:

  • text

  • number

  • email

  • dropdown

  • checkbox

  • radio

  • date

  • textarea

  • table

  • section

  • tab

6.4 Action Definition

{
  "label": "Submit",
  "type": "primary",
  "action": "submitForm",
  "confirm": true
}

6.5 Conditional Rules

{
  "model": "salary",
  "visible": "employeeType == 'FullTime'"
}

The rule can be interpreted using a simple expression parser.

6.6 Reusable Components

{
  "type": "component",
  "componentName": "AddressBlock",
  "input": { "prefix": "permanent_" }
}

7. Building the Angular Runtime Component Generator

This is the core engine. It reads the meta-model and renders UI.

7.1 High-Level Responsibilities

The generator must:

  • interpret JSON

  • create Angular components dynamically

  • construct templates

  • manage FormGroup and FormControls

  • inject validation rules

  • handle conditional visibility

  • handle events

7.2 Implementation Steps

Step 1: Create a Generic Component

This component is the host for all generated UI.

@Component({
  selector: 'app-dsl-screen',
  template: `<ng-template #container></ng-template>`
})
export class DslScreenComponent {
  @Input() schema: any;
  @ViewChild('container', { read: ViewContainerRef }) container!: ViewContainerRef;
}

Step 2: Create a Registry of UI Elements

Each field type is mapped to a renderer function.

export const FIELD_REGISTRY = {
  text: TextFieldRenderer,
  number: NumberFieldRenderer,
  dropdown: DropdownRenderer
};

Step 3: Render Fields Dynamically

const renderer = FIELD_REGISTRY[field.type];
renderer(field, viewContainerRef, formGroup);

Step 4: Dynamic Components Using Angular APIs

const componentRef = viewContainer.createComponent(TextInputComponent);
componentRef.setInput('config', field);

This enables runtime construction of any element.

7.3 Building a Dynamic Layout System

Automatic layouts:

  • single column

  • two column

  • grid

  • responsive

Use CSS grid for predictable results.

8. Implementing a Renderer Engine

The renderer engine has sub-engines:

8.1 Form Engine

Creates FormGroup:

for (const f of schema.fields) {
  group[f.model] = new FormControl('', getValidators(f));
}

8.2 Event Engine

Supports custom event handlers:

if (action.action === 'submitForm') {
  this.submitForm();
}

Or dynamic functions using a registry:

actionRegistry.execute(action.action, formValue);

8.3 API Engine

Handles load/submit:

if (schema.api?.load) {
  this.http.get(schema.api.load).subscribe(...)
}

9. Handling Forms, Validation, and Events

9.1 Validation Rules

Extract validators from DSL:

function getValidators(f) {
  const validators = [];
  if (f.required) validators.push(Validators.required);
  if (f.maxLength) validators.push(Validators.maxLength(f.maxLength));
  return validators;
}

9.2 Conditional Visibility

Evaluated during change detection:

visible = evaluateExpression(field.visible, formGroup.value);

Use a simple safe expression evaluator.

9.3 Component-Level Events

Supported events:

  • onClick

  • onChange

  • onLoad

  • onSubmit

10. Incorporating Angular Best Practices

Use Standalone Components

Avoid bloated modules.

Lazy Load DSL Screens

Each DSL screen can be lazy loaded via route resolver.

Use Signals for Reactive Behaviour

Angular signals simplify dynamic updates.

Reuse Common Components

Keep renderer clean using shared UI components.

Push Change Detection

To avoid re-rendering multiple dynamic elements.

11. Optimisation and Performance

1. Cache Rendered Templates

If a DSL screen is used repeatedly (common in admin panels), store pre-parsed meta-models.

2. Minimise DOM Nodes

Use structural directives only when necessary.

3. Avoid Recalculating Expressions

Cache visibility rules or compute only on relevant model changes.

4. Pagination for Tables

Large JSON-driven tables should use server-side paging.

12. Security Considerations

1. Avoid Dynamic Code Execution

Never allow raw JavaScript execution inside DSL expressions.

2. Validate JSON on Server

Only whitelisted keys should be allowed.

3. Escape Content

All text shown in UI must be escaped to prevent injection.

4. Limit API Call Action Names

Events must map to predefined functions.

13. Real-World Use Cases

  1. Admin Consoles
    Most admin screens are repetitive; DSL saves huge development time.

  2. Configurable Portals
    Business teams can add fields or screens without code changes.

  3. Workflow-Based Applications
    Each workflow stage can define its own UI form via JSON.

  4. Multi-Tenant Applications
    Different tenants can have customised screens without code branching.

  5. Low-Code Accelerators
    DSL forms the base of a internal low-code builder.

14. Limitations and How to Improve Over Time

Limitations

  • Difficult to support highly custom UI.

  • Debugging becomes indirect since UI is not written in Angular template.

  • Performance may degrade for very complex JSON screens.

  • Expression logic must remain safe and restricted.

Improvements

  • Add a visual JSON screen builder.

  • Add a plugin system for custom field types.

  • Add versioning for JSON schemas.

  • Precompile frequently used screens into Angular modules.

15. Conclusion

Building a Domain-Specific UI DSL for Angular is a powerful way to standardise, accelerate, and govern UI development in enterprise applications. Instead of writing repetitive components, teams define screens using JSON. Angular dynamically generates complete components with forms, validations, layouts, and events.

The architecture requires a well-designed DSL, a parser, and a robust renderer engine. Once in place, it becomes a long-term productivity multiplier.

You now have a complete guide with architecture, workflow diagrams, flowcharts, JSON DSL examples, Angular implementation details, and production considerations.