components: Cannot create a custom MatFormFieldControl that implement ControlValueAccessor

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.

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Reactions: 10
  • Comments: 23 (2 by maintainers)

Commits related to this issue

Most upvoted comments

Please provide a tutorial of a MatFormFieldControl that implements ControlValueAccessor…

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

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?

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 { ... }

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

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

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.

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

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

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.

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

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.

advise for the above comment would be helpful

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.

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.