Reputation: 2083
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]]
});
Upvotes: 4
Views: 2708
Reputation: 214165
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
}
Upvotes: 4