angular-dynamic-forms-ai
Version:
AI-enhanced dynamic form generator for Angular using Template-Driven Forms
444 lines (421 loc) • 25.8 kB
JavaScript
import * as i0 from '@angular/core';
import { Injectable, EventEmitter, Component, Input, Output, NgModule } from '@angular/core';
import * as i2 from '@angular/common';
import { CommonModule } from '@angular/common';
import * as i3 from '@angular/forms';
import { FormsModule } from '@angular/forms';
class AiValidationService {
validationRules = new Map([
['email', /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/],
['phone', /^\+?[\d\s-]{10,}$/],
['url', /^(https?:\/\/)?([\da-z.-]+)\.([a-z.]{2,6})([\/\w .-]*)*\/?$/],
['zipcode', /^\d{5}(-\d{4})?$/],
]);
suggestValidations(field) {
const fieldNameLower = field.name.toLowerCase();
const validations = { ...field.validations } || {};
// Email validation
if (fieldNameLower.includes('email')) {
validations.email = true;
validations.required = true;
field.type = 'email';
}
// Password validation
if (fieldNameLower.includes('password')) {
validations.required = true;
validations.minLength = 6;
field.type = 'password';
validations.pattern = '^(?=.*[A-Za-z])(?=.*\\d)[A-Za-z\\d]{6,}$';
}
// Phone validation
if (fieldNameLower.includes('phone')) {
validations.pattern = this.validationRules.get('phone')?.toString().slice(1, -1);
validations.required = true;
}
// Name validation
if (fieldNameLower.includes('name')) {
validations.required = true;
validations.minLength = 2;
}
// URL validation
if (fieldNameLower.includes('url') || fieldNameLower.includes('website')) {
validations.pattern = this.validationRules.get('url')?.toString().slice(1, -1);
}
// Zip code validation
if (fieldNameLower.includes('zip')) {
validations.pattern = this.validationRules.get('zipcode')?.toString().slice(1, -1);
}
field.validations = validations;
field.errorMessages = this.generateErrorMessages(field);
return field;
}
generateErrorMessages(field) {
const messages = {};
const validations = field.validations || {};
if (validations.required) {
messages.required = `${field.label} is required`;
}
if (validations.minLength) {
messages.minlength = `${field.label} must be at least ${validations.minLength} characters`;
}
if (validations.maxLength) {
messages.maxlength = `${field.label} cannot exceed ${validations.maxLength} characters`;
}
if (validations.email) {
messages.email = `Please enter a valid email address`;
}
if (validations.pattern) {
messages.pattern = this.getPatternErrorMessage(field.name);
}
return messages;
}
getPatternErrorMessage(fieldName) {
const fieldNameLower = fieldName.toLowerCase();
if (fieldNameLower.includes('password')) {
return 'Password must contain at least one letter and one number';
}
if (fieldNameLower.includes('phone')) {
return 'Please enter a valid phone number';
}
if (fieldNameLower.includes('url') || fieldNameLower.includes('website')) {
return 'Please enter a valid URL';
}
if (fieldNameLower.includes('zip')) {
return 'Please enter a valid ZIP code';
}
return 'Please enter a valid value';
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.0.6", ngImport: i0, type: AiValidationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.0.6", ngImport: i0, type: AiValidationService, providedIn: 'root' });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.0.6", ngImport: i0, type: AiValidationService, decorators: [{
type: Injectable,
args: [{
providedIn: 'root'
}]
}] });
class DynamicFormComponent {
aiValidationService;
formConfig;
formSubmit = new EventEmitter();
constructor(aiValidationService) {
this.aiValidationService = aiValidationService;
}
ngOnInit() {
// Apply AI-based validation rules to each field
this.formConfig.fields = this.formConfig.fields.map(field => this.aiValidationService.suggestValidations(field));
}
isFieldVisible(field) {
if (!field.conditionalDisplay) {
return true;
}
const dependentField = this.formConfig.fields.find(f => f.name === field.conditionalDisplay?.dependsOn);
return dependentField?.value === field.conditionalDisplay.showWhen;
}
onSubmit(form) {
if (form.valid) {
const formData = this.formConfig.fields.reduce((acc, field) => {
if (this.isFieldVisible(field)) {
acc[field.name] = field.value;
}
return acc;
}, {});
this.formSubmit.emit(formData);
}
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.0.6", ngImport: i0, type: DynamicFormComponent, deps: [{ token: AiValidationService }], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.0.6", type: DynamicFormComponent, selector: "lib-dynamic-form", inputs: { formConfig: "formConfig" }, outputs: { formSubmit: "formSubmit" }, ngImport: i0, template: `
<form
<div *ngFor="let field of formConfig.fields" class="form-field" [ngClass]="{'hidden': !isFieldVisible(field)}">
<label [for]="field.name" class="form-label">
{{ field.label }}
<span *ngIf="field.validations?.required" class="required-marker">*</span>
</label>
<ng-container [ngSwitch]="field.type">
<!-- Text, Email, Number, Password inputs -->
<input *ngSwitchCase="'text'"
[]="field.type"
[]="field.name"
[]="field.name"
[]="field.placeholder || ''"
[(ngModel)]="field.value"
[]="field.validations?.required"
[]="field.validations?.minLength"
[]="field.validations?.maxLength"
[]="field.validations?.pattern"
[]="field.validations?.email"
class="form-input"
[]="{'error': fieldModel.invalid && (fieldModel.dirty || fieldModel.touched)}"
[]="fieldModel.invalid && (fieldModel.dirty || fieldModel.touched)"
[]="field.name + '-error'">
<input *ngSwitchCase="'email'"
type="email"
[]="field.name"
[]="field.name"
[]="field.placeholder || ''"
[(ngModel)]="field.value"
[]="field.validations?.required"
[]="true"
class="form-input"
[]="{'error': fieldModel.invalid && (fieldModel.dirty || fieldModel.touched)}"
[]="fieldModel.invalid && (fieldModel.dirty || fieldModel.touched)"
[]="field.name + '-error'">
<input *ngSwitchCase="'number'"
type="number"
[]="field.name"
[]="field.name"
[]="field.placeholder || ''"
[(ngModel)]="field.value"
[]="field.validations?.required"
class="form-input"
[]="{'error': fieldModel.invalid && (fieldModel.dirty || fieldModel.touched)}"
[]="fieldModel.invalid && (fieldModel.dirty || fieldModel.touched)"
[]="field.name + '-error'">
<input *ngSwitchCase="'password'"
type="password"
[]="field.name"
[]="field.name"
[]="field.placeholder || ''"
[(ngModel)]="field.value"
[]="field.validations?.required"
[]="field.validations?.minLength"
[]="field.validations?.pattern"
class="form-input"
[]="{'error': fieldModel.invalid && (fieldModel.dirty || fieldModel.touched)}"
[]="fieldModel.invalid && (fieldModel.dirty || fieldModel.touched)"
[]="field.name + '-error'">
<!-- Select dropdown -->
<select *ngSwitchCase="'select'"
[]="field.name"
[]="field.name"
[(ngModel)]="field.value"
[]="field.validations?.required"
class="form-select"
[]="{'error': fieldModel.invalid && (fieldModel.dirty || fieldModel.touched)}"
[]="fieldModel.invalid && (fieldModel.dirty || fieldModel.touched)"
[]="field.name + '-error'">
<option value="">Select {{ field.label }}</option>
<option *ngFor="let option of field.options" [value]="option.value">
{{ option.label }}
</option>
</select>
<!-- Textarea -->
<textarea *ngSwitchCase="'textarea'"
[]="field.name"
[]="field.name"
[]="field.placeholder || ''"
[(ngModel)]="field.value"
[]="field.validations?.required"
[]="field.validations?.minLength"
[]="field.validations?.maxLength"
class="form-textarea"
[]="{'error': fieldModel.invalid && (fieldModel.dirty || fieldModel.touched)}"
[]="fieldModel.invalid && (fieldModel.dirty || fieldModel.touched)"
[]="field.name + '-error'">
</textarea>
</ng-container>
<!-- Error messages -->
<div *ngIf="fieldModel.invalid && (fieldModel.dirty || fieldModel.touched)"
class="error-messages"
[]="field.name + '-error'"
role="alert">
<div *ngIf="fieldModel.errors?.required">
{{ field.errorMessages?.required }}
</div>
<div *ngIf="fieldModel.errors?.minlength">
{{ field.errorMessages?.minlength }}
</div>
<div *ngIf="fieldModel.errors?.maxlength">
{{ field.errorMessages?.maxlength }}
</div>
<div *ngIf="fieldModel.errors?.email">
{{ field.errorMessages?.email }}
</div>
<div *ngIf="fieldModel.errors?.pattern">
{{ field.errorMessages?.pattern }}
</div>
</div>
</div>
<button type="submit"
[]="!form.valid"
[]="formConfig.submitButton?.class || 'submit-button'"
[]="!form.valid">
{{ formConfig.submitButton?.text || 'Submit' }}
</button>
</form>
`, isInline: true, styles: [".dynamic-form{max-width:600px;margin:0 auto;padding:20px;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif}.form-field{margin-bottom:20px}.form-label{display:block;margin-bottom:8px;font-weight:500;color:#333}.required-marker{color:#dc3545;margin-left:4px}.form-input,.form-select,.form-textarea{width:100%;padding:8px 12px;border:1px solid #ced4da;border-radius:4px;font-size:16px;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}.form-textarea{min-height:100px;resize:vertical}.form-input:focus,.form-select:focus,.form-textarea:focus{border-color:#80bdff;outline:0;box-shadow:0 0 0 .2rem #007bff40}.error{border-color:#dc3545}.error:focus{border-color:#dc3545;box-shadow:0 0 0 .2rem #dc354540}.error-messages{margin-top:5px;color:#dc3545;font-size:14px}.hidden{display:none}.submit-button{background-color:#007bff;color:#fff;padding:10px 20px;border:none;border-radius:4px;font-size:16px;cursor:pointer;transition:background-color .15s ease-in-out}.submit-button:hover:not(:disabled){background-color:#0056b3}.submit-button:disabled{background-color:#6c757d;cursor:not-allowed;opacity:.65}@media (max-width: 768px){.dynamic-form{padding:15px}}\n"], dependencies: [{ kind: "directive", type: i2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2.NgSwitch, selector: "[ngSwitch]", inputs: ["ngSwitch"] }, { kind: "directive", type: i2.NgSwitchCase, selector: "[ngSwitchCase]", inputs: ["ngSwitchCase"] }, { kind: "directive", type: i3.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i3.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i3.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i3.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i3.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i3.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i3.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i3.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i3.MinLengthValidator, selector: "[minlength][formControlName],[minlength][formControl],[minlength][ngModel]", inputs: ["minlength"] }, { kind: "directive", type: i3.MaxLengthValidator, selector: "[maxlength][formControlName],[maxlength][formControl],[maxlength][ngModel]", inputs: ["maxlength"] }, { kind: "directive", type: i3.PatternValidator, selector: "[pattern][formControlName],[pattern][formControl],[pattern][ngModel]", inputs: ["pattern"] }, { kind: "directive", type: i3.EmailValidator, selector: "[email][formControlName],[email][formControl],[email][ngModel]", inputs: ["email"] }, { kind: "directive", type: i3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i3.NgForm, selector: "form:not([ngNoForm]):not([formGroup]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }] });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.0.6", ngImport: i0, type: DynamicFormComponent, decorators: [{
type: Component,
args: [{ selector: 'lib-dynamic-form', template: `
<form
<div *ngFor="let field of formConfig.fields" class="form-field" [ngClass]="{'hidden': !isFieldVisible(field)}">
<label [for]="field.name" class="form-label">
{{ field.label }}
<span *ngIf="field.validations?.required" class="required-marker">*</span>
</label>
<ng-container [ngSwitch]="field.type">
<!-- Text, Email, Number, Password inputs -->
<input *ngSwitchCase="'text'"
[]="field.type"
[]="field.name"
[]="field.name"
[]="field.placeholder || ''"
[(ngModel)]="field.value"
[]="field.validations?.required"
[]="field.validations?.minLength"
[]="field.validations?.maxLength"
[]="field.validations?.pattern"
[]="field.validations?.email"
class="form-input"
[]="{'error': fieldModel.invalid && (fieldModel.dirty || fieldModel.touched)}"
[]="fieldModel.invalid && (fieldModel.dirty || fieldModel.touched)"
[]="field.name + '-error'">
<input *ngSwitchCase="'email'"
type="email"
[]="field.name"
[]="field.name"
[]="field.placeholder || ''"
[(ngModel)]="field.value"
[]="field.validations?.required"
[]="true"
class="form-input"
[]="{'error': fieldModel.invalid && (fieldModel.dirty || fieldModel.touched)}"
[]="fieldModel.invalid && (fieldModel.dirty || fieldModel.touched)"
[]="field.name + '-error'">
<input *ngSwitchCase="'number'"
type="number"
[]="field.name"
[]="field.name"
[]="field.placeholder || ''"
[(ngModel)]="field.value"
[]="field.validations?.required"
class="form-input"
[]="{'error': fieldModel.invalid && (fieldModel.dirty || fieldModel.touched)}"
[]="fieldModel.invalid && (fieldModel.dirty || fieldModel.touched)"
[]="field.name + '-error'">
<input *ngSwitchCase="'password'"
type="password"
[]="field.name"
[]="field.name"
[]="field.placeholder || ''"
[(ngModel)]="field.value"
[]="field.validations?.required"
[]="field.validations?.minLength"
[]="field.validations?.pattern"
class="form-input"
[]="{'error': fieldModel.invalid && (fieldModel.dirty || fieldModel.touched)}"
[]="fieldModel.invalid && (fieldModel.dirty || fieldModel.touched)"
[]="field.name + '-error'">
<!-- Select dropdown -->
<select *ngSwitchCase="'select'"
[]="field.name"
[]="field.name"
[(ngModel)]="field.value"
[]="field.validations?.required"
class="form-select"
[]="{'error': fieldModel.invalid && (fieldModel.dirty || fieldModel.touched)}"
[]="fieldModel.invalid && (fieldModel.dirty || fieldModel.touched)"
[]="field.name + '-error'">
<option value="">Select {{ field.label }}</option>
<option *ngFor="let option of field.options" [value]="option.value">
{{ option.label }}
</option>
</select>
<!-- Textarea -->
<textarea *ngSwitchCase="'textarea'"
[]="field.name"
[]="field.name"
[]="field.placeholder || ''"
[(ngModel)]="field.value"
[]="field.validations?.required"
[]="field.validations?.minLength"
[]="field.validations?.maxLength"
class="form-textarea"
[]="{'error': fieldModel.invalid && (fieldModel.dirty || fieldModel.touched)}"
[]="fieldModel.invalid && (fieldModel.dirty || fieldModel.touched)"
[]="field.name + '-error'">
</textarea>
</ng-container>
<!-- Error messages -->
<div *ngIf="fieldModel.invalid && (fieldModel.dirty || fieldModel.touched)"
class="error-messages"
[]="field.name + '-error'"
role="alert">
<div *ngIf="fieldModel.errors?.required">
{{ field.errorMessages?.required }}
</div>
<div *ngIf="fieldModel.errors?.minlength">
{{ field.errorMessages?.minlength }}
</div>
<div *ngIf="fieldModel.errors?.maxlength">
{{ field.errorMessages?.maxlength }}
</div>
<div *ngIf="fieldModel.errors?.email">
{{ field.errorMessages?.email }}
</div>
<div *ngIf="fieldModel.errors?.pattern">
{{ field.errorMessages?.pattern }}
</div>
</div>
</div>
<button type="submit"
[]="!form.valid"
[]="formConfig.submitButton?.class || 'submit-button'"
[]="!form.valid">
{{ formConfig.submitButton?.text || 'Submit' }}
</button>
</form>
`, styles: [".dynamic-form{max-width:600px;margin:0 auto;padding:20px;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif}.form-field{margin-bottom:20px}.form-label{display:block;margin-bottom:8px;font-weight:500;color:#333}.required-marker{color:#dc3545;margin-left:4px}.form-input,.form-select,.form-textarea{width:100%;padding:8px 12px;border:1px solid #ced4da;border-radius:4px;font-size:16px;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}.form-textarea{min-height:100px;resize:vertical}.form-input:focus,.form-select:focus,.form-textarea:focus{border-color:#80bdff;outline:0;box-shadow:0 0 0 .2rem #007bff40}.error{border-color:#dc3545}.error:focus{border-color:#dc3545;box-shadow:0 0 0 .2rem #dc354540}.error-messages{margin-top:5px;color:#dc3545;font-size:14px}.hidden{display:none}.submit-button{background-color:#007bff;color:#fff;padding:10px 20px;border:none;border-radius:4px;font-size:16px;cursor:pointer;transition:background-color .15s ease-in-out}.submit-button:hover:not(:disabled){background-color:#0056b3}.submit-button:disabled{background-color:#6c757d;cursor:not-allowed;opacity:.65}@media (max-width: 768px){.dynamic-form{padding:15px}}\n"] }]
}], ctorParameters: function () { return [{ type: AiValidationService }]; }, propDecorators: { formConfig: [{
type: Input
}], formSubmit: [{
type: Output
}] } });
class DynamicFormsModule {
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.0.6", ngImport: i0, type: DynamicFormsModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "16.0.6", ngImport: i0, type: DynamicFormsModule, declarations: [DynamicFormComponent], imports: [CommonModule,
FormsModule], exports: [DynamicFormComponent] });
static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "16.0.6", ngImport: i0, type: DynamicFormsModule, providers: [
AiValidationService
], imports: [CommonModule,
FormsModule] });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.0.6", ngImport: i0, type: DynamicFormsModule, decorators: [{
type: NgModule,
args: [{
declarations: [
DynamicFormComponent
],
imports: [
CommonModule,
FormsModule
],
exports: [
DynamicFormComponent
],
providers: [
AiValidationService
]
}]
}] });
/*
* Public API Surface of dynamic-forms
*/
/**
* Generated bundle index. Do not edit.
*/
export { AiValidationService, DynamicFormComponent, DynamicFormsModule };
//# sourceMappingURL=angular-dynamic-forms-ai.mjs.map