#angular #typescript #angular-reactive-forms #controlvalueaccessor
#angular #typescript #угловые реактивные формы #controlvalueaccessor
Вопрос:
Я попытался создать 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-ARRAY имеет.
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-FORM
для каждой группы форм я создал отдельный компонент на случай, если я хочу использовать сам компонент, а не весь массив.
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.
вот ее слайды.
помогите, пожалуйста!
Комментарии:
1. Мне непонятно, вы спрашиваете о фоновой форме?
2. ДА. если вы посмотрите на json. вы увидите, что основная форма (фоновая форма) не синхронизирована с другими.
3. Взгляните на ответ posetd
Ответ №1:
Когда вы используете «пользовательский элемент управления формой», вам необходимо учитывать, что вы передаете элемент управления cursom Form с помощью элемента управления Form (не FormArray, не FormGroup). FormControl имеет в качестве значения массив или объект, но вас это не должно смущать.(*)
Вы можете увидеть в работе в stackblitz
Это ваша форма, как
//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(),
...
})
Я создал const для экспорта просто в код. МОЙ const expor
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 хранит объект {год: .. месяц: .., день ..},mat-multiselect хранит массив, …)
Обновление Конечно, мы можем передавать наш элемент управления данными из сервиса или аналогичного. Единственное, что мы должны учитывать, это то, как мы передаем данные. Как обычно, мне нравится создавать функцию, которая получила данные или значение null и возвращает 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
}
и выберите другой интерфейс
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, если он обновлен
Комментарии:
1. Спасибо за ответ! могу ли я использовать интерфейс вместо const? Я не хочу, чтобы форма-массив была привязана к основной форме. Или вы думаете, что у меня должен быть const в другом файле?
2. здравствуйте, я попытался поместить решение в свой проект. Я пытаюсь загрузить исходные данные, которые уже существуют. Я следовал тому же коду, что и ваш. но я заметил, что когда я инициализирую данные, они не передаются дочерним элементам, и когда я пытаюсь добавить другие маршруты, он помещает индекс перед объектами массива. вот так «маршруты»: { «0»: { «RegionID»: «» … «municipalityId»: «» } } . это также может быть проблемой с инициализацией. знаете ли вы, какое может быть решение?
3. @Vato, я обновил ответ и stackblitz, надеюсь, это поможет. Я пытался использовать интерфейс или константу, но безрезультатно:(, но, конечно, интерфейсы и константы могут быть вне основной формы
4. @Eliseo, спасибо, он отлично работает. но он все равно добавляет индекс перед массивами после добавления адреса. он создает массивы { 0: {} 1: {} … } и т.д. перед добавлением массив выглядит нормально [{ },{ },{ }]
5. чтобы избежать изменения выражения… иногда это полезно
setTimeOut(()=>{...your code..})
Ответ №2:
Я считаю, что проблема здесь в том, что formArrayName
это не вход для NG_VALUE_ACCESSOR/DefaultValueAccessor
.
Также обратите внимание:
Ее примеры статичны
parent->multiple children
… значение от 1 до многих и не динамическое. Вы пытаетесь создать статическиеparent
для многих динамическихchild->grandChild
отношений, построенных изformArray
, затем пытаетесь динамически связатьgrandChild
форму с родительскойformArrayIndex
формой, которая была передана черезchild
grandChild
. Ваш stackblitz отклоняется от структуры, которую она преподает, и, безусловно, представляет некоторые новые проблемы, не рассмотренные в лекции.
Возможным решением может быть изучение того, как перебирать 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>
Селекторы
- ввод: нет([type=checkbox])[formControlName]
- текстовая область [formControlName]
- ввод: нет ([type=checkbox])[FormControl]
- текстовая область [FormControl]
- ввод: нет([type=checkbox])[ngModel]
- текстовая область[ngModel]
- [ngDefaultControl]
https://angular.io/api/forms/DefaultValueAccessor#selectors
Единственными вариантами ввода являются formControlName
, formControl
, ngModel
и ngDefaultControl
…
Это причина
formArrayName
, по которой, однако, неmain-form
<--> cva-form-arrayformControl
будет работатьchild-child to child level
, поскольку вы передаете единственноеformControl
число в свойapp-cva-form
, из своегоapp-cva-form-array
через*ngFor
цикл.
<mat-card *ngFor="let route of getForm.controls;
<app-cva-form [formControl]="route" (blur)="onTouched()"></app-cva-form>
Я считаю, что ключ к пониманию здесь заключается в том, что formArray
это всего лишь организационный контейнер для его дочерних элементов… он не будет делать то, что вы хотите, в этом сценарии без помощи дополнительной логики.
В настоящее время, по-видимому, нет необходимой функциональности для приема
formArray
в качестве входных данных, итерации / динамического управления массивом и ссылки изменений обратно на родительскийformArray
.
Ответ №3:
Вам необходимо передать обновленные данные формы из дочернего компонента в родительский компонент. Я использовал this.form.valueChanges()
метод для обнаружения изменений, а затем передал значение формы родительскому компоненту.
Родительский компонент:
HTML-код:
<app-cva-form-array formArrayName="routes" (onChildFormValueChange)="onFormChange($event)"></app-cva-form-array>
TS-код:
public onFormChange(form): void {
this.requestForm = form;
}
Дочерний компонент:
HTML-код:
No change:)
TS-код:
@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();
}
Комментарии:
1. это дает только одностороннюю привязку. хотя я знаю, как сделать 2 способа, это все еще не то, что я ищу. если вы проверите, как cva-form-array привязывается к cva-form, я хочу добиться аналогичной привязки для main-form <—> cva-form-array . цель использования cva — упростить код и не использовать дополнительные функции.
2. Я добавил внешние ресурсы о происхождении кода в нижней части вопроса.
3. @Vato Нет, это двусторонняя привязка
4. Если вы измените основную форму, cva-form-array не изменится. если вы напишете RegionName: new FormControl(‘asdfa’), в main-form он оставляет имена регионов дочерних компонентов null.
5. stackblitz.com/edit/angular-b6nm8c здесь, если вы добавляете массив из родительского, он не подключается к дочернему, а только наоборот. но в любом случае, как я уже сказал, ни одна из функций не должна требоваться с использованием cva.