15

I created custom component representing password form control (code below is simplified).

PasswordComponent (html)

<form [formGroup]="passwordForm">
  ...
  <input formControlName="password" type="password">
</form>

PasswordComponent (ts)

...
@Component({
  selector: 'password',
  templateUrl: './password.component.html',
  styleUrls: ['./password.component.css'],
  providers: [{
    provide: NG_VALUE_ACCESSOR,
    useExisting: forwardRef(() => PasswordComponent),
    multi: true
  }]
})
export class PasswordComponent implements ControlValueAccessor {

  passwordForm: FormGroup;
  onChange = (password: string) => { };  
  onTouched = () => { };                  

  constructor() {
    this.passwordForm = new FormGroup({
      ...
      password: new FormControl('')
    });

    this.passwordForm.valueChanges.subscribe(data => this.onChange(this.value));
  }

  get value(): string {
    return this.passwordForm.get('password').value;
  }

  writeValue(password: string): void {
    this.passwordForm.get('password').setValue(password);
    this.onChange(this.value);
  }

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

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

  setDisabledState?(isDisabled: boolean): void { }
}

I use it in other components instead of standard input element:

<form [formGroup]="userForm">
  ...
  <password formControlName="password"></password>
</form>

Validators are coming from outer form (they're not defined inside PasswordComponent)

this.userForm = fb.group({
  ...
  password: ['', [Validators.minLength(10), Validators.maxLength(100)]] 
});

My question is: how can I get <password> element validity from inside PasswordComponent? I would like to stylize it based on validity. In other words how can I get validity of userForm's 'password' control from PasswordComponent that represents this control.

Wilhelm Olejnik
  • 2,382
  • 3
  • 14
  • 21

2 Answers2

21

As we can't get NgControl instance directly from DI system since we'll get a circular dependency error. The following diagram shows why it happens if we inject NgControl in our custom value accessor:

enter image description here

Now it should be clear that we have NgControl -> FormControlName -> ValueAccessor -> CustomValueAccessor -> NgControl circular dependency

To work around it you can leverageInjector to achieve that:

component.ts

import { NgControl } from '@angular/forms';
export class PasswordComponent implements ControlValueAccessor {
  ...
  ngControl: NgControl;

  constructor(private inj: Injector) {
    ...
  }

  ngOnInit() {
    this.ngControl = this.inj.get(NgControl)
  }

template.html

{{ ngControl.control.valid }}

Plunker Example

yurzui
  • 205,937
  • 32
  • 433
  • 399
  • 1
    This works like a charm, but it looks so ugly, or is this just me who thinks this? It looks like a very basic use case – dendimiiii Aug 08 '17 at 10:06
  • 1
    Could you explain a bit more in detail what's happening here ? Why will we have a circular dep error ? Why doesn't the injector have one ? – Ced Nov 02 '17 at 22:11
  • Thanks for the visual of how things are setup. Angular documentation would benefit from these kinds of things! – Jessy Feb 19 '19 at 18:06
  • when I `this.inj.get(NgControl)` I get a `FormControlName` which is useles because the `formControlname.parent.control` is a `formgroup` and "I cant know" which of the controls is the "custom control". – Andre Elrico Jun 13 '19 at 13:59
  • 1
    I was in the wrong lifecycle. In `ngAfterViewInit` I can read `name` then get the right control. `_parent` is private though. – Andre Elrico Jun 13 '19 at 14:59
  • Thank you. This helped me a lot. I have already lost 2 days to override custom value getter in form control. – Tomislav Brabec Sep 24 '20 at 14:50
3

One more way to solve this problem is to Remove the NG_VALUE_ACCESSOR from the provider and just inject NgControl. Using the NgControl instance the component will be registered as ValueAccessor.

constructor(
..., 
@Optional() @Self() public ngControl: NgControl,
...,
) {
// Setting the value accessor directly (instead of using
// the providers) to avoid running into a circular import.
if (this.ngControl != null) { this.ngControl.valueAccessor = this; }
}
Manoj
  • 497
  • 7
  • 13