首页 > 解决方案 > 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>

标签: angularangular-reactive-forms

解决方案


@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 中制作,或者如果您正在更改另一个不会更改值的输入


推荐阅读