
Security News
GitHub Actions Checkout Now Blocks Risky pull_request_target Checkouts
GitHub Actions checkout now blocks risky pull_request_target checkouts by default to help prevent pwn request supply chain attacks.
@expeed/ngx-dyna-form
Advanced tools
Dynamic form component that renders forms from JSON Schema with x-display-type extensions
A dynamic form renderer for Angular that builds reactive forms from JSON Schema definitions. Supports various input types, validation, nested objects, and arrays.
x-display-type to control input renderingnpm install @expeed/ngx-dyna-form
npm install @angular/cdk @angular/material ngx-quill quill
Add Quill styles to your angular.json:
{
"styles": [
"node_modules/quill/dist/quill.core.css",
"node_modules/quill/dist/quill.snow.css"
]
}
import { Component, signal } from '@angular/core';
import { DynamicFormComponent, DynamicFormSchema } from '@expeed/ngx-dyna-form';
@Component({
selector: 'app-form',
standalone: true,
imports: [DynamicFormComponent],
template: `
<dyna-form
[schema]="schema"
[values]="formValues()"
(valuesChange)="onValuesChange($event)"
(validChange)="onValidChange($event)"
/>
<button [disabled]="!isValid()" (click)="submit()">
Submit
</button>
`
})
export class FormComponent {
schema: DynamicFormSchema = {
type: 'object',
title: 'Contact Form',
properties: {
name: {
type: 'string',
title: 'Full Name',
minLength: 2
},
email: {
type: 'string',
title: 'Email',
format: 'email' // Auto-renders as email input with validation
},
message: {
type: 'string',
title: 'Message',
'x-display-type': 'textarea'
}
},
required: ['name', 'email']
};
formValues = signal<Record<string, unknown>>({});
isValid = signal(false);
onValuesChange(values: Record<string, unknown>): void {
this.formValues.set(values);
}
onValidChange(valid: boolean): void {
this.isValid.set(valid);
}
submit(): void {
console.log('Submitting:', this.formValues());
}
}
<dyna-form
[schema]="schema"
[values]="formValues()"
[columns]="2"
[defaultFieldColumns]="1"
[fieldColumns]="{ 'description': 2, 'address.street': 2 }"
[excludeFields]="['internalId', 'metadata']"
(valuesChange)="onValuesChange($event)"
(validChange)="onValidChange($event)"
/>
import { ViewChild } from '@angular/core';
import { DynamicFormComponent } from '@expeed/ngx-dyna-form';
@ViewChild(DynamicFormComponent) dynaForm!: DynamicFormComponent;
submit(): void {
if (this.dynaForm.isValid()) {
const data = this.dynaForm.getValue();
this.apiService.save(data).subscribe();
} else {
this.dynaForm.markAllTouched(); // Show validation errors
}
}
| Input | Type | Default | Description |
|---|---|---|---|
schema | DynamicFormSchema | null | JSON Schema defining the form |
values | Record<string, unknown> | {} | Initial form values |
columns | 1 | 2 | 2 | Number of grid columns |
defaultFieldColumns | number | 1 | Default column span for fields |
fieldColumns | Record<string, number> | {} | Per-field column span overrides |
excludeFields | string[] | [] | Field paths to exclude |
emptyMessage | string | '' | Message when no fields |
| Output | Type | Description |
|---|---|---|
valuesChange | EventEmitter<Record<string, unknown>> | Emits on value changes |
validChange | EventEmitter<boolean> | Emits on validity changes |
| Method | Returns | Description |
|---|---|---|
getValue() | Record<string, unknown> | Get current form values |
isValid() | boolean | Check if form is valid |
markAllTouched() | void | Mark all fields as touched |
The DynamicFormSchema follows JSON Schema (draft 2020-12) with x-display-type extension:
interface DynamicFormSchema {
type?: 'string' | 'number' | 'integer' | 'boolean' | 'object' | 'array';
title?: string; // Field label
description?: string; // Help text shown below field
default?: unknown; // Default value
// Object properties
properties?: Record<string, DynamicFormSchema>;
required?: string[]; // Required field names
// Array properties
items?: DynamicFormSchema; // Schema for array items
minItems?: number;
maxItems?: number;
// String constraints
minLength?: number;
maxLength?: number;
pattern?: string; // Regex pattern
format?: 'email' | 'uri' | 'url' | 'date' | 'date-time' | 'time';
// Number constraints
minimum?: number;
maximum?: number;
// Allowed values
enum?: unknown[]; // Simple values
oneOf?: Array<{ const: unknown; title?: string }>; // Labeled values
// Display hint
'x-display-type'?: string; // See Display Types table
}
Note: For arrays with enum options (multiselect), place enum/oneOf on items and x-display-type on the array:
{
type: 'array',
title: 'Colors',
'x-display-type': 'multiselect', // Display type on array
items: {
type: 'string',
enum: ['red', 'green', 'blue'] // Options on items
}
}
Control how fields are rendered using x-display-type:
| Display Type | Description | Field Type |
|---|---|---|
textbox | Single-line text input | string |
textarea | Multi-line text input | string |
richtext | Quill rich text editor | string |
dropdown | Select dropdown | string (enum) |
radio | Radio button group | string (enum) |
datepicker | Date picker | date |
datetimepicker | Date and time picker | date |
timepicker | Time picker | time |
stepper | Number stepper with +/- | number |
checkbox | Checkbox | boolean |
toggle | Toggle switch | boolean |
multiselect | Checkbox group | array (enum) |
multiselect-dropdown | Dropdown with checkboxes | array (enum) |
tags | Tag input | array (string) |
The following display types are automatically inferred from schema properties:
| Schema | Inferred Display Type | Renders As |
|---|---|---|
format: 'email' | email | <input type="email"> |
format: 'uri' / 'url' | url | <input type="url"> |
format: 'date' / 'date-time' | datepicker | <input type="date"> |
format: 'time' | timepicker | <input type="time"> |
type: 'array' + items.enum | multiselect | Checkbox group |
type: 'array' + items.type: 'string' | tags | Tag input |
type: 'array' + items.type: 'object' | array-objects | Repeatable fieldset |
type: 'object' + properties | fieldset | Nested fieldset |
const schema: DynamicFormSchema = {
type: 'object',
properties: {
bio: {
type: 'string',
title: 'Biography',
'x-display-type': 'richtext'
},
email: {
type: 'string',
title: 'Email',
format: 'email' // Auto-infers email input type
},
website: {
type: 'string',
title: 'Website',
format: 'uri' // Auto-infers url input type
},
birthDate: {
type: 'string',
title: 'Birth Date',
format: 'date' // Auto-infers datepicker
},
country: {
type: 'string',
title: 'Country',
enum: ['US', 'UK', 'CA'],
'x-display-type': 'dropdown'
},
notifications: {
type: 'boolean',
title: 'Enable Notifications',
'x-display-type': 'toggle'
},
age: {
type: 'integer',
title: 'Age',
minimum: 0,
maximum: 150,
'x-display-type': 'stepper'
},
skills: {
type: 'array',
title: 'Skills',
items: { type: 'string' } // Auto-infers tags input
}
}
};
Define allowed values for dropdowns, radio buttons, and multiselect fields:
// Simple enum - values are used as both value and label
{
type: 'string',
title: 'Size',
enum: ['small', 'medium', 'large'],
'x-display-type': 'dropdown'
}
// Labeled values using oneOf - separate value and display label
{
type: 'string',
title: 'Size',
oneOf: [
{ const: 'sm', title: 'Small' },
{ const: 'md', title: 'Medium' },
{ const: 'lg', title: 'Large' }
],
'x-display-type': 'dropdown'
}
// Multiselect with labeled values
{
type: 'array',
title: 'Categories',
items: {
type: 'string',
oneOf: [
{ const: 'tech', title: 'Technology' },
{ const: 'health', title: 'Healthcare' },
{ const: 'finance', title: 'Finance' }
]
},
'x-display-type': 'multiselect'
}
The oneOf format allows the form to display user-friendly labels while storing machine-readable values.
Nested objects render as fieldsets:
const schema: DynamicFormSchema = {
type: 'object',
properties: {
name: { type: 'string', title: 'Name' },
address: {
type: 'object',
title: 'Address',
properties: {
street: { type: 'string', title: 'Street' },
city: { type: 'string', title: 'City' },
zip: { type: 'string', title: 'ZIP Code' }
},
required: ['street', 'city']
}
}
};
Arrays of objects render with add/remove functionality:
const schema: DynamicFormSchema = {
type: 'object',
properties: {
contacts: {
type: 'array',
title: 'Contacts',
minItems: 1,
maxItems: 5,
items: {
type: 'object',
properties: {
name: { type: 'string', title: 'Name' },
email: { type: 'string', title: 'Email', format: 'email' }
},
required: ['name']
}
}
}
};
Exclude fields using paths with wildcard support:
// Exclude specific fields
excludeFields: ['password', 'internalId']
// Exclude nested fields
excludeFields: ['address.country', 'metadata.createdAt']
// Exclude array item fields (wildcard)
excludeFields: ['contacts[*].phone']
Validators are automatically applied from schema constraints:
| Schema Property | Validator |
|---|---|
required (in parent) | Required |
minLength | Min length |
maxLength | Max length |
pattern | Regex pattern |
minimum | Min value |
maximum | Max value |
format: 'email' | Email format |
format: 'uri' | URL format |
Customize with CSS custom properties:
dyna-form {
--df-color-primary: #3b82f6;
--df-color-error: #ef4444;
--df-color-text-primary: #1f2937;
--df-color-text-secondary: #6b7280;
--df-color-border: #e5e7eb;
--df-color-surface: #ffffff;
--df-radius-default: 0.375rem;
--df-font-size-sm: 0.875rem;
}
// Components
export { DynamicFormComponent } from './lib/components/dynamic-form/dynamic-form.component';
// Types
export { DynamicFormSchema } from './lib/components/dynamic-form/dynamic-form.component';
export { FormField } from './lib/components/dynamic-form/dynamic-form.component';
Apache 2.0
FAQs
Dynamic form component that renders forms from JSON Schema with x-display-type extensions
We found that @expeed/ngx-dyna-form demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
GitHub Actions checkout now blocks risky pull_request_target checkouts by default to help prevent pwn request supply chain attacks.

Product
Socket now supports Custom Roles and Repository Access Permissions so organizations can control who can access specific repositories and actions.

Product
Socket MCP now lets AI assistants review org alerts, investigate threats using the Socket threat feed, and inspect package files in addition to dependency scoring.