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
Admin Consoles
Most admin screens are repetitive; DSL saves huge development time.
Configurable Portals
Business teams can add fields or screens without code changes.
Workflow-Based Applications
Each workflow stage can define its own UI form via JSON.
Multi-Tenant Applications
Different tenants can have customised screens without code branching.
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.