@bsachref/ng-form
Version:
A dynamic form component for Angular using PrimeNG or Angular Material
410 lines (405 loc) • 68.6 kB
JavaScript
import * as i1$1 from '@angular/common';
import { CommonModule } from '@angular/common';
import * as i0 from '@angular/core';
import { Input, Component, input, output, ChangeDetectionStrategy } from '@angular/core';
import * as i1 from '@angular/forms';
import { Validators, ReactiveFormsModule, FormsModule } from '@angular/forms';
import { BehaviorSubject } from 'rxjs';
import * as i3 from '@angular/material/input';
import { MatInputModule } from '@angular/material/input';
import * as i5 from '@angular/material/select';
import { MatSelectModule } from '@angular/material/select';
import * as i7 from '@angular/material/checkbox';
import { MatCheckboxModule } from '@angular/material/checkbox';
import * as i8 from '@angular/material/radio';
import { MatRadioModule } from '@angular/material/radio';
import * as i9 from '@angular/material/button';
import { MatButtonModule } from '@angular/material/button';
import * as i10 from '@angular/material/datepicker';
import { MatDatepickerModule } from '@angular/material/datepicker';
import * as i4 from '@angular/material/form-field';
import { MatFormFieldModule } from '@angular/material/form-field';
import * as i6 from '@angular/material/core';
import { provideNativeDateAdapter } from '@angular/material/core';
import * as i3$1 from 'primeng/inputtext';
import { InputTextModule } from 'primeng/inputtext';
import * as i4$1 from 'primeng/multiselect';
import { MultiSelectModule } from 'primeng/multiselect';
import * as i5$1 from 'primeng/textarea';
import { TextareaModule } from 'primeng/textarea';
import * as i6$1 from 'primeng/checkbox';
import { CheckboxModule } from 'primeng/checkbox';
import * as i7$1 from 'primeng/radiobutton';
import { RadioButtonModule } from 'primeng/radiobutton';
import * as i9$1 from 'primeng/togglebutton';
import { ToggleButtonModule } from 'primeng/togglebutton';
import { FileUploadModule } from 'primeng/fileupload';
import * as i10$1 from 'primeng/slider';
import { SliderModule } from 'primeng/slider';
import * as i11 from 'primeng/button';
import { ButtonModule } from 'primeng/button';
import * as i8$1 from 'primeng/datepicker';
import { DatePickerModule } from 'primeng/datepicker';
import * as i12 from 'primeng/select';
import { SelectModule } from 'primeng/select';
class ValidationMessagesComponent {
control; // The form control to validate
controlName; // The name of the control (for displaying in messages)
config; // The configuration for the control
getValidatorValue(validatorName, key) {
return this.control?.errors?.[validatorName]?.[key];
}
getCustomErrorMessage() {
if (this.control?.errors?.['custom']) {
return this.control.errors['custom'].message ?? 'Invalid input';
}
return null;
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: ValidationMessagesComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.13", type: ValidationMessagesComponent, isStandalone: true, selector: "validation-messages", inputs: { control: "control", controlName: "controlName", config: "config" }, ngImport: i0, template: "@if (control?.invalid && control?.touched) {\n <section class=\"error-messages\" style=\"color: red\">\n @if (control?.errors?.[\"required\"]) {\n <small>{{ controlName }} is required</small>\n }\n @if (control?.errors?.[\"minlength\"]) {\n <small\n >Minimum length:\n {{ getValidatorValue(\"minlength\", \"requiredLength\") }}</small\n >\n }\n @if (control?.errors?.[\"maxlength\"]) {\n <small\n >Maximum length:\n {{ getValidatorValue(\"maxlength\", \"requiredLength\") }}</small\n >\n }\n @if (control?.errors?.[\"pattern\"]) {\n <small>Invalid format</small>\n }\n @if (control?.errors?.[\"email\"]) {\n <small>Invalid email address</small>\n }\n @if (control?.errors?.[\"min\"]) {\n <small\n >Value must be at least {{ getValidatorValue(\"min\", \"min\") }}</small\n >\n }\n @if (control?.errors?.[\"max\"]) {\n <small>Value must not exceed {{ getValidatorValue(\"max\", \"max\") }}</small>\n }\n @if (control?.errors?.[\"custom\"]) {\n <small>{{ getCustomErrorMessage() }}</small>\n }\n </section>\n}\n" });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: ValidationMessagesComponent, decorators: [{
type: Component,
args: [{ selector: 'validation-messages', standalone: true, imports: [], template: "@if (control?.invalid && control?.touched) {\n <section class=\"error-messages\" style=\"color: red\">\n @if (control?.errors?.[\"required\"]) {\n <small>{{ controlName }} is required</small>\n }\n @if (control?.errors?.[\"minlength\"]) {\n <small\n >Minimum length:\n {{ getValidatorValue(\"minlength\", \"requiredLength\") }}</small\n >\n }\n @if (control?.errors?.[\"maxlength\"]) {\n <small\n >Maximum length:\n {{ getValidatorValue(\"maxlength\", \"requiredLength\") }}</small\n >\n }\n @if (control?.errors?.[\"pattern\"]) {\n <small>Invalid format</small>\n }\n @if (control?.errors?.[\"email\"]) {\n <small>Invalid email address</small>\n }\n @if (control?.errors?.[\"min\"]) {\n <small\n >Value must be at least {{ getValidatorValue(\"min\", \"min\") }}</small\n >\n }\n @if (control?.errors?.[\"max\"]) {\n <small>Value must not exceed {{ getValidatorValue(\"max\", \"max\") }}</small>\n }\n @if (control?.errors?.[\"custom\"]) {\n <small>{{ getCustomErrorMessage() }}</small>\n }\n </section>\n}\n" }]
}], propDecorators: { control: [{
type: Input
}], controlName: [{
type: Input
}], config: [{
type: Input
}] } });
class BaseFormsComponent {
fb;
cdr;
formName = input.required();
controls = input.required();
formSubmit = output();
form;
formChanges$ = new BehaviorSubject(false);
constructor(fb, cdr) {
this.fb = fb;
this.cdr = cdr;
}
ngOnInit() {
this.initializeForm();
}
initializeForm() {
const formControls = {};
this.controls().forEach((control) => {
formControls[control.name] = [
control.value ?? '',
this.getValidators(control),
];
});
this.form = this.fb.group(formControls);
this.controls().forEach((control) => {
const formControl = this.form.get(control.name);
if (formControl) {
formControl.valueChanges.subscribe((value) => {
// Only update validators if the value actually changes
if (formControl.value !== value) {
this.updateValidators(formControl, value);
}
});
}
});
this.formChanges$.subscribe(() => this.cdr.markForCheck());
}
getValidators(control) {
const validators = [];
if (control.validators) {
control.validators.forEach((validator) => {
if (validator.required)
validators.push(Validators.required);
if (validator.minlength)
validators.push(Validators.minLength(validator.minlength));
if (validator.maxlength)
validators.push(Validators.maxLength(validator.maxlength));
if (validator.pattern)
validators.push(Validators.pattern(validator.pattern));
if (validator.email)
validators.push(Validators.email);
if (validator.custom)
validators.push(validator.custom);
});
}
return validators;
}
updateValidators(control, value) {
const requiresValidation = this.shouldRequireValidation(value);
// Check if the validators need to be updated
const hasRequiredValidator = control.hasValidator(Validators.required);
if (requiresValidation && !hasRequiredValidator) {
control.addValidators(Validators.required);
control.updateValueAndValidity();
}
else if (!requiresValidation && hasRequiredValidator) {
control.removeValidators(Validators.required);
control.updateValueAndValidity();
}
this.formChanges$.next(true);
}
shouldRequireValidation(value) {
return value !== null && value !== '';
}
onSubmit() {
this.form.markAllAsTouched();
this.form.markAsDirty();
this.form.updateValueAndValidity();
if (this.form.valid) {
this.formSubmit.emit(this.form.value);
this.form.reset();
}
else {
console.error('Form Invalid');
}
this.formChanges$.next(true);
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: BaseFormsComponent, deps: [{ token: i1.FormBuilder }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "18.2.13", type: BaseFormsComponent, isStandalone: true, selector: "base-forms", inputs: { formName: { classPropertyName: "formName", publicName: "formName", isSignal: true, isRequired: true, transformFunction: null }, controls: { classPropertyName: "controls", publicName: "controls", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { formSubmit: "formSubmit" }, ngImport: i0, template: '', isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "ngmodule", type: FormsModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: BaseFormsComponent, decorators: [{
type: Component,
args: [{
selector: 'base-forms',
standalone: true,
imports: [
CommonModule,
ReactiveFormsModule,
FormsModule,
ValidationMessagesComponent,
],
template: '',
changeDetection: ChangeDetectionStrategy.OnPush,
}]
}], ctorParameters: () => [{ type: i1.FormBuilder }, { type: i0.ChangeDetectorRef }] });
/**
* @component DefaultFormsComponent
* @description
* The `DefaultFormsComponent` is an Angular standalone component that provides a dynamic form generation
* based on the provided configuration. It uses reactive forms to handle form controls and their validations.
* The component is designed to be highly configurable and supports various types of form validators.
*
* @selector default-forms
* @standalone true
* @imports
* - ReactiveFormsModule
* - CommonModule
* - FormsModule
* - ValidationMessagesComponent
* @templateUrl ./default-forms.component.html
* @styleUrl ./default-forms.component.css
* @changeDetection ChangeDetectionStrategy.OnPush
*
* @input
* - `formName: string` - The name of the form.
* - `controls: FormControlConfig[]` - An array of form control configurations.
*
* @output
* - `formSubmit: EventEmitter<Record<string, any>>` - Emits the form value when the form is submitted.
*
* @class DefaultFormsComponent
* @implements OnInit
*
* @property {string} formName - The name of the form.
* @property {FormControlConfig[]} controls - The configuration for the form controls.
* @property {EventEmitter<Record<string, any>>} formSubmit - Event emitter for form submission.
* @property {FormGroup} form - The reactive form group instance.
* @property {BehaviorSubject<boolean>} formChanges$ - A subject to track form changes.
*
* @constructor
* @param {FormBuilder} fb - Angular's FormBuilder service to create form controls.
* @param {ChangeDetectorRef} cdr - Angular's ChangeDetectorRef service to manually trigger change detection.
*
* @method ngOnInit
* @description Lifecycle hook that is called after the component's view has been initialized. It initializes the form.
*
* @method initializeForm
* @description Initializes the form by creating form controls based on the provided configuration and sets up value change subscriptions.
*
* @method getValidators
* @param {FormControlConfig} control - The configuration for a form control.
* @returns {ValidatorFn[]} An array of validators for the form control.
* @description Generates an array of validators based on the provided control configuration.
*
* @method updateValidators
* @param {AbstractControl} control - The form control to update validators for.
* @param {any} value - The current value of the form control.
* @description Updates the validators for a form control based on its current value.
*
* @method shouldRequireValidation
* @param {any} value - The value to check for validation requirement.
* @returns {boolean} Whether the value requires validation.
* @description Determines if a value should require validation.
*
* @method onSubmit
* @description Handles the form submission. Marks the form as touched and dirty, validates the form, emits the form value if valid, and resets the form.
*/
class DefaultFormsComponent extends BaseFormsComponent {
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: DefaultFormsComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.13", type: DefaultFormsComponent, isStandalone: true, selector: "default-forms", usesInheritance: true, ngImport: i0, template: "<form [formGroup]=\"form\" [attr.name]=\"formName()\" (ngSubmit)=\"onSubmit()\">\n @for (control of controls(); track $index) {\n @if (control.uiFramework === \"default\" || !control.uiFramework) {\n @if (form.get(control.name)) {\n <section [ngClass]=\"control.class\" [ngStyle]=\"control.style\">\n @if (control.label) {\n <label\n [attr.for]=\"control.name\"\n [ngClass]=\"control.labelClass\"\n [ngStyle]=\"control.labelStyle\"\n >\n {{ control.label }}\n </label>\n }\n\n @if (control.type === \"input\") {\n <input\n [formControlName]=\"control.name\"\n [id]=\"control.name\"\n type=\"text\"\n />\n }\n @if (control.type === \"select\") {\n <select [formControlName]=\"control.name\" [id]=\"control.name\">\n @for (option of control.options; track $index) {\n <option [value]=\"option\">{{ option }}</option>\n }\n </select>\n }\n @if (control.type === \"textarea\") {\n <textarea\n [formControlName]=\"control.name\"\n [id]=\"control.name\"\n ></textarea>\n }\n @if (control.type === \"checkbox\") {\n <input\n type=\"checkbox\"\n [formControlName]=\"control.name\"\n [id]=\"control.name\"\n />\n }\n @if (control.type === \"radio\") {\n @for (option of control.options ?? []; track $index) {\n <div>\n <input\n type=\"radio\"\n [formControlName]=\"control.name\"\n [id]=\"control.name + '-' + option\"\n [value]=\"option\"\n />\n <label [for]=\"control.name + '-' + option\">{{ option }}</label>\n </div>\n }\n }\n\n <validation-messages\n [control]=\"form.get(control.name)\"\n [controlName]=\"control.name\"\n [config]=\"control\"\n ></validation-messages>\n </section>\n }\n }\n }\n <button type=\"submit\" [disabled]=\"form.invalid\">Submit</button>\n</form>\n", dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.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: i1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1.RadioControlValueAccessor, selector: "input[type=radio][formControlName],input[type=radio][formControl],input[type=radio][ngModel]", inputs: ["name", "formControlName", "value"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1$1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "ngmodule", type: FormsModule }, { kind: "component", type: ValidationMessagesComponent, selector: "validation-messages", inputs: ["control", "controlName", "config"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: DefaultFormsComponent, decorators: [{
type: Component,
args: [{ selector: 'default-forms', standalone: true, imports: [
BaseFormsComponent,
ReactiveFormsModule,
CommonModule,
FormsModule,
ValidationMessagesComponent,
], changeDetection: ChangeDetectionStrategy.OnPush, template: "<form [formGroup]=\"form\" [attr.name]=\"formName()\" (ngSubmit)=\"onSubmit()\">\n @for (control of controls(); track $index) {\n @if (control.uiFramework === \"default\" || !control.uiFramework) {\n @if (form.get(control.name)) {\n <section [ngClass]=\"control.class\" [ngStyle]=\"control.style\">\n @if (control.label) {\n <label\n [attr.for]=\"control.name\"\n [ngClass]=\"control.labelClass\"\n [ngStyle]=\"control.labelStyle\"\n >\n {{ control.label }}\n </label>\n }\n\n @if (control.type === \"input\") {\n <input\n [formControlName]=\"control.name\"\n [id]=\"control.name\"\n type=\"text\"\n />\n }\n @if (control.type === \"select\") {\n <select [formControlName]=\"control.name\" [id]=\"control.name\">\n @for (option of control.options; track $index) {\n <option [value]=\"option\">{{ option }}</option>\n }\n </select>\n }\n @if (control.type === \"textarea\") {\n <textarea\n [formControlName]=\"control.name\"\n [id]=\"control.name\"\n ></textarea>\n }\n @if (control.type === \"checkbox\") {\n <input\n type=\"checkbox\"\n [formControlName]=\"control.name\"\n [id]=\"control.name\"\n />\n }\n @if (control.type === \"radio\") {\n @for (option of control.options ?? []; track $index) {\n <div>\n <input\n type=\"radio\"\n [formControlName]=\"control.name\"\n [id]=\"control.name + '-' + option\"\n [value]=\"option\"\n />\n <label [for]=\"control.name + '-' + option\">{{ option }}</label>\n </div>\n }\n }\n\n <validation-messages\n [control]=\"form.get(control.name)\"\n [controlName]=\"control.name\"\n [config]=\"control\"\n ></validation-messages>\n </section>\n }\n }\n }\n <button type=\"submit\" [disabled]=\"form.invalid\">Submit</button>\n</form>\n" }]
}] });
/**
* @component
* @name MaterialFormsComponent
* @description
* A standalone Angular component that renders a dynamic form using Angular Material components.
* The form configuration is provided via inputs, and the form submission is handled via an output event.
*
* @selector material-forms
* @standalone true
* @imports
* - CommonModule
* - ReactiveFormsModule
* - MatInputModule
* - MatSelectModule
* - MatCheckboxModule
* - MatRadioModule
* - MatButtonModule
* - ValidationMessagesComponent
* - MatDatepickerModule
* - MatFormFieldModule
* @providers provideNativeDateAdapter
* @templateUrl ./material-form.component.html
* @styleUrl ./material-form.component.css
* @changeDetection ChangeDetectionStrategy.OnPush
*
* @inputs
* @property {string} formName - The name of the form.
* @property {FormControlConfig[]} controls - The configuration for the form controls.
*
* @outputs
* @property {EventEmitter<Record<string, any>>} formSubmit - Event emitted when the form is submitted.
*
* @class
* @name MaterialFormsComponent
* @description
* This class defines the MaterialFormsComponent which initializes the form based on the provided controls configuration,
* handles form submission, and provides validation for the form controls.
*
* @constructor
* @param {FormBuilder} fb - Angular FormBuilder service to create form groups and controls.
* @param {ChangeDetectorRef} cdr - Angular ChangeDetectorRef service to manually trigger change detection.
*
* @method
* @name ngOnInit
* @description
* Lifecycle hook that is called after the component's view has been initialized. It initializes the form.
*
* @method
* @name initializeForm
* @description
* Initializes the form controls based on the provided configuration. Supports datepicker range controls.
*
* @method
* @name getValidators
* @description
* Returns an array of Angular validators based on the provided control configuration.
* @param {FormControlConfig} control - The configuration for the form control.
* @returns {Validators[]} - An array of Angular validators.
*
* @method
* @name onSubmit
* @description
* Handles the form submission. Marks all controls as touched and dirty, updates the form's validity,
* emits the form values if the form is valid, and resets the form. Triggers change detection.
*/
class MaterialFormsComponent extends BaseFormsComponent {
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: MaterialFormsComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.13", type: MaterialFormsComponent, isStandalone: true, selector: "material-forms", providers: [provideNativeDateAdapter()], usesInheritance: true, ngImport: i0, template: "<form [formGroup]=\"form\" [attr.name]=\"formName()\" (ngSubmit)=\"onSubmit()\">\n @for (control of controls(); track $index) {\n @if (control.uiFramework === \"material\" || !control.uiFramework) {\n @if (form.get(control.name)) {\n <section [ngClass]=\"control.class\" [ngStyle]=\"control.style\">\n @if (control.type === \"input\") {\n <mat-form-field appearance=\"outline\">\n @if (control.label) {\n <mat-label>{{ control.label }}</mat-label>\n }\n <input matInput [formControlName]=\"control.name\" [id]=\"control.name\" type=\"text\" />\n </mat-form-field>\n }\n @if (control.type === \"select\") {\n <mat-form-field appearance=\"outline\">\n @if (control.label) {\n <mat-label>{{ control.label }}</mat-label>\n }\n <mat-select [formControlName]=\"control.name\" [id]=\"control.name\">\n @for (option of control.options ?? []; track $index) {\n <mat-option [value]=\"option\">{{ option }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n }\n @if (control.type === \"checkbox\") {\n <mat-checkbox [formControlName]=\"control.name\" [id]=\"control.name\">\n {{ control.label }}\n </mat-checkbox>\n }\n @if (control.type === \"radio\") {\n <mat-radio-group [formControlName]=\"control.name\">\n @for (option of control.options ?? []; track $index) {\n <mat-radio-button [id]=\"control.name + '-' + option\" [value]=\"option\">\n {{ option }}\n </mat-radio-button>\n }\n </mat-radio-group>\n }\n @if (control.type === \"datepicker\") {\n @if (control.datePickerMode === \"single\") {\n <mat-form-field appearance=\"outline\">\n <mat-label>Choose a date</mat-label>\n <input matInput [matDatepicker]=\"datepicker\" [formControlName]=\"control.name\" />\n <mat-datepicker-toggle matIconSuffix [for]=\"datepicker\"></mat-datepicker-toggle>\n <mat-datepicker #datepicker></mat-datepicker>\n </mat-form-field>\n } @else if (control.datePickerMode === \"range\") {\n <mat-form-field>\n <mat-label>Enter a date range</mat-label>\n <mat-date-range-input [rangePicker]=\"picker\" [formGroup]=\"form\" ]>\n <input matStartDate formControlName=\"{{ control.name }}_Start\" placeholder=\"Start date\" />\n <input matEndDate formControlName=\"{{ control.name }}_End\" placeholder=\"End date\" />\n </mat-date-range-input>\n <mat-datepicker-toggle matIconSuffix [for]=\"picker\"></mat-datepicker-toggle>\n <mat-date-range-picker #picker></mat-date-range-picker>\n\n <!-- @if (form.controls.start.hasError('matStartDateInvalid')) {\n <mat-error>Invalid start date</mat-error>\n }\n @if (form.controls.end.hasError('matEndDateInvalid')) {\n <mat-error>Invalid end date</mat-error>\n } -->\n </mat-form-field>\n }\n }\n\n <validation-messages [control]=\"form.get(control.name)\" [controlName]=\"control.name\"\n [config]=\"control\"></validation-messages>\n </section>\n }\n }\n }\n <button mat-raised-button color=\"primary\" type=\"submit\" [disabled]=\"form.invalid\">\n Submit\n </button>\n</form>", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1$1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.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: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i3.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly"], exportAs: ["matInput"] }, { kind: "component", type: i4.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i4.MatLabel, selector: "mat-label" }, { kind: "directive", type: i4.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i5.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i6.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatCheckboxModule }, { kind: "component", type: i7.MatCheckbox, selector: "mat-checkbox", inputs: ["aria-label", "aria-labelledby", "aria-describedby", "id", "required", "labelPosition", "name", "value", "disableRipple", "tabIndex", "color", "disabledInteractive", "checked", "disabled", "indeterminate"], outputs: ["change", "indeterminateChange"], exportAs: ["matCheckbox"] }, { kind: "ngmodule", type: MatRadioModule }, { kind: "directive", type: i8.MatRadioGroup, selector: "mat-radio-group", inputs: ["color", "name", "labelPosition", "value", "selected", "disabled", "required", "disabledInteractive"], outputs: ["change"], exportAs: ["matRadioGroup"] }, { kind: "component", type: i8.MatRadioButton, selector: "mat-radio-button", inputs: ["id", "name", "aria-label", "aria-labelledby", "aria-describedby", "disableRipple", "tabIndex", "checked", "value", "labelPosition", "disabled", "required", "color", "disabledInteractive"], outputs: ["change"], exportAs: ["matRadioButton"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i9.MatButton, selector: " button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ", exportAs: ["matButton"] }, { kind: "component", type: ValidationMessagesComponent, selector: "validation-messages", inputs: ["control", "controlName", "config"] }, { kind: "ngmodule", type: MatDatepickerModule }, { kind: "component", type: i10.MatDatepicker, selector: "mat-datepicker", exportAs: ["matDatepicker"] }, { kind: "directive", type: i10.MatDatepickerInput, selector: "input[matDatepicker]", inputs: ["matDatepicker", "min", "max", "matDatepickerFilter"], exportAs: ["matDatepickerInput"] }, { kind: "component", type: i10.MatDatepickerToggle, selector: "mat-datepicker-toggle", inputs: ["for", "tabIndex", "aria-label", "disabled", "disableRipple"], exportAs: ["matDatepickerToggle"] }, { kind: "component", type: i10.MatDateRangeInput, selector: "mat-date-range-input", inputs: ["rangePicker", "required", "dateFilter", "min", "max", "disabled", "separator", "comparisonStart", "comparisonEnd"], exportAs: ["matDateRangeInput"] }, { kind: "directive", type: i10.MatStartDate, selector: "input[matStartDate]", outputs: ["dateChange", "dateInput"] }, { kind: "directive", type: i10.MatEndDate, selector: "input[matEndDate]", outputs: ["dateChange", "dateInput"] }, { kind: "component", type: i10.MatDateRangePicker, selector: "mat-date-range-picker", exportAs: ["matDateRangePicker"] }, { kind: "ngmodule", type: MatFormFieldModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: MaterialFormsComponent, decorators: [{
type: Component,
args: [{ selector: 'material-forms', standalone: true, imports: [
BaseFormsComponent,
CommonModule,
ReactiveFormsModule,
MatInputModule,
MatSelectModule,
MatCheckboxModule,
MatRadioModule,
MatButtonModule,
ValidationMessagesComponent,
MatDatepickerModule,
MatFormFieldModule,
], providers: [provideNativeDateAdapter()], changeDetection: ChangeDetectionStrategy.OnPush, template: "<form [formGroup]=\"form\" [attr.name]=\"formName()\" (ngSubmit)=\"onSubmit()\">\n @for (control of controls(); track $index) {\n @if (control.uiFramework === \"material\" || !control.uiFramework) {\n @if (form.get(control.name)) {\n <section [ngClass]=\"control.class\" [ngStyle]=\"control.style\">\n @if (control.type === \"input\") {\n <mat-form-field appearance=\"outline\">\n @if (control.label) {\n <mat-label>{{ control.label }}</mat-label>\n }\n <input matInput [formControlName]=\"control.name\" [id]=\"control.name\" type=\"text\" />\n </mat-form-field>\n }\n @if (control.type === \"select\") {\n <mat-form-field appearance=\"outline\">\n @if (control.label) {\n <mat-label>{{ control.label }}</mat-label>\n }\n <mat-select [formControlName]=\"control.name\" [id]=\"control.name\">\n @for (option of control.options ?? []; track $index) {\n <mat-option [value]=\"option\">{{ option }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n }\n @if (control.type === \"checkbox\") {\n <mat-checkbox [formControlName]=\"control.name\" [id]=\"control.name\">\n {{ control.label }}\n </mat-checkbox>\n }\n @if (control.type === \"radio\") {\n <mat-radio-group [formControlName]=\"control.name\">\n @for (option of control.options ?? []; track $index) {\n <mat-radio-button [id]=\"control.name + '-' + option\" [value]=\"option\">\n {{ option }}\n </mat-radio-button>\n }\n </mat-radio-group>\n }\n @if (control.type === \"datepicker\") {\n @if (control.datePickerMode === \"single\") {\n <mat-form-field appearance=\"outline\">\n <mat-label>Choose a date</mat-label>\n <input matInput [matDatepicker]=\"datepicker\" [formControlName]=\"control.name\" />\n <mat-datepicker-toggle matIconSuffix [for]=\"datepicker\"></mat-datepicker-toggle>\n <mat-datepicker #datepicker></mat-datepicker>\n </mat-form-field>\n } @else if (control.datePickerMode === \"range\") {\n <mat-form-field>\n <mat-label>Enter a date range</mat-label>\n <mat-date-range-input [rangePicker]=\"picker\" [formGroup]=\"form\" ]>\n <input matStartDate formControlName=\"{{ control.name }}_Start\" placeholder=\"Start date\" />\n <input matEndDate formControlName=\"{{ control.name }}_End\" placeholder=\"End date\" />\n </mat-date-range-input>\n <mat-datepicker-toggle matIconSuffix [for]=\"picker\"></mat-datepicker-toggle>\n <mat-date-range-picker #picker></mat-date-range-picker>\n\n <!-- @if (form.controls.start.hasError('matStartDateInvalid')) {\n <mat-error>Invalid start date</mat-error>\n }\n @if (form.controls.end.hasError('matEndDateInvalid')) {\n <mat-error>Invalid end date</mat-error>\n } -->\n </mat-form-field>\n }\n }\n\n <validation-messages [control]=\"form.get(control.name)\" [controlName]=\"control.name\"\n [config]=\"control\"></validation-messages>\n </section>\n }\n }\n }\n <button mat-raised-button color=\"primary\" type=\"submit\" [disabled]=\"form.invalid\">\n Submit\n </button>\n</form>" }]
}] });
/**
* PrimeNgFormsComponent is a standalone Angular component that dynamically generates
* a form based on the provided configuration. It leverages PrimeNG UI components
* and Angular Reactive Forms for form creation and validation.
*
* @selector primeng-forms
* @standalone true
* @imports [
* CommonModule,
* FormsModule,
* ReactiveFormsModule,
* ValidationMessagesComponent,
* InputTextModule,
* MultiSelectModule,
* TextareaModule,
* CheckboxModule,
* RadioButtonModule,
* DatePickerModule,
* ToggleButtonModule,
* FileUploadModule,
* SliderModule,
* ButtonModule,
* SelectModule,
* FileUploadValueAccessor
* ]
* @templateUrl ./prime-form.component.html
* @styleUrl ./prime-form.component.css
* @changeDetection ChangeDetectionStrategy.OnPush
*
* @property {string} formName - The name of the form.
* @property {FormControlConfig[]} controls - The configuration for the form controls.
* @property {EventEmitter<Record<string, any>>} formSubmit - Event emitter for form submission.
* @property {FormGroup} form - The reactive form group.
* @property {BehaviorSubject<boolean>} formChanges$ - Observable to track form changes.
*
* @constructor
* @param {FormBuilder} fb - FormBuilder service to create form controls.
* @param {ChangeDetectorRef} cdr - ChangeDetectorRef to manually trigger change detection.
*
* @method ngOnInit - Lifecycle hook that initializes the form.
* @method initializeForm - Initializes the form controls and sets up value change subscriptions.
* @method getValidators - Returns an array of validators for a given form control configuration.
* @param {FormControlConfig} control - The configuration for a form control.
* @returns {ValidatorFn[]} - An array of validator functions.
* @method updateValidators - Updates the validators for a form control based on its value.
* @param {AbstractControl} control - The form control to update.
* @param {any} value - The current value of the form control.
* @method shouldRequireValidation - Determines if a form control should require validation.
* @param {any} value - The current value of the form control.
* @returns {boolean} - True if validation is required, false otherwise.
* @method onFileSelect - Handles file selection for file upload controls.
* @param {any} event - The file selection event.
* @param {string} controlName - The name of the form control.
* @method onSubmit - Handles form submission, validates the form, and emits the form value.
*/
class PrimeNgFormsComponent extends BaseFormsComponent {
onFileSelect(event, controlName) {
const file = event.files[0];
if (file) {
this.form.get(controlName)?.setValue(file);
this.form.get(controlName)?.updateValueAndValidity();
const controlConfig = this.controls().find((control) => control.name === controlName);
if (controlConfig?.events?.onUpload) {
controlConfig.events.onUpload(event);
}
}
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: PrimeNgFormsComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.13", type: PrimeNgFormsComponent, isStandalone: true, selector: "primeng-forms", usesInheritance: true, ngImport: i0, template: "<form [formGroup]=\"form\" [attr.name]=\"formName()\" (ngSubmit)=\"onSubmit()\">\n @for (control of controls(); track $index) {\n @if (control.uiFramework === \"primeng\" || !control.uiFramework) {\n @if (form.get(control.name)) {\n <section [ngClass]=\"control.class\" [ngStyle]=\"control.style\">\n @if (control.label) {\n <label\n [attr.for]=\"control.name\"\n [ngClass]=\"control.labelClass\"\n [ngStyle]=\"control.labelStyle\"\n >\n {{ control.label }}\n </label>\n }\n\n @switch (control.type) {\n @case (\"text\") {\n <input\n pInputText\n [formControlName]=\"control.name\"\n [id]=\"control.name\"\n (change)=\"control.events?.onChange?.($event)\"\n (focus)=\"control.events?.onFocus?.($event)\"\n (blur)=\"control.events?.onBlur?.($event)\"\n (keyup)=\"control.events?.onKeyUp?.($event)\"\n (keydown)=\"control.events?.onKeyDown?.($event)\"\n (paste)=\"control.events?.onPaste?.($event)\"\n />\n }\n @case (\"number\") {\n <input\n pInputText\n type=\"number\"\n [formControlName]=\"control.name\"\n [id]=\"control.name\"\n (change)=\"control.events?.onChange?.($event)\"\n (focus)=\"control.events?.onFocus?.($event)\"\n (blur)=\"control.events?.onBlur?.($event)\"\n (keyup)=\"control.events?.onKeyUp?.($event)\"\n (keydown)=\"control.events?.onKeyDown?.($event)\"\n (paste)=\"control.events?.onPaste?.($event)\"\n />\n }\n @case (\"password\") {\n <input\n pInputText\n type=\"password\"\n [formControlName]=\"control.name\"\n [id]=\"control.name\"\n (change)=\"control.events?.onChange?.($event)\"\n (focus)=\"control.events?.onFocus?.($event)\"\n (blur)=\"control.events?.onBlur?.($event)\"\n (keyup)=\"control.events?.onKeyUp?.($event)\"\n (keydown)=\"control.events?.onKeyDown?.($event)\"\n (paste)=\"control.events?.onPaste?.($event)\"\n />\n }\n @case (\"email\") {\n <input\n pInputText\n type=\"email\"\n [formControlName]=\"control.name\"\n [id]=\"control.name\"\n (change)=\"control.events?.onChange?.($event)\"\n (focus)=\"control.events?.onFocus?.($event)\"\n (blur)=\"control.events?.onBlur?.($event)\"\n (keyup)=\"control.events?.onKeyUp?.($event)\"\n (keydown)=\"control.events?.onKeyDown?.($event)\"\n (paste)=\"control.events?.onPaste?.($event)\"\n />\n }\n @case (\"select\") {\n <p-select\n [options]=\"control.options\"\n [formControlName]=\"control.name\"\n (onChange)=\"control.events?.onChange?.($event)\"\n (onFocus)=\"control.events?.onFocus?.($event)\"\n (onBlur)=\"control.events?.onBlur?.($event)\"\n />\n }\n @case (\"multi-select\") {\n <p-multiSelect\n [options]=\"control.options\"\n [formControlName]=\"control.name\"\n (onChange)=\"control.events?.onChange?.($event)\"\n (onFocus)=\"control.events?.onFocus?.($event)\"\n (onBlur)=\"control.events?.onBlur?.($event)\"\n ></p-multiSelect>\n }\n @case (\"textarea\") {\n <textarea\n pTextarea\n [formControlName]=\"control.name\"\n [id]=\"control.name\"\n (change)=\"control.events?.onChange?.($event)\"\n (focus)=\"control.events?.onFocus?.($event)\"\n (blur)=\"control.events?.onBlur?.($event)\"\n (keyup)=\"control.events?.onKeyUp?.($event)\"\n (keydown)=\"control.events?.onKeyDown?.($event)\"\n (paste)=\"control.events?.onPaste?.($event)\"\n ></textarea>\n }\n @case (\"checkbox\") {\n <p-checkbox\n [binary]=\"true\"\n [formControlName]=\"control.name\"\n (onChange)=\"control.events?.onChange?.($event)\"\n (onFocus)=\"control.events?.onFocus?.($event)\"\n (onBlur)=\"control.events?.onBlur?.($event)\"\n ></p-checkbox>\n }\n @case (\"radio\") {\n @for (option of control.options ?? []; track $index) {\n <div>\n <p-radioButton\n [name]=\"control.name\"\n [formControlName]=\"control.name\"\n [value]=\"option\"\n [inputId]=\"'radio-' + option\"\n (onChange)=\"control.events?.onChange?.($event)\"\n (onFocus)=\"control.events?.onFocus?.($event)\"\n (onBlur)=\"control.events?.onBlur?.($event)\"\n ></p-radioButton>\n <label [for]=\"'radio-' + option\">{{ option }}</label>\n </div>\n }\n }\n @case (\"date\") {\n <p-datepicker\n [formControlName]=\"control.name\"\n (onSelect)=\"control.events?.onSelect?.($event)\"\n (onFocus)=\"control.events?.onFocus?.($event)\"\n (onBlur)=\"control.events?.onBlur?.($event)\"\n />\n }\n @case (\"toggle\") {\n <p-toggleButton\n [formControlName]=\"control.name\"\n (onChange)=\"control.events?.onChange?.($event)\"\n (onFocus)=\"control.events?.onFocus?.($event)\"\n (onBlur)=\"control.events?.onBlur?.($event)\"\n ></p-toggleButton>\n }\n <!-- @case (\"file\") {\n <p-fileUpload \n [customUpload]=\"true\"\n [formControlName]=\"control.name\"\n (onSelect)=\"control.events?.onSelect?.($event)\"\n (onClear)=\"control.events?.onClear?.($event)\"\n (onRemove)=\"control.events?.onRemove?.($event)\"\n (onError)=\"control.events?.onError?.($event)\"\n (onBeforeUpload)=\"control.events?.onBeforeUpload?.($event)\"\n (onProgress)=\"control.events?.onProgress?.($event)\"\n (onUpload)=\"control.events?.onUpload?.($event)\"\n >\n\n <p-fileUpload\n [customUpload]=\"true\"\n (onUpload)=\"onFileSelect($event, control.name)\"\n [formControlName]=\"control.name\"\n [id]=\"control.name\"\n [chooseLabel]=\"control.primeng?.chooseLabel\"\n [uploadLabel]=\"control.primeng?.uploadLabel\"\n [cancelLabel]=\"control.primeng?.cancelLabel\"\n [auto]=\"control.primeng?.auto\"\n [url]=\"control.primeng?.url\"\n [withCredentials]=\"control.primeng?.withCredentials\"\n [multiple]=\"control.primeng?.multiple\"\n [maxFileSize]=\"control.primeng?.maxFileSize\"\n [customUpload]=\"control.primeng?.customUpload\"\n [showUploadButton]=\"control.primeng?.showUploadButton\"\n [showCancelButton]=\"control.primeng?.showCancelButton\"\n [accept]=\"control.primeng?.accept\"\n [mode]=\"control.primeng?.mode\"\n [previewWidth]=\"control.primeng?.previewWidth\"\n (onSelect)=\"control.events?.onSelect?.($event)\"\n (onClear)=\"control.events?.onClear?.($event)\"\n (onRemove)=\"control.events?.onRemove?.($event)\"\n (onError)=\"control.events?.onError?.($event)\"\n (onBeforeUpload)=\"control.events?.onBeforeUpload?.($event)\"\n (onProgress)=\"control.events?.onProgress?.($event)\"\n (onUpload)=\"control.events?.onUpload?.($event)\"\n >\n <ng-template pTemplate=\"content\">\n <p-button label=\"Choose File\" icon=\"pi pi-upload\"></p-button>\n </ng-template>\n </p-fileUpload>\n } -->\n @case (\"slider\") {\n <p-slider\n [formControlName]=\"control.name\"\n (onChange)=\"control.events?.onChange?.($event)\"\n (onSlideEnd)=\"control.events?.onSlideEnd?.($event)\"\n ></p-slider>\n }\n }\n\n <validation-messages\n [control]=\"form.get(control.name)\"\n [controlName]=\"control.name\"\n [config]=\"control\"\n ></validation-messages>\n </section>\n }\n }\n }\n <button\n pButton\n type=\"submit\"\n [disabled]=