Что такое динамические формы? Как их создать?angular-60

Динамические формы — это формы, структура которых определяется во время выполнения приложения, а не на этапе компиляции. Они особенно полезны, когда:

  • Структура формы зависит от данных из API
  • Нужно создавать формы с переменным количеством полей
  • Требуется гибкость в изменении валидаторов и структуры

Основные подходы к созданию динамических форм

1. Использование FormBuilder и программное создание контролов

import { FormBuilder, FormGroup, FormArray, Validators } from '@angular/forms';

@Component({
  selector: 'app-dynamic-form',
  templateUrl: './dynamic-form.component.html'
})
export class DynamicFormComponent {
  dynamicForm: FormGroup;

  constructor(private fb: FormBuilder) {
    this.dynamicForm = this.fb.group({
      personalInfo: this.fb.group({
        firstName: ['', Validators.required],
        lastName: ['', Validators.required]
      }),
      addresses: this.fb.array([]) // Динамический массив адресов
    });
  }

  addAddress() {
    const addressGroup = this.fb.group({
      street: ['', Validators.required],
      city: ['', Validators.required],
      zip: ['', [Validators.required, Validators.pattern(/^\d{5}$/)]]
    });
    this.addresses.push(addressGroup);
  }

  get addresses(): FormArray {
    return this.dynamicForm.get('addresses') as FormArray;
  }
}

Шаблон для такого подхода:

<form [formGroup]="dynamicForm">
  <div formGroupName="personalInfo">
    <input formControlName="firstName" placeholder="First Name">
    <input formControlName="lastName" placeholder="Last Name">
  </div>

  <div formArrayName="addresses">
    <div *ngFor="let address of addresses.controls; let i=index" [formGroupName]="i">
      <input formControlName="street" placeholder="Street">
      <input formControlName="city" placeholder="City">
      <input formControlName="zip" placeholder="ZIP Code">
    </div>
    <button type="button" (click)="addAddress()">Add Address</button>
  </div>
</form>

2. Полностью динамическое создание формы на основе конфигурации

interface FieldConfig {
  type: string;
  name: string;
  label?: string;
  value?: any;
  validators?: ValidatorFn[];
}

@Component({
  selector: 'app-configurable-form',
  templateUrl: './configurable-form.component.html'
})
export class ConfigurableFormComponent {
  form: FormGroup;
  fields: FieldConfig[] = [];
  fieldTypes = ['text', 'email', 'password', 'number'];

  constructor(private fb: FormBuilder) {
    this.loadFormConfig();
  }

  loadFormConfig() {
    // Обычно конфиг загружается с сервера
    this.fields = [
      { type: 'text', name: 'username', label: 'Username', validators: [Validators.required] },
      { type: 'email', name: 'email', label: 'Email', validators: [Validators.required, Validators.email] },
      { type: 'password', name: 'password', label: 'Password', validators: [Validators.required, Validators.minLength(8)] }
    ];

    this.createForm();
  }

  createForm() {
    const group = {};
    this.fields.forEach(field => {
      group[field.name] = [field.value || '', field.validators || []];
    });
    this.form = this.fb.group(group);
  }

  addField() {
    this.fields.push({ type: 'text', name: `field_${Date.now()}`, label: 'New Field' });
    this.createForm();
  }
}

Шаблон для динамического отображения полей:

<form [formGroup]="form">
  <div *ngFor="let field of fields">
    <label>{{field.label || field.name}}</label>

    <input *ngIf="field.type === 'text' || field.type === 'email' || field.type === 'password'"
           [type]="field.type"
           [formControlName]="field.name"
           [placeholder]="field.label">

    <input *ngIf="field.type === 'number'"
           type="number"
           [formControlName]="field.name"
           [placeholder]="field.label">

    <div *ngIf="form.get(field.name).invalid && form.get(field.name).touched">
      Ошибка валидации
    </div>
  </div>

  <button type="button" (click)="addField()">Add Field</button>
</form>

3. Использование ComponentFactoryResolver для динамических компонентов

Для сложных случаев можно создавать компоненты форм динамически:

@Component({
  selector: 'app-dynamic-form-container',
  template: `<ng-container #formContainer></ng-container>`
})
export class DynamicFormContainerComponent implements OnInit {
  @ViewChild('formContainer', {read: ViewContainerRef}) container: ViewContainerRef;

  constructor(private resolver: ComponentFactoryResolver) {}

  ngOnInit() {
    this.loadFormComponents();
  }

  async loadFormComponents() {
    const config = await this.getFormConfigFromServer();

    config.sections.forEach(section => {
      const factory = this.resolver.resolveComponentFactory(FormSectionComponent);
      const componentRef = this.container.createComponent(factory);
      componentRef.instance.config = section;
    });
  }
}

Валидация в динамических формах

  1. Динамические валидаторы:
updateValidator(fieldName: string, validators: ValidatorFn[]) {
  const control = this.form.get(fieldName);
  control.setValidators(validators);
  control.updateValueAndValidity();
}
  1. Кросс-полевая валидация:
setCrossFieldValidation() {
  this.form.setValidators((group: FormGroup) => {
    const pass = group.get('password').value;
    const confirmPass = group.get('confirmPassword').value;
    return pass === confirmPass ? null : { mismatch: true };
  });
}

Оптимизация производительности

  1. Используйте ChangeDetectionStrategy.OnPush
  2. Кешируйте созданные FormControl'ы при изменении конфигурации
  3. Для больших форм применяйте виртуальный скроллинг
  4. Лениво загружайте части формы

Резюмируем

  1. Динамические формы создаются во время выполнения приложения
  2. Основные подходы:
    • Программное создание FormGroup/FormArray
    • Генерация формы из конфигурации
    • Динамическое создание компонентов форм
  3. Ключевые технологии:
    • FormBuilder для создания контролов
    • FormArray для динамических списков
    • Reactive Forms для управления состоянием
  4. Валидация может быть динамической и кросс-полейной
  5. Производительность важна для сложных форм

Динамические формы открывают возможности для создания гибких, адаптивных интерфейсов, которые могут меняться без перекомпиляции приложения.