Components: Cannot create a custom MatFormFieldControl that implement ControlValueAccessor

Created on 31 Oct 2017  路  23Comments  路  Source: angular/components

Bug, feature request, or proposal:

Bug/Feature Request

What is the expected behavior?

The guide "Creating a custom form field control" should give an example with a custom MatFormFieldControl that implement ControlValueAccessor.

What is the current behavior?

The guide advise us to use:

constructor(..., @Optional() @Self() public ngControl: NgControl) { ... }

But it leads to a cyclic dependancy error:

ERROR Error: Uncaught (in promise): Error: Template parse errors:
Cannot instantiate cyclic dependency! NgControl

What are the steps to reproduce?

Here is a StackBlitz

Which versions of Angular, Material, OS, TypeScript, browsers are affected?

@angular/material: 2.0.0-beta.12
@angular/core: 4.4.4
@angular/cli: 1.4.4
typescript: 2.4.2
Windows 10 64bit
Chrome/Firefox/Edge

Is there anything else we should know?

I do not think so.

P3 docs has pr help wanted

Most helpful comment

Please provide a tutorial of a MatFormFieldControl that implements ControlValueAccessor....

All 23 comments

I find what was wrong: I had to remove the NG_VALUE_ACCESSOR provider on my custom-input

{
    provide: NG_VALUE_ACCESSOR,
    useExisting: forwardRef(() => CustomInputComponent),
    multi: true,
},

Maybe the guide should warn about that.

I have a similar problem. However, removing the NG_VALUE_ACCESSOR provider yields the following error:

No value accessor for form control with path '...'

I'm trying to implement MatFormFieldControl.errorState, so I want to derive that from a Validator that is on the FormControl.
@GuillaumeNury how did you register your ControlValueAccessor with Angular?

@ADegele Yes ! I struggled a lot with it !

Here is what I have done:

// Just add this in your constructor
if (this.ngControl) {
    this.ngControl.valueAccessor = this;
}

@GuillaumeNury Thank you very much!

Please provide a tutorial of a MatFormFieldControl that implements ControlValueAccessor....

Anyone who has created a custom MatFormFieldControl with a ControlValueAccessor want to take a stab at improving the docs?

I did implement a file input that works with MatFormField and ControlValueAccessor.

For what it's worth: https://plnkr.co/edit/VGCSprNVT1pobOxjWwmT?p=preview
If i find some time to update the docs, I can give a try.

A cyclic dependency error is also present when providing NG_VALIDATORS

@Component({
  ...
  providers: [
    {
      provide: MatFormFieldControl,
      useExisting: forwardRef(() => MyComponent)
    },
    {
      provide: NG_VALIDATORS,
      useExisting: forwardRef(() => MyComponent),
      multi: true
    }
  ]
})
export class MyComponent implements MatFormFieldControl<string>, ControlValueAccessor, Validator {
  constructor(@Optional() @Self() public ngControl: NgControl) {...}
  ...
}

Uncaught Error: Template parse errors: Cannot instantiate cyclic dependency! NgControl

https://stackoverflow.com/questions/48682339/matformfieldcontrol-that-implements-controlvalueaccessor-and-validator-creates-c

I solved the problem by removing Validator interface from the component and instead provided the validator function directly.

export function phoneNumberValidator(control: AbstractControl) {
  ...
}

@Component({
  selector: 'fe-phone-number-input',
  templateUrl: './phone-number-input.component.html',
  styleUrls: ['./phone-number-input.component.scss'],
  providers: [
    {
      provide: MatFormFieldControl,
      useExisting: forwardRef(() => PhoneNumberInputComponent)
    },
    {
      provide: NG_VALIDATORS,
      useValue: phoneNumberValidator,
      multi: true
    }
  ]
})
export class MyComponent implements MatFormFieldControl<string>, ControlValueAccessor { ... }

It has any idea to use NG_VALIDATORS in MatFormFieldControl?
I have use @malymato method to implement but some trouble to get component input variable e.g. min or max.

advise for the above comment would be helpful

It's working this way :
you can create method for validation inside your component (or other, if you don't know how look here https://blog.thoughtram.io/angular/2016/07/27/custom-form-controls-in-angular-2.html#adding-custom-validation):

public validate(c: FormControl): ValidationErrors {
    return {
        testErr: this.test,
    };
}

Then you add it to your NgControl inside AfterViewInit or other method :

if (this._control && this._control.control) {
    this._control.control.setValidators(this.validate);
}

Just for info your constructor looks like this :

constructor(
    @Self() @Optional() public _control: NgControl,
) {
    if (this._control) {
        this._control.valueAccessor = this;
    }
}

Tell me if stackblitz or plunkr is needed.

Hi everybody,
with the answer of @malymato I got my Control working :-)
Now I try to implement the Validation descripted at the site mentioned by @Maxouhell. But it will not work. I set the validators like described above.
Now I always get the error: this.validateFn is not a function.

On debug I can see, that in the Validation function itself, there is missing the property vaildateFn like descibed in the given link above.
To cut a long story short, could you @Maxouhell plz. make an example on stackblitz?The important thig is, to give an additional value to the Validation function, which is declared as Attribute of the Control.

Best regards

Update:
I fixed my issue. Thx @malymato and @Maxouhell for the important hints and solutions

https://stackblitz.com/edit/angular-fptggt?file=src%2Fapp%2Fstock-counter.component.ts

I just took stock-counter component from ultimate angular tutorial and update it for self validation.

If your counter go > 40 it will be invalid.
Check the code and tell me if you have any questions.

I'm surprised this is still an issue, why have custom field components that cannot be validated without having dig through tickets to actually get them working, why is this not mentioned in the docs.

Could the docs mention how to actually set this up?

Also anyone have any luck getting the fields state setting the UI red?

@crisbeto could you take a look at this one?

Got a full working example in this StackBlitz. This is a custom number input with styled buttons, error state and implements ControlValueAccessor. The SB includes example usage in both Template Driven and Model Driven forms, feel free to link to this in existing or future documentation.

To fix the problem people have been reporting here I had to add a provider for the MatFormFieldControl to provide my component and set ngControl.valueAccessor = this in the constructor, as well as a few other changes to support the implementation of ControlValueAccessor and two way binding

ref #13624

Hey guys,

I had been experiencing a very strange issue while building directive for making input uppercase.
The input was extending DefaultValueAccessor. It was containing an error when having invalid value, but mat-input was not getting red.

The fix appeared to be simple: I was forgetting calling:
this.onTouched() in OnBlur() method.

Hope this helps!

@StickNitro thanks for the StackBlitz, it answered some problems I was having, but I still have one that I can't figure out how to do. When this custom component is in a host form view, and the host view has a Save button, I need the custom component to display the red error validation if it is not in a valid state, such as being empty when it's required. I forked your StackBlitz project to add the Save button to demonstrate what I mean. My StackBlitz fork. Could you update my fork to show how I can accomplish this?

@Maxouhell

I've used your solution for a while, but it's problematic when users of the component want to add other validators that should coexist with the internal validators:

  • In OnInit, we'll overwrite existing validators.
  • Runtime, we'll get our validators overwritten.

I've fixed the first issue by Validators.compose([this.ngControl.control.validator, ...]), but I'm yet to find a solution to the second issue.

I'm having the same problem as @kahanu . I need to show the red error validation (mat-form-field-invalid class) on my custom input field when the value is invalid. I would appreciate any examples showing how to do that.

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

_This action has been performed automatically by a bot._

Was this page helpful?
0 / 5 - 0 ratings

Related issues

crutchcorn picture crutchcorn  路  3Comments

theunreal picture theunreal  路  3Comments

julianobrasil picture julianobrasil  路  3Comments

Hiblton picture Hiblton  路  3Comments

michaelb-01 picture michaelb-01  路  3Comments