Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular reactive forms and ControlValueAccessor, form value is always null

Hi am trying to implement ControlValueAccessor with reactive forms, but I always receive null value for the form on the parent component. Am trying to use the child component in many places therefore I need it to be reusable and can be added to parent forms

Thanks for help Child component

    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());
  }
}

the HTML for child component is ;

<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>

and the parent form is

    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);
  }

and the 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>
like image 749
MuhanadY Avatar asked Oct 20 '25 09:10

MuhanadY


1 Answers

@MuhandY, In a Custom form control that implements ControlValueAccessor there're two important functions

1.-writeValue. This makes that when your formControl, becomes with a value, the control show the value (you can imaginate as a function that "initialize"), so you need makes some like

writeValue(obj: any): void {
  this.imzaYetkilisiForm.setValue(obj); //<--add this line
}

2.-onChange. well really the function you say in registerOnChnage, So you need makes some like

registerOnChange(fn: any): void {
    this.onChange = fn; //<---is this.onChange
  }

So, each time we want to say to the "parent" that the value has changed you use

 this.onChange(value) //<--value the value your want "transmit"

You only need call to "this.Change" when you want to change the value "in parent". e.g. you has a formGroup and subscribe to valueChanges, only need call to this function in the subscription -it's not necesary make in blur or if you're changing another input that not change the value

like image 185
Eliseo Avatar answered Oct 21 '25 22:10

Eliseo