Search code examples
angulartypescriptangular-materialangular5angular-components

MatFormFieldControl that implements ControlValueAccessor and Validator creates cyclic dependency


I'm trying to create custom form control by implementing MatFormFieldControl, ControlValueAccessor and Validator interfaces.

However, when I provide NG_VALUE_ACCESSOR or NG_VALIDATORS..

@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_VALUE_ACCESSOR,
      useExisting: forwardRef(() => PhoneNumberInputComponent),
      multi: true,
    },
    {
      provide: NG_VALIDATORS,
      useExisting: forwardRef(() => PhoneNumberInputComponent),
      multi: true
    }
  ]
})
export class PhoneNumberInputComponent implements MatFormFieldControl<string>,
  ControlValueAccessor, Validator, OnDestroy {
  ...
}

cyclic dependencies are created:

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

This works:

@Component({
  selector: 'fe-phone-number-input',
  templateUrl: './phone-number-input.component.html',
  styleUrls: ['./phone-number-input.component.scss'],
  providers: [
    {
      provide: MatFormFieldControl,
      useExisting: forwardRef(() => PhoneNumberInputComponent)
    }
  ]
})
export class PhoneNumberInputComponent implements MatFormFieldControl<string>,
  ControlValueAccessor, Validator, OnDestroy {
  ...
  constructor(@Optional() @Self() public ngControl: NgControl) {
    if (this.ngControl) {
      this.ngControl.valueAccessor = this;
    }
  }
}

But I still cannot figure out how to make validation work. Providing NG_VALIDATORS creates cyclical dependency. Without providing it, validate method is simply not called.

I'm using @angular/material 5.0.4.


Solution

  • To get rid of cyclical dependency, I removed the 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 PhoneNumberInputComponent implements MatFormFieldControl<string>,
      ControlValueAccessor, OnDestroy {
      ...
      constructor(@Optional() @Self() public ngControl: NgControl) {
        if (this.ngControl) {
          this.ngControl.valueAccessor = this;
        }
      }
    }