Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular Material custom form field control's value is not updated in the calling FormGroup

I made a custom form field control in Angular Material following this guide.

Then I add this control in my FormGroup. But the problem I have here is the FormGroup is not able to get the correct value of the custom control. It always gets undefined. I did check if the correct values are being seeded to the value property in the custom control from the input and it does.

What could be the problem here?

My Custom Control: The Component

import { Component, OnDestroy, HostBinding, Input, Optional, Self, ElementRef } from '@angular/core';
import { MatFormFieldControl } from '@angular/material/form-field';
import { Subject } from 'rxjs/internal/Subject';
import { NgControl, ControlValueAccessor, FormBuilder, FormGroup } from '@angular/forms';
import { FocusMonitor } from '@angular/cdk/a11y';
import { coerceBooleanProperty } from '@angular/cdk/coercion';

@Component({
  selector: 'app-test-input',
  templateUrl: './test-input.html',
  styleUrls: ['./test-input.scss'],
  providers: [{ provide: MatFormFieldControl, useExisting: MyTestInput }]
})
export class MyTestInput implements MatFormFieldControl<string>, OnDestroy, ControlValueAccessor {
  static nextId = 0;
  FormGrp: FormGroup;
  stateChanges = new Subject<void>();
  private val: string;
  private ph: string;
  private req = false;
  private dis = false;
  onChange: () => void;
  onTouched: () => void;

  public get value(): string {
    return this.val;
  }

  public set value(val: string) {
    this.val = val;
    this.stateChanges.next();
  }

  controlType = 'my-test-input';

  @HostBinding() id = `${this.controlType}-${MyTestInput.nextId++}`;

  @Input()
  get placeholder() {
    return this.ph;
  }
  set placeholder(plh) {
    this.ph = plh;
    this.stateChanges.next();
  }

  focused = false;

  get empty() {
    return false;
  }

  @HostBinding('class.floating')
  get shouldLabelFloat() {
    return this.focused || !this.empty;
  }

  @Input()
  get required() {
    return this.req;
  }
  set required(req) {
    this.req = coerceBooleanProperty(req);
    this.stateChanges.next();
  }

  @Input()
  get disabled(): boolean { return this.dis; }
  set disabled(value: boolean) {
    this.dis = coerceBooleanProperty(value);
    this.stateChanges.next();
  }

  errorState = this.FormGrp == null ? false : this.FormGrp.invalid;

  @HostBinding('attr.aria-describedby') describedBy = '';

  setDescribedByIds(ids: string[]) {
    this.describedBy = ids.join(' ');
  }

  onContainerClick(event: MouseEvent) {
    if ((event.target as Element).tagName.toLowerCase() !== 'input') {
      this.elRef.nativeElement.querySelector('input').focus();
    }
    this.onTouched();
  }

  ngOnDestroy() {
    this.stateChanges.complete();
    this.fm.stopMonitoring(this.elRef.nativeElement);
  }


  constructor(
    @Optional() @Self() public ngControl: NgControl,
    fb: FormBuilder, private fm: FocusMonitor, private elRef: ElementRef<HTMLElement>) {
    this.FormGrp = fb.group({
      data: ['', this.required]
    });
    if (ngControl != null) {
      ngControl.valueAccessor = this;
    }
    fm.monitor(elRef.nativeElement, true).subscribe(origin => {
      this.focused = !!origin;
      this.stateChanges.next();
    });
  }

  writeValue(value: any): void {
    this.FormGrp.get('data').setValue(value);
  }

  registerOnChange(fn: any): void {
    this.onChange = fn;
  }

  registerOnTouched(fn: any): void {
    this.onTouched = fn;
  }

  setDisabledState(isDisabled: boolean): void {
    isDisabled ? this.FormGrp.get('data').disable() : this.FormGrp.get('data').enable();
  }

  input() {
    this.value = this.FormGrp.get('data').value;
    this.onChange();
  }
}

The Template:

<div [formGroup]="FormGrp">
    <input formControlName="data" (input)="input()">
</div>

My Calling form:

<form [formGroup]="Form">  
    <mat-form-field>
        <app-test-input formControlName="testControl"></app-test-input>
    </mat-form-field>
    <button>Submit</button>
</form>
<p *ngIf="Form">
    {{Form.value | json}}
</p>

My Calling form definition:

this.Form = fb.group({
  testControl: ['', [Validators.required]]
});
like image 617
Anup Sharma Avatar asked Sep 17 '25 00:09

Anup Sharma


1 Answers

You forgot to pass updated value to onChange method which is part of ControlValueAccessor implementation:

test-input.component.ts

export class TestInputComponent ... {
 onChange = (_: any) => {};

 ...
 input() {
    this.value = this.FormGrp.get('data').value;

    this.onChange(this.value);
                      \/
                  pass newValue
  }

Stackblitz Example

like image 180
yurzui Avatar answered Sep 18 '25 12:09

yurzui