Я попытался иметь 2 вложенные формы, используя CVA. проблема в том, что второй из не инициализируется данными, когда я привязываю его к formControl.
У меня есть ГЛАВНАЯ ФОРМА:
this.requestForm = this.fb.group({
garageId: 0,
routes: new FormArray([
new FormGroup({
addressPointId: new FormControl,
municipalityId: new FormControl,
regionId: new FormControl,
rvId: new FormControl,
sequenceNumber: new FormControl,
settlementId: new FormControl,
regionName: new FormControl,
municipalityName: new FormControl,
settlementName: new FormControl,
description: new FormControl,
})
]),
endDateTime: 0,
});
В html основной формы я связываю маршруты с помощью formArrayName.
<app-cva-form-array formArrayName = "routes"></app-cva-form-array>
Компонент CVA-ФОРМА-МАССИВ имеет.
form = new FormArray([
new FormGroup({
addressPointId: new FormControl,
municipalityId: new FormControl,
regionId: new FormControl,
rvId: new FormControl,
sequenceNumber: new FormControl,
settlementId: new FormControl,
regionName: new FormControl,
municipalityName: new FormControl,
settlementName: new FormControl,
description: new FormControl,
})
]);
Отсюда все работает нормально. Я привязываю каждую группу форм в массиве к дочернему компоненту CVA-FORM.
<app-cva-form [formControl] = "route" (blur) = "onTouched()"></app-cva-form>
CVA-ФОРМА для каждой formGroup я создал отдельный компонент на случай, если я хочу использовать сам компонент, а не весь массив.
form: FormGroup = new FormGroup({
regionName: new FormControl,
regionId: new FormControl,
municipalityName: new FormControl,
municipalityId: new FormControl,
sequenceNumber: new FormControl,
settlementName: new FormControl,
settlementId: new FormControl,
addressPointId: new FormControl,
description: new FormControl,
rvId: new FormControl,
});
привязка main-form <--to--> app-cva-form-array по какой-то причине не работает.
Идея этих форм исходит от выступление Кары на angulaconnect.вот ее слайды.
помогите плз!
да. если вы посмотрите на jsons. вы увидите, что основная форма (фоновая форма) не синхронизирована с другими.
Посмотрите на ответ posetd
Вам необходимо передать обновленные данные формы из дочернего компонента в родительский компонент. Я использовал метод this.form.valueChanges()
для обнаружения изменений, а затем передал значение формы родительскому компоненту.
Parent Component:
HTML-код:
<app-cva-form-array formArrayName = "routes" (onChildFormValueChange) = "onFormChange($event)"></app-cva-form-array>
Код ТС:
public onFormChange(form): void {
this.requestForm = form;
}
Child Component:
HTML-код:
No change:)
Код ТС:
@Output() onChildFormValueChange: EventEmitter<any> = new EventEmitter<any>();
registerEvent() {
this.form.valueChanges.subscribe(() => {
this.onFormValueChange()
});
}
public onFormValueChange(): void {
this.onChildFormValueChange.emit(this.form);
}
и вызовите метод registerEvent
в конструкторе, например:
constructor(){
this.registerEvent();
}
это дает только одностороннюю привязку. хотя я знаю, как сделать 2 способа, это все еще не то, что я ищу. если вы проверите, как cva-form-array привязывается к cva-form, я хочу добиться аналогичной привязки для main-form <--> cva-form-array. цель использования cva — упростить код и не использовать лишние функции.
Я добавил внешние ресурсы о происхождении кода внизу вопроса.
@Vato Нет, это двусторонняя привязка
Если вы измените основную форму, массив cva-form не изменится. если вы пишете regionName: new FormControl('asdfa'), в основной форме он оставляет имена областей дочерних компонентов нулевыми.
Я считаю, что проблема здесь в том, что formArrayName
не является входом для NG_VALUE_ACCESSOR/DefaultValueAccessor
.
Также обратите внимание:
Her examples are static
parent->multiple children
... meaning 1 to many and not dynamic. You are attempting staticparent
to many dynamicchild->grandChild
relationships built fromformArray
, then trying to dynamically linkgrandChild
form to parentformArrayIndex
that was passed through thechild
to thegrandChild
. Your stackblitz deviates from the structure she is teaching, and certainly introduces some new challenges not covered in the lecture.
Изучение того, как перебирать FormArray
на уровне parent
и создавать экземпляры отношений child->grandchild
внутри этого цикла, может быть возможным решением, таким образом, вы не передаете весь массив вниз, а только formGroup
, который будет применяться.
<h1>MAIN FORM</h1>
{{ requestForm.value | json }}
<div *ngFor = "let route of requestForm.get('routes').controls">
<app-cva-form-array formControl = "route" (onChildFormValueChange) = "onFormChange($event)"></app-cva-form-array>
</div>
Селекторы
https://angular.io/api/forms/DefaultValueAccessor#selectors
Единственными вариантами ввода являются formControlName
, formControl
, ngModel
и ngDefaultControl
...
This is the reason
formArrayName
will not work inmain-form <--> cva-form-array
however,formControl
will work forchild-child to child level
, as you are passing a singularformControl
into yourapp-cva-form
, from yourapp-cva-form-array
via the*ngFor
loop.
<mat-card *ngFor = "let route of getForm.controls;
<app-cva-form [formControl] = "route" (blur) = "onTouched()"></app-cva-form>
Я считаю, что ключ к пониманию здесь заключается в том, что formArray
— это всего лишь организационный контейнер для своих дочерних элементов... он не будет делать то, что вы хотите, в этом сценарии без помощи дополнительной логики.
There currently doesn't appear to be the necessary functionality to accept
formArray
as an input, iterate/dynamically manage the array, and link changes back to the parentformArray
.
Когда вы используете «настраиваемый элемент управления формой», вам необходимо учитывать, что вы загружаете элемент управления формой cursom с помощью элемента управления формы (не FormArray, не FormGroup). FormControl имеет в качестве значения массив или объект, но вам не нужно смущаться по этому поводу. (*)
Посмотреть в работе можно в стекблиц
Это твоя форма, как
//in main.form
this.requestForm = new FormGroup({
garageId: new FormControl(0),
routes: new FormControl(routes), //<--routes will be an array of object
endDateTime: new FormControl(0)
})
//in cva-form-array
this.form=new FormArray([new FormControl(...)]); //<-this.form is a
//formArray of FormControls NOT of formGroup
//finally in your cva-form
this.form=new FormGroup({});
this.form=formGroup({
addressPointId: new FormControl(),
municipalityId: new FormControl(),
...
})
Я создал константу для экспорта просто в код. МОЙ постоянный экспорт
export const dataI = {
addressPointId: "",
municipalityId: "",
regionId: "",
rvId: "",
sequenceNumber: "",
settlementId: "",
regionName: "",
municipalityName: "",
settlementName: "",
description: "",
}
Итак, в mainForm у нас есть
ngOnInit() {
let routes:any[]=[];
routes.push({...dataI});
this.requestForm = new FormGroup({
garageId: new FormControl(0),
routes: new FormControl(routes),
endDateTime: new FormControl(0)
})
}
<mat-card [formGroup] = "requestForm" style = "background: #8E8D8A">
<app-cva-form-array formControlName = "routes"></app-cva-form-array>
</mat-card>
В массиве cvc-form создайте formArray, когда мы дадим значение
writeValue(v: any) {
this.form=new FormArray([]);
for (let value of v)
this.form.push(new FormControl(value))
this.form.valueChanges.subscribe(res=>
{
if (this.onChange)
this.onChange(this.form.value)
})
}
<form [formGroup] = "form" >
<mat-card *ngFor = "let route of form.controls;
let routeIndex = index; let routeLast = last;">
<button (click) = "deleteRoute(routeIndex)">
cancel
</button>
<app-cva-form [formControl] = "route" (blur) = "onTouched()"></app-cva-form>
</form>
Наконец, cva-форма
writeValue(v: any) {
this.form=new FormGroup({});
Object.keys(dataI).forEach(x=>{
this.form.addControl(x,new FormControl())
})
this.form.setValue(v, { emitEvent: false });
this.form.valueChanges.subscribe(res=>{
if (this.onChanged)
this.onChanged(this.form.value)
})
}
<div [formGroup] = "form">
<mat-form-field class = "locationDate">
<input formControlName = "regionName">
<mat-autocomplete #region = "matAutocomplete"
(optionSelected) = "selectedLocation($event)">
<mat-option *ngFor = "let region of regions"
[value] = "region">
{{region.regionName}}
</mat-option>
</mat-autocomplete>
</mat-form-field>
<mat-form-field class = "locationDate">
<input formControlName = "municipalityName"
[matAutocomplete] = "municipality"
(blur) = "onTouched()"
[readonly] = "checked || this.form.value.regionId < 1">
....
</form>
(*) Да, мы привыкли видеть, что FormControl имеет в качестве значения строку или число, но никто не запрещает нам, чтобы значение было объектом или массивом (например, ng-bootstrap DatePicker хранит объект {year : .. месяц: .., день ..}, mat-multiselect хранит массив, ...)
Обновлять Конечно, мы можем передать нашему элементу управления данные из службы или чего-то подобного. Единственное, что мы должны учитывать, это то, как мы предоставляем данные. Как обычно, мне нравится создавать функцию, которая получает данные или ноль и возвращает FormControl.
getForm(data: any): FormGroup {
data = data || {} as IData;
return new FormGroup({
garageId: new FormControl(data.garageId),
routes: new FormControl(data.routes),
endDateTime: new FormControl(data.endDateTime)
})
}
где IData — это интерфейс
export interface IData {
garageId: number;
routes: IDetail[];
endDateTime: any
}
и IDetail другой интерфейс
export interface IDetail {
addressPointId: string;
...
description: string;
}
Тогда у нас могут быть сложные данные, например (извините за большой объект)
let data = {
garageId: 1,
routes: [{
addressPointId: "adress",
municipalityId: "municipallyty",
regionId: "regionId",
rvId: "rvId",
sequenceNumber: "sequenceNumber",
settlementId: "settlementId",
regionName: "regionName",
municipalityName: "municipalityName",
settlementName: "settlementName",
description: "description",
},
{
addressPointId: "another adress",
municipalityId: "another municipallyty",
regionId: "another regionId",
rvId: "another rvId",
sequenceNumber: "another sequenceNumber",
settlementId: "another settlementId",
regionName: "another regionName",
municipalityName: "another municipalityName",
settlementName: "another settlementName",
description: "another description",
}],
endDateTime: new Date()
}
Тогда только нужно сделать
this.requestForm = this.getForm(data);
Stackblitz, если он обновлен
Спасибо за ответ! я могу использовать интерфейс вместо константы? Я не хочу, чтобы массив форм был привязан к основной форме. Или вы думаете, что я должен иметь const в другом файле?
привет, я попытался поместить решение в свой проект. Я пытаюсь загрузить исходные данные, которые уже существуют. Я следовал тому же коду, что и ваш. но я заметил, что когда я инициализирую данные, они не передаются дочерним элементам, а когда я пытаюсь добавить другие маршруты, он ставит индекс перед объектами массива. например, "маршруты": { "0": { "regionId": "" ... "municipalityId": "" } }. это может быть проблемой и с инициализацией. знаете какое может быть решение?
@Vato, я обновил ответ и stackblitz, надеюсь, это поможет. Насчет использования интерфейса или константы я пробовал, но безрезультатно :(, но конечно интерфейсы и константы могут быть вне основной формы
@Eliseo, спасибо, отлично работает. но он по-прежнему добавляет индекс перед массивами после добавления адреса. он делает массивы { 0: {} 1: {} ... } и т. д., прежде чем вы добавите массив, который выглядит нормально [{ }, { }, { }]
неважно. Я удалил распространение {...} из onChange, и он добавляется в массив без индексов. Я еще не знаком с распространением, поэтому это меня смутило. Спасибо за помощь!
Я рад, что вы нашли "ошибку"
Подумав над проблемой, реально можно убрать вообще this.onChange, в stackblitz.com/edit/angular-6seas7?file=src/app/cva-form-array/… (forket stackblitz) удаляю и смотрю как работает
@Eliseo по какой-то причине я до сих пор не могу предварительно загрузить данные из сервиса. Я получаю каждый маршрут с сервера и нажимаю их на route.push({...route}), но когда данные попадают в массив cva-form-array, они не проходят через цикл writeValue, где вы добавляете их в форму. Я заметил, что он не входит в цикл for (const value of v). В первом случае, когда я добавляю dataI, я печатаю v в writeValue и получаю [{...}] в консоли, но когда я пишу новые данные с сервера поверх dataI, в writeValue печатается []. Оба массива состоят из объектов одного типа, когда я расширяю их в инструментах браузера, но сначала это просто пустые значения ' '.
в чем разница между [] и [{...}] и как это может повлиять на цикл for (const value of v)? Извините, что снова беспокою.
чтобы избежать проблемы, которая у меня была со скобками (несвоевременная загрузка файлов). Я вызвал сервис в родительском компоненте, который вызывает основную форму в диалоге. Поэтому я передал в диалог уже готовые данные. Таким образом, формы заполняются сейчас. Я не уверен, есть ли лучший способ или время вызвать в мою службу, но это пока отлично работает :)
единственное, что теперь я получаю ExpressionChangedAfterItHasBeenCheckedError. что не так уж и плохо. Я постараюсь решить это.
чтобы избежать ExpressionChanged... иногда полезно использовать setTimeOut(()=>{...your code..})
Я предполагаю, что вложенное состояние проверки FormControls/FormGroups не может быть отражено в форме верхнего уровня при использовании этого метода. Я ошибся ? Было бы здорово распространить состояние проверки элементов управления app-cva-form на основную форму.
Мне непонятно, вы спрашиваете о форме фона?