Как стать автором
Обновить

Переиспользуемые реактивные формы в Angular

Уровень сложностиПростой

Введение

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

Проблемы при создании реактивных форм

Рассмотрим проблемы на примере кода:

import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';

@Component({
  selector: 'app-user-form',
  template: `
    <form>
      <div>
        <label for="name">Name:</label>
        <input>
        <div>
          Name is required.
        </div>
      </div>
    
      <div>
        <label for="email">Email:</label>
        <input id="email">
        <div>
          A valid email is required.
        </div>
      </div>
    
      <div>
        <label for="age">Age:</label>
        <input type="number" id="age">
        <div>
          Age must be a positive number.
        </div>
      </div>
    
      <button type="submit">Submit</button>
    </form>
  `
})
export class UserFormComponent implements OnInit {
  userForm: FormGroup;

  constructor(private fb: FormBuilder) {}

  ngOnInit(): void {
    this.userForm = this.fb.group({
      name: ['', Validators.required],
      email: ['', [Validators.required, Validators.email]],
      age: ['', [Validators.required, Validators.min(1)]]
    });
  }

  onSubmit(): void {
    if (this.userForm.valid) {
      console.log('Form Submitted!', this.userForm.value);
    } else {
      console.log('Form is invalid');
    }
  }
}

Предположим, что часть формы содержит сложную логику или используется в нескольких местах. Логичным решением будет вынести эту часть в отдельный компонент. Однако при попытке использовать вынесенную часть формы могут возникнуть проблемы:

import { Component } from '@angular/core';

@Component({
  selector: 'app-user-email-form',
  template: `
    <div>
      <label for="email">Email:</label>
      <input id="email">
      <div>
        A valid email is required.
      </div>
    </div>
  `
})
export class UserEmailFormComponent {}

При использовании этого компонента возникает ошибка:

Error: formControlName must be used with a parent formGroup directive.

Чтобы понять, что произошло, важно обратить внимание на реализацию директивы formControlName. В частности, на строчку в конструкторе:

@Optional() @Host() @SkipSelf() parent: ControlContainer,

Декораторы Host и SkipSelf играют ключевую роль в этом вопросе. SkipSelf пропускает текущий уровень и ищет зависимость на уровне родительского компонента или сервиса. Host указывает Angular искать зависимость только в текущем компоненте или директиве.

Решения для эффективного создания переиспользуемых реактивных форм

Первый подход к решению проблемы — добавить параметр Input в компонент и передать туда экземпляр formGroup:

import { Component, Input } from '@angular/core';
import { FormGroup } from '@angular/forms';

@Component({
  selector: 'app-user-email-form',
  template: `
    <div>
      <label for="email">Email:</label>
      <input id="email">
    </div>
  `
})
export class UserEmailFormComponent {
  @Input() form: FormGroup;
}

В основном компоненте использование будет выглядеть так:

<app-user-email-form [form]="userForm"></app-user-email-form>

Хотя это простое решение, есть более элегантный подход.

Прокидывание зависимости

В Angular используются providers и viewProviders для определения сервисов. Рассмотрим различия:

  • providers: сервис доступен текущему компоненту и всем его дочерним компонентам.

  • viewProviders: сервис доступен только дочерним компонентам текущего представления.

В нашем случае подойдёт viewProviders. Добавим недостающую зависимость и избавимся от параметра Input:

@Component({
  selector: 'app-user-email-form',
  template: `
    <div>
      <label for="email">Email:</label>
      <input id="email">
    </div>
  `,
  viewProviders: [
    {
      provide: ControlContainer,
      useFactory: () =&gt; inject(ControlContainer, { skipSelf: true })
    }
  ]
})
export class UserEmailFormComponent {}

Заключение

В этой статье мы рассмотрели проблемы, связанные с дублированием кода и сложностью создания реактивных форм в Angular. Мы рассмотрели несколько эффективных решений для создания переиспользуемых форм. Использование подходов, таких как viewProviders, не только улучшает качество кода, но и упрощает процесс разработки. Применяя эти методы, вы сможете создавать более гибкие и чистые формы.

Теги:
Хабы:
Данная статья не подлежит комментированию, поскольку её автор ещё не является полноправным участником сообщества. Вы сможете связаться с автором только после того, как он получит приглашение от кого-либо из участников сообщества. До этого момента его username будет скрыт псевдонимом.