
Angular Form Generator
Description
This is a simple form generator that can be used to generate forms in angular using material.
// silver-field.component.ts
import { CommonModule } from '@angular/common';
import { Component, Injectable, Input, Pipe, PipeTransform } from '@angular/core';
import { FormControl, FormGroup, FormsModule, ReactiveFormsModule, Validators } from '@angular/forms';
import { MatNativeDateModule } from '@angular/material/core';
import { MatDatepickerModule } from '@angular/material/datepicker';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { MatSelectModule } from '@angular/material/select';
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
import { MatSliderModule } from '@angular/material/slider';
export const SilverFieldTypes = {
SELECT: 'select',
RANGE: 'range',
TEXT: 'text',
TOGGLE: 'toggle',
DATE: 'date',
DATE_RANGE: 'date-range',
LONG_TEXT: 'long-text',
};
export class BaseField {
controlType!: string;
label!: string;
key!: string;
value: any;
valid: any;
}
export class SelectField extends BaseField {
override controlType = 'select';
multiple = false;
dropdown = true;
options!: { [key: string]: string };
}
export class TextField extends BaseField {
override controlType = 'text';
type!: string;
placeholder!: string;
}
export class TextArea extends BaseField {
override controlType = 'long-text';
rows!: number;
}
export class RangeField extends BaseField {
override controlType = 'range';
min!: number;
max!: number;
declare value: number;
}
export class ToggleField extends BaseField {
override controlType = 'toggle';
declare value: boolean;
}
export class DateField extends BaseField {
override controlType = 'date';
declare value: Date;
}
export class DateRangeField extends BaseField {
override controlType = 'date-range';
declare value: { from: Date; to: Date };
}
export type SilverField = SelectField | TextField | RangeField | ToggleField | DateField | DateRangeField | TextArea;
@Injectable({ providedIn: 'root' })
export class SilverFieldService {
public fieldsToForm(fields: BaseField[]) {
const formGroup = new FormGroup({});
fields.forEach((field) => {
const validations = [];
if (field.valid) {
const v = field.valid;
if (v.required) validations.push(Validators.required);
}
if (field.controlType === 'date-range') {
const range = new FormGroup({
from: new FormControl<Date | null>(field.value.from, validations),
to: new FormControl<Date | null>(field.value.to, validations),
});
formGroup.addControl(field.key, range);
} else {
formGroup.addControl(field.key, new FormControl(field.value, validations));
}
});
return formGroup;
}
}
@Pipe({ name: 'entries', standalone: true, pure: true })
export class EntriesPipe implements PipeTransform {
transform(value: any): [string, any][] {
return Object.entries(value);
}
}
@Component({
standalone: true,
imports: [
CommonModule,
EntriesPipe,
FormsModule,
MatDatepickerModule,
MatFormFieldModule,
MatInputModule,
MatNativeDateModule,
MatSelectModule,
MatSliderModule,
MatSlideToggleModule,
ReactiveFormsModule,
],
selector: 'app-silver-field',
template: `<ng-container [formGroup]="form">
<ng-container [ngSwitch]="field.controlType">
<mat-form-field *ngSwitchCase="'text'">
<input [formControl]="form.controls[ctrlName]" matInput [type]="field.type" [placeholder]="field.placeholder" />
</mat-form-field>
<mat-form-field *ngSwitchCase="'long-text'">
<textarea matInput [formControl]="form.controls[ctrlName]" [rows]="field.rows || 3"></textarea>
</mat-form-field>
<mat-form-field *ngSwitchCase="'select'">
<mat-select [multiple]="field.multiple" [formControl]="form.controls[ctrlName]">
<mat-option *ngFor="let opt of field.options | entries" [value]="opt[0]">{{ opt[1] }}</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field *ngSwitchCase="'date-range'">
<mat-date-range-input [formGroup]="form.controls[ctrlName]" [rangePicker]="picker">
<input matStartDate formControlName="from" placeholder="Start date" />
<input matEndDate formControlName="to" placeholder="End date" />
</mat-date-range-input>
<mat-hint>MM/DD/YYYY - MM/DD/YYYY</mat-hint>
<mat-datepicker-toggle matIconSuffix [for]="picker"></mat-datepicker-toggle>
<mat-date-range-picker #picker></mat-date-range-picker>
<mat-error *ngIf="form.controls[ctrlName].controls.from.hasError('matStartDateInvalid')"
>Invalid start date</mat-error
>
<mat-error *ngIf="form.controls[ctrlName].controls.to.hasError('matEndDateInvalid')"
>Invalid end date</mat-error
>
</mat-form-field>
<mat-form-field *ngSwitchCase="'date'" appearance="fill">
<input matInput [matDatepicker]="picker2" [formControl]="form.controls[ctrlName]" />
<mat-hint>MM/DD/YYYY</mat-hint>
<mat-datepicker-toggle matSuffix [for]="picker2"></mat-datepicker-toggle>
<mat-datepicker #picker2></mat-datepicker>
</mat-form-field>
<mat-slider thumbLabel class="mx-3" *ngSwitchCase="'range'" [min]="field.min" [max]="field.max">
<input matSliderThumb [formControl]="form.controls[ctrlName]" />
</mat-slider>
<div class="text-center" *ngSwitchCase="'toggle'">
<mat-slide-toggle [formControl]="form.controls[ctrlName]" [checked]="field.value"></mat-slide-toggle>
</div> </ng-container
></ng-container>`,
})
export class SilverFieldComponent {
@Input() field: SilverField;
@Input() form: FormGroup;
@Input() ctrlName!: string;
}
Usage
import { CommonModule } from '@angular/common';
import { HttpClientModule } from '@angular/common/http';
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
import { FormGroup, FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatIconModule } from '@angular/material/icon';
import { MatTabsModule } from '@angular/material/tabs';
import { MatTooltipModule } from '@angular/material/tooltip';
import {
SilverField,
SilverFieldComponent,
SilverFieldService,
SilverFieldTypes,
} from '../../components/silver-field.component';
import { DirectiveModule } from '../../directive/directive.module';
@Component({
selector: 'app-test',
standalone: true,
imports: [
CommonModule,
DirectiveModule,
FormsModule,
HttpClientModule,
MatIconModule,
MatTabsModule,
MatTooltipModule,
ReactiveFormsModule,
SilverFieldComponent,
],
template: `
<div class="grow flex flex-col">
<form [formGroup]="formGroup" class="w-[720px] m-auto" (ngSubmit)="submit()">
<div *ngFor="let item of form" [formGroupName]="item.key" class="flex flex-col">
<div>{{ item.label }}</div>
<app-silver-field
class="flex flex-col"
[field]="item"
[ctrlName]="item.key"
[form]="formGroup"
></app-silver-field>
</div>
<button type="submit">Submit</button>
</form>
</div>
`,
styles: [
`
:host {
overflow: auto;
}
`,
],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class TestComponent {
#silverFieldService = inject(SilverFieldService);
form: SilverField[];
formGroup: FormGroup<any>;
constructor() {
this.makeForm();
}
submit() {
console.log(this.formGroup.value);
}
async makeForm() {
this.form = [
{
controlType: SilverFieldTypes.LONG_TEXT,
label: 'Name',
placeholder: 'Enter Name',
type: 'text',
key: 'name',
value: '',
valid: { required: true },
},
{
controlType: SilverFieldTypes.TEXT,
label: 'Name',
placeholder: 'Enter Name',
type: 'text',
key: 'name',
value: '',
valid: { required: true },
},
{
controlType: SilverFieldTypes.TEXT,
label: 'Number',
placeholder: 'Enter Number',
type: 'number',
key: 'number',
value: '',
valid: { required: true },
},
{
controlType: SilverFieldTypes.SELECT,
label: 'Select',
key: 'select',
value: '',
valid: { required: true },
multiple: false,
dropdown: true,
options: {
option1: 'Option 1',
option2: 'Option 2',
option3: 'Option 3',
},
},
{
controlType: SilverFieldTypes.SELECT,
label: 'Select',
key: 'select',
value: '',
valid: { required: true },
multiple: true,
dropdown: true,
options: {
option1: 'Option 1',
option2: 'Option 2',
option3: 'Option 3',
},
},
{
controlType: SilverFieldTypes.DATE,
label: 'Date',
key: 'date',
value: new Date(),
valid: { required: true },
},
{
controlType: SilverFieldTypes.DATE_RANGE,
label: 'Date Range',
key: 'dateRange',
value: { from: new Date(), to: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000) },
valid: { required: true },
},
{
controlType: SilverFieldTypes.RANGE,
label: 'Range',
key: 'range',
value: 0,
min: 0,
max: 100,
valid: { required: true },
},
{
controlType: SilverFieldTypes.TOGGLE,
label: 'Toggle',
key: 'toggle',
value: false,
valid: { required: true },
},
];
this.formGroup = this.#silverFieldService.fieldsToForm(this.form);
// this.formGroup.valueChanges.subscribe((value) => {
// console.log(value);
// });
}
}