angular - Angular 响应式表单和 ControlValueAccessor,表单值始终为空
问题描述
您好我正在尝试使用响应式表单实现 ControlValueAccessor,但我总是收到父组件上表单的空值。我试图在很多地方使用子组件,因此我需要它是可重用的并且可以添加到父表单中
感谢帮助子组件
import { ControlValueAccessor, FormBuilder, FormControl, FormGroup, Validators, NG_VALUE_ACCESSOR, NG_VALIDATORS } from '@angular/forms';
import { Component, HostBinding, Input, OnDestroy, OnInit, forwardRef } from '@angular/core';
import { Observable, Subscription } from 'rxjs';
import { HttpClient } from '@angular/common/http';
import { Ng4LoadingSpinnerService } from 'ng4-loading-spinner';
import { ToastrService } from 'ngx-toastr';
import { startWith, map } from 'rxjs/operators';
import { cons } from 'src/app/services/helper.service';
export interface ImzaYetkilisi {
ad: '';
emp_no: '';
}
@Component({
selector: 'app-imza-yetkilisi-v3',
templateUrl: './imza-yetkilisi-v3.component.html',
styleUrls: ['./imza-yetkilisi-v3.component.css'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => ImzaYetkilisiV3Component),
multi: true
},
{
provide: NG_VALIDATORS,
useExisting: forwardRef(() => ImzaYetkilisiV3Component),
multi: true
}
]
})
export class ImzaYetkilisiV3Component implements OnInit, OnDestroy, ControlValueAccessor {
imzaYetkilisiForm: FormGroup;
filteredOptions = new Observable<any[]>();
yetkiliListesi = [];
imzaTuruList = [];
@Input() maxImzaSayisi = 0;
@Input() maxParafSayisi = 0;
@Input() imzaZorunlu = false;
@Input() parafZorunlu = false;
@Input() yetkiliIds: {};
@Input() parafciImzaci = false;
@HostBinding('disabled') isDisabled: boolean;
subscriptions: Subscription[] = [];
get value(): ImzaYetkilisi {
return this.imzaYetkilisiForm.value;
}
set value(value: ImzaYetkilisi) {
this.imzaYetkilisiForm.setValue(value);
this.onChange(value);
this.onTouched();
}
constructor(private fb: FormBuilder, private spinner: Ng4LoadingSpinnerService, private toastr: ToastrService,
private http: HttpClient) {
this.imzaYetkilisiForm = this.fb.group({
ad: '',
emp_no: new FormControl('', [Validators.required])
});
this.subscriptions.push(
this.imzaYetkilisiForm.valueChanges.subscribe(value => {
this.onChange(value);
this.onTouched();
})
);
}
onChange: any = () => { };
onTouched: any = (_: any) => { };
writeValue(obj: any): void {
}
registerOnChange(fn: any): void {
this.onTouched = fn;
}
registerOnTouched(fn: any): void {
this.onTouched = fn;
}
setDisabledState?(isDisabled: boolean): void {
this.isDisabled = isDisabled;
}
validate(_: FormControl) {
return this.imzaYetkilisiForm.valid ? null : { imzaci: { valid: false } };
}
ngOnInit(): void {
this.http.post(cons.baseUrl + 'xyz', this.yetkiliIds).toPromise()
.then((result: any) => {
// console.log(result);
this.yetkiliListesi = result;
this.filteredOptions = this.imzaYetkilisiForm.controls.ad.valueChanges.pipe(
startWith(''),
map(value => this._filter(value))
);
})
.catch(error => {
this.toastr.error('İmza yetkilileri çekilirken hata oluştur', 'Hata');
}).finally(() => { this.spinner.hide(); });
}
getImzaciAd(emp_no) {
return this.yetkiliListesi.find(y => y.EMP_NO === emp_no).AD;
}
imzaciChange(event) {
const emp = this.yetkiliListesi.find(y => y.EMP_NO === event.option.value);
this.imzaYetkilisiForm.patchValue({
emp_no: event.option.value,
ad: emp.AD,
emp_cat: emp.CAT
});
this.filteredOptions = this.imzaYetkilisiForm.controls.ad.valueChanges.pipe(
startWith(''),
map(value => this._filter(value))
);
this.onChange(this.imzaYetkilisiForm.value);
this.onTouched();
}
private _filter(value: string): string[] {
const filterValue = value.toLowerCase();
return this.yetkiliListesi.filter((option: any) => option.AD.toLowerCase().indexOf(filterValue) === 0);
}
onBlur() {
this.onChange(this.imzaYetkilisiForm.value);
this.onTouched();
}
ngOnDestroy(): void {
this.subscriptions.forEach(s => s.unsubscribe());
}
}
子组件的 HTML 是;
<div [formGroup]="imzaYetkilisiForm">
<div class="form-group">
<mat-form-field class="col-sm-6">
<mat-label>İmza Yetkilisi</mat-label>
<input type="text" placeholder="Yetkili Seçin" aria-label="Number" matInput formControlName="emp_no" id="empno"
[matAutocomplete]="auto" #autoInput >
<mat-autocomplete (optionSelected)="imzaciChange($event)" autoActiveFirstOption #auto="matAutocomplete"
[displayWith]="getImzaciAd.bind(this)" >
<mat-option *ngFor="let yetkili of filteredOptions |async" [value]="yetkili.EMP_NO">
{{yetkili.AD}}
</mat-option>
</mat-autocomplete>
</mat-form-field>
</div>
</div>
父表单是
export class BankaOdemeTalimatModalComponent implements OnInit {
imzaForm: FormGroup;
imzaYekileri: any;
constructor(public activeModal: NgbActiveModal, public formBuilder: FormBuilder) {
this.imzaForm = this.formBuilder.group({
imzaci: []
});
}
ngOnInit(): void {
this.imzaYekileri = { ust_yonetim: true, direktor: true, personel: ['10488', '10484'] };
}
addImzaci() {
console.log(this.imzaForm.value);
}
和 HTML
<form [formGroup]="imzaForm" #f="ngForm" (ngSubmit)="addImzaci()">
<div class="modal-boy pt-1">
<div class="largeContent">
<app-imza-yetkilisi-v3 [yetkiliIds]="imzaYekileri" [maxImzaSayisi]="2" [imzaZorunlu]="true" [maxParafSayisi]="2"
[parafZorunlu]="false" [parafciImzaci]="false" formControlName="imzaci">
</app-imza-yetkilisi-v3>
</div>
<!-- <app-sup-list formControlName="testForm"></app-sup-list> -->
</div>
<div class="col-sm-12 d-flex justify-content-end">
<button class="btn btn-sm btn-success" type="submit"
>Ekle</button>
</div>
</form>
解决方案
@MuhandY,在实现 ControlValueAccessor 的自定义表单控件中,有两个重要功能
1.-写值。这使得当你的 formControl 变成一个值时,控件显示该值(你可以想象为一个“初始化”的函数),所以你需要做一些像
writeValue(obj: any): void {
this.imzaYetkilisiForm.setValue(obj); //<--add this line
}
2.-onChange。嗯,真的是你在 registerOnChnage 中所说的功能,所以你需要做一些像
registerOnChange(fn: any): void {
this.onChange = fn; //<---is this.onChange
}
所以,每次我们想对“父母”说值已经改变时,你使用
this.onChange(value) //<--value the value your want "transmit"
当您想更改“在父级”中的值时,您只需要调用“this.Change”。例如,您有一个 formGroup 并订阅了 valueChanges,只需要在订阅中调用此函数 - 它不需要在 blur 中制作,或者如果您正在更改另一个不会更改值的输入
推荐阅读
- mysql - 使用mysql工作台创建表时出错
- package - Common Lisp 包 - 当包可能无法加载时如何处理?
- flutter - Flutter:是否可以在 main.dart 中实现一次快餐栏,例如,当连接状态发生变化时,用于所有屏幕
- python - DataError:没有数字类型可以聚合尝试对字母数字值进行排名
- ios - 使用 NSCalendar Swift 查找周日和周一
- c - 使用传递的参数返回指向函数的指针的函数
- c - ANSI 颜色代码删除 printf
- javascript - 为什么 VS Studio Code 会在输入时插入我不想要的代码?
- vb.net - 如何在自定义控件组合框中枚举 My.Setting.Connectionstring
- python - 在 jupyter 笔记本中出现导入错误?