Введение
В процессе разработки веб-приложений на 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: () => inject(ControlContainer, { skipSelf: true })
}
]
})
export class UserEmailFormComponent {}
Заключение
В этой статье мы рассмотрели проблемы, связанные с дублированием кода и сложностью создания реактивных форм в Angular. Мы рассмотрели несколько эффективных решений для создания переиспользуемых форм. Использование подходов, таких как viewProviders
, не только улучшает качество кода, но и упрощает процесс разработки. Применяя эти методы, вы сможете создавать более гибкие и чистые формы.