Я работаю над созданием повторно используемых компонентов ввода форм, используя реактивные формы Angular.
Что касается моего компонента ввода FormArray, я столкнулся с проблемами.
Я обнаружил, что мне нужно использовать приведение двойного типа:
get arrayGroup(): FormGroup {
return this.formArray as AbstractControl as FormGroup;
}
Потому что, если я не обернул входной HTML примерно так:
<div [formGroup] = "arrayGroup"></div>
Я получил эту ошибку:
NG01053: formGroupName необходимо использовать с родительской директивой formGroup. Вам понадобится добавить директиву formGroup и передать ее существующему экземпляру FormGroup (вы можете создать его в своем классе).
Мое решение работает и выполняет свою работу, но должен быть способ получше.
Я поделился двумя компонентами: формой и вводом.
** Компоненты **
// FILENAME: forms.component.ts
import { Component, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
import {
FormArray,
FormBuilder,
FormGroup,
ReactiveFormsModule,
} from '@angular/forms';
import { ArrayInputComponent } from './array-input/array-input.component';
@Component({
selector: 'app-forms',
standalone: true,
imports: [ArrayInputComponent, CommonModule, ReactiveFormsModule],
template: `
<div>
<h1>Forms</h1>
<form [formGroup] = "form" (ngSubmit) = "onSubmit()">
<app-array-input [formArray] = "formArray"></app-array-input>
<button>Submit</button>
</form>
</div>
`,
})
export class FormsComponent implements OnInit {
form: FormGroup = this.fb.group({});
constructor(private fb: FormBuilder) {}
ngOnInit(): void {
this.form.addControl('formArray', this.fb.array([]));
}
get formArray() {
return this.form.get('formArray') as FormArray;
}
onSubmit() {
console.info('Form valid: ', this.form.valid);
console.info('Form values: ', this.form.value);
}
}
// FILENAME: ./array-input/array-input.component
import { CommonModule } from '@angular/common';
import { Component, Input } from '@angular/core';
import {
AbstractControl,
FormArray,
FormBuilder,
FormGroup,
ReactiveFormsModule,
Validators,
} from '@angular/forms';
@Component({
selector: 'app-array-input',
standalone: true,
imports: [CommonModule, ReactiveFormsModule],
template: `
<div [formGroup] = "arrayGroup">
<h1>Array Input</h1>
<button type = "button" (click) = "add()">Add</button>
<ng-container *ngFor = "let item of this.formArray.controls; let i = index">
<div [formGroupName] = "i">
<input full placeholder = "name" formControlName = "name" />
<input placeholder = "relation" formControlName = "relation" />
</div>
</ng-container>
</div>
`,
})
export class ArrayInputComponent {
@Input() formArray!: FormArray;
constructor(private fb: FormBuilder) {}
add() {
const item = this.fb.group({
name: ['', [Validators.required]],
relation: [],
});
this.formArray.push(item);
}
get arrayGroup(): FormGroup {
return this.formArray as AbstractControl as FormGroup;
}
}
Угловая версия 17
ControlContainer
DI можно использовать для получения родительского FormGroup
, используя
this.formGroup = this.controlContainer!.control as FormGroup;
Тогда мы можем использовать это formGroup
, чтобы получить formArray
.
get formArray(): FormArray {
return this.formGroup.get('formArray') as FormArray;
}
После этого мы настраиваем formArrayName
и formGroup
, у которых есть отдельный DIV.
<div [formGroup] = "formGroup">
<div formArrayName = "formArray">
<h1>Array Input</h1>
<button type = "button" (click) = "add()">Add</button>
<ng-container *ngFor = "let item of this.formArray.controls; let i = index">
<div [formGroupName] = "i">
<input full placeholder = "name" formControlName = "name" />
<input placeholder = "relation" formControlName = "relation" />
</div>
</ng-container>
</div>
</div>
После этого ваш компонент должен работать нормально.
import { bootstrapApplication } from '@angular/platform-browser';
import 'zone.js';
import { CommonModule } from '@angular/common';
import { Component, Input } from '@angular/core';
import {
AbstractControl,
ControlContainer,
FormArray,
FormBuilder,
FormGroup,
FormGroupName,
NgForm,
ReactiveFormsModule,
Validators,
} from '@angular/forms';
@Component({
selector: 'app-array-input',
standalone: true,
imports: [CommonModule, ReactiveFormsModule],
template: `
<div [formGroup] = "formGroup">
<div formArrayName = "formArray">
<h1>Array Input</h1>
<button type = "button" (click) = "add()">Add</button>
<ng-container *ngFor = "let item of this.formArray.controls; let i = index">
<div [formGroupName] = "i">
<input full placeholder = "name" formControlName = "name" />
<input placeholder = "relation" formControlName = "relation" />
</div>
</ng-container>
</div>
</div>
`,
})
export class ArrayInputComponent {
formGroup: FormGroup = new FormGroup({});
constructor(
private fb: FormBuilder,
private controlContainer: ControlContainer
) {}
ngOnInit() {
console.info(this.controlContainer.control);
this.formGroup = this.controlContainer!.control as FormGroup;
}
add() {
const item = this.fb.group({
name: ['', [Validators.required]],
relation: [],
});
this.formArray.push(item);
}
get formArray(): FormArray {
return this.formGroup.get('formArray') as FormArray;
}
}
@Component({
selector: 'app-root',
standalone: true,
imports: [ArrayInputComponent, CommonModule, ReactiveFormsModule],
template: `
<div>
<h1>Forms</h1>
<form [formGroup] = "form" (ngSubmit) = "onSubmit()">
<app-array-input></app-array-input>
<button>Submit</button>
</form>
</div>
`,
})
export class App {
form: FormGroup = this.fb.group({});
constructor(private fb: FormBuilder) {}
ngOnInit(): void {
this.form.addControl('formArray', this.fb.array([]));
}
get formArray() {
return this.form.get('formArray') as FormArray;
}
onSubmit() {
console.info('Form valid: ', this.form.valid);
console.info('Form values: ', this.form.value);
}
}
bootstrapApplication(App);
Если вам нужно что-то еще более повторно используемое, вы можете указать имя массива в качестве рабочего примера ниже.
FormArray может быть FormArray из FormsControls или FormArray из FormsGroups.
В Angular 17 (почти) вы можете определить
formArrayOfControls!:FormArray<FormControl>
formArrayOfGroups!:FormArray<FormGroup>
Теперь вы можете повторить.
С новым @for
вы можете использовать
@for (control of formArrayOfControls.controls;let i=$index;track i)
{
//see that you use "formControl"
<input [formControl] = "control"/>
}
//and
@for (group of formArrayOfGroups.controls;track $index)
{
<div [formGroup] = "group">
<!--here you can use formControlName, e.g.-->
<input formControlName = "control"/>
</div>
}
Использование старого *ngFor
<div *ngFor = "let control of formArray.controls">
<input [formControl] = "control">
</div>
//and
<div *ngFor = "let group of formArray.controls" [formGroup] = "group">
<input formControlName = "control">
</div>
Таким образом, вы можете свободно передавать сам FormArray в свой компонент, просто используя
get formArray() {
return this.form.get('formArray') as FormArray<FormGroup>;
}
и пройти
<app-array-input [formArray] = "formArray"></app-array-input>
В Angular 17 вы можете использовать FormArray<FormGroup> :) (Через четыре года ребята из Angular решили проблему с итерацией и использованием переменной цикла.)