Table of Contents
Comprender el patrón de fábrica en Angular
Las aplicaciones modernas Angulares a menudo requieren formas que se adapten a diferentes funciones de usuario, esquemas de datos o entornos de configuración. La creación manual de instancias para cada permutación conduce a código duplicado, casos de cambio frágiles y lógica de validación enredado. El Patrón de fábrica aborda esto proporcionando un mecanismo centralizado para producir objetos de forma basados en un tipo o configuración definido.
La fábrica encapsula la lógica de instantáneas, por lo que los componentes ya no necesitan saber qué forma clase a instantánea o cómo montar los controles. Esta separación de preocupaciones hace que la base de código sea más modular, testable y escalable, especialmente cuando el número de tipos de formas crece más allá de un puñado. El patrón encaja naturalmente en el sistema de inyección de dependencia de Angular, lo que le permite registrar funciones de fábrica como proveedores y inyectarlas donde sea necesario.
¿Por qué utilizar el patrón de fábrica para formas dinámicas?
Angular ya proporciona el para manejar escenarios complejos de forma, pero el patrón de fábrica elimina la caldera procesal que a menudo acompaña a la creación de forma. Aquí están las ventajas principales:
- Reduce la duplicación de código. En lugar de repetir construcciones similares a través de componentes o servicios, centraliza la lógica en un método de fábrica. Cada tipo de forma es construido por una clase dedicada de constructores, por lo que escribe su lógica sólo una vez.
- Mejora la capacidad de mantenimiento. La adición de un nuevo tipo de forma significa crear una nueva clase de constructores y registrarla en la fábrica. No hay componentes existentes que cambiar, siempre y cuando soliciten formularios por tipo.
- Permite configuraciones de formularios flexibles. La fábrica puede aceptar parámetros adicionales (por ejemplo, permisos de usuario, local o banderas de características) que influyen en los controles incluidos o en la forma en que se validan.
- Aporta interfaces de usuario dinámicas. Cuando el tipo de formulario se determina en tiempo de ejecución (por ejemplo, desde un registro de bases de datos o un parámetro de ruta), la fábrica hace que sea trivial para instantánear la correcta en la mosca.
- Mejora la testabilidad. Cada constructor de forma puede ser probado por unidad de forma independiente. La fábrica en sí se convierte en una simple asignación que puede ser burlada o obstruida en pruebas de componentes.
Estos beneficios son especialmente valiosos en las aplicaciones empresariales que deben apoyar muchos flujos de trabajo distintos, cada uno con su propio conjunto de campos y reglas de validación.
Medidas básicas de aplicación
Paso 1: Definir el tipo de formulario Enumeración e Interfaces
Comience por definir un conjunto de tipos de formularios. Usar un enum previene los tipos de los tipos y proporciona un contrato claro:
export enum FormType {
Login = 'login',
Registration = 'registration',
Feedback = 'feedback',
Contact = 'contact'
}
A continuación, definir una interfaz que todos los constructores de forma implementarán. Esto asegura que la fábrica puede tratarlos polimorficamente:
import { FormGroup } from '@angular/forms';
export interface FormBuilderInterface {
build(config?: any): FormGroup;
}
El parámetro opcional permite a los constructores personalizar el formulario basado en datos de tiempo de ejecución, como valores de pre-filado o ajustar reglas de validación por función del usuario.
Paso 2: Crear Constructores de Forma Concreto
Cada constructor de concreto implementa y devuelve un configurado . Por ejemplo, un constructor de formularios de inicio de sesión puede parecerse a esto:
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
export class LoginFormBuilder implements FormBuilderInterface {
private fb: FormBuilder;
constructor() {
this.fb = new FormBuilder();
}
build(): FormGroup {
return this.fb.group({
email: ['', [Validators.required, Validators.email]],
password: ['', [Validators.required, Validators.minLength(6)]],
rememberMe: [false]
});
}
}
Un constructor de formularios de registro podría incluir controles adicionales y validación de campos cruzados:
export class RegistrationFormBuilder implements FormBuilderInterface {
private fb: FormBuilder;
constructor() {
this.fb = new FormBuilder();
}
build(): FormGroup {
const form = this.fb.group({
firstName: ['', Validators.required],
lastName: ['', Validators.required],
email: ['', [Validators.required, Validators.email]],
password: ['', [Validators.required, Validators.minLength(8)]],
confirmPassword: ['', Validators.required]
}, { validators: this.matchPasswords });
return form;
}
private matchPasswords(group: FormGroup): { [key: string]: any } | null {
const password = group.get('password')?.value;
const confirm = group.get('confirmPassword')?.value;
return password === confirm ? null : { mismatch: true };
}
}
Esta estructura se escala limpiamente: cada constructor se centra exclusivamente en los detalles de su propia forma, y puede reutilizar la lógica mediante la composición de constructores o el uso de funciones de ayuda.
Paso 3: Construir la clase de fábrica
La clase de fábrica es responsable de mapear un tipo de formulario a su correspondiente constructor. Una implementación simple utiliza una declaración de conmutación o un objeto de mapa:
import { Injectable } from '@angular/core';
import { FormGroup } from '@angular/forms';
import { FormType } from './form-type.enum';
import { LoginFormBuilder } from './login-form.builder';
import { RegistrationFormBuilder } from './registration-form.builder';
import { FeedbackFormBuilder } from './feedback-form.builder';
@Injectable({ providedIn: 'root' })
export class FormFactory {
private builders = new Map<FormType, FormBuilderInterface>();
constructor() {
// Register builders
this.builders.set(FormType.Login, new LoginFormBuilder());
this.builders.set(FormType.Registration, new RegistrationFormBuilder());
this.builders.set(FormType.Feedback, new FeedbackFormBuilder());
}
createForm(type: FormType, config?: any): FormGroup {
const builder = this.builders.get(type);
if (!builder) {
throw new Error(`No builder registered for form type: ${type}`);
}
return builder.build(config);
}
}
Los constructores registrados dentro del constructor son directos, pero para aplicaciones más grandes que usted podría preferir utilizar la inyección de dependencia de Angular para proporcionar constructores. Ese enfoque se cubre más adelante.
Paso 4: Use la fábrica en componentes
Con la fábrica en su lugar, los componentes pueden solicitar una instancia de formulario sin conocer los detalles de su construcción. Por ejemplo, un componente que maneja múltiples tipos de formas basados en un parámetro de ruta:
import { Component, OnInit } from '@angular/core';
import { FormGroup } from '@angular/forms';
import { FormFactory } from './form.factory';
import { FormType } from './form-type.enum';
@Component({
selector: 'app-dynamic-form',
template: `