FORMS ‐ FACTORY - JU-DEV-Bootcamps/ERAS GitHub Wiki
- Form Factory Summary
- Architecture
- Dynamic Field Model
- Field Type
- FormFactory Service
- Creation new Inputs Components
- Clean and Extensible Architecture
- Decoupled FormFactoryComponent
- Benefits
- Takeaway
Allows us to generate forms in a more dynamic, reusable, and maintainable way. This helps us reduce code duplication, styles everywhere, and facilitates the creation of complex forms.
Defines an interface for all the inputs, so our factory can inject them dinamically. If we need to add any aditional attribute that an input requires we need to added here.
// Used to model our formFields variables.
export interface DynamicField {
type: FieldType;
name: string;
label: string;
placeholder?: string;
validators?: (ValidatorFn | string)[];
value?: any;
options?: DynamicOption[];
multipleSelect?: boolean;
disabled?: boolean;
}Defines the inputs types, that our factory supports. If we requires a newer input, we MUST to added here the new type.
export type FieldType =
| 'date'
| 'select'
| 'text'
| 'textarea'
| 'password'
| //NEW COMPONENTS TYPES REGISTER SECTION;Creates the new inputs that our FormFactory requires. If we requires a newer input we need to create the component and MUST register the component's name here.
@Injectable({
providedIn: 'root',
})
export class FormFactoryService {
private _componentsMapper: Record<string, Type<DynamicInputComponent>> = {
date: DateInputComponent,
select: SelectInputComponent,
text: TextInputComponent,
textarea: TextareaInputComponent,
password: PasswordInputComponent,
//NEW COMPONENTS REGISTER SECTION
};
getComponentByType(type: string): Type<DynamicInputComponent> {
return this._componentsMapper[type];
}
}Example creation of small and reusables input components.
text-input.component.ts
@Component({
selector: 'app-text-input',
imports: [MatInputModule, ReactiveFormsModule],
templateUrl: './text-input.component.html',
viewProviders: [//1. Important providers
{ provide: ControlContainer, useExisting: FormGroupDirective },
],
})
//2. You need to implement DynamicInputComponent for all the new inputs.
export class TextInputComponent implements DynamicInputComponent {
field = input.required<DynamicField>();//Field required.
form = input.required<FormGroup>();//Field required.
formUtils = FormUtils;//Field required.
}text-input.component.html
<mat-form-field class="w-full" appearance="outline">//Add w-full and apperance style.
<mat-label>{{ field().label }}</mat-label>
<input
matInput
[type]="field().type"
[formControlName]="field().name"
[placeholder]="field().placeholder!"
/>
//Handles any validations and message errors.
@if (formUtils.isValidField(form(), field().name)) {
<mat-error>{{ formUtils.getFieldError(form(), field()) }}</mat-error>
}
</mat-form-field>Each input type (text, select, date, etc.) lives in its own component. This makes adding a new field type as simple as:
- Creating the new component.
- Registering it in the FormFactoryService _componentMapper.
- Adding its definition to the field type model.
Our FormFactoryComponent knows nothing about field types, it just dynamically creates the FormGroup and uses the FormFactoryService to render inputs.
- This completely separates the form logic from the presentation.
- Handles a modular, maintainable and scalable architecture.
- Add new input component fields easily.
- Centralizes the Validations and defaults logic.
- Each type of input has its own component, clean and reusable.
- Uses Angular standard validators: required, email, etc.
- Uses custom validators by name or function: noSpaces, forbiddenChars, etc.
- Strongly typed, so intellicense can help us to create easily our formularies.
It handles an Standalone approach. Mention too, that it follows the Single Responsability and Open-Closed principles. From now on, we do not need to worry about:
- The imports of Angular forms libraries.
- Formularies styles.
- Handling Validations and error messages, etc.