FORMS ‐ FACTORY - JU-DEV-Bootcamps/ERAS GitHub Wiki

Form Factory Summary

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.

Architecture

image

⬆️ Back to Top

Dynamic Field Model

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;
}

⬆️ Back to Top

Field Type

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;

⬆️ Back to Top

FormFactory Service

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];
  }
}

⬆️ Back to Top

Creation new Inputs Components

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>

⬆️ Back to Top

Clean and Extensible Architecture

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.

⬆️ Back to Top

Decoupled FormFactoryComponent

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.

⬆️ Back to Top

Benefits

  • 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.

⬆️ Back to Top

Takeaway

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.

⬆️ Back to Top

⚠️ **GitHub.com Fallback** ⚠️