setValidators in custom control without from reference in Angular
Asked Answered
G

2

6

i have created a custom input control. I don't want to create any custom validation. I want to use default required, minLength, maxLength etc.I know i can do like this

this.form.controls["firstName"].setValidators([Validators.minLength(1), Validators.maxLength(30)]);

but i can't send form reference from parent component. How can i use setValidator inside textbox component.

// input-box.component.ts    
    import { Component, Input, forwardRef } from '@angular/core';
    import { NG_VALUE_ACCESSOR, ControlValueAccessor } from '@angular/forms';


    @Component({
      selector: 'app-input-box',
      template:`<label >{{inputdata.label}}</label><br>
      <input type="text" required #textBox [value]="textValue" (keyup)="onChange($event.target.value)" />`,
      styleUrls: ['./input-box.component.less'],
      providers: [
        {
          provide: NG_VALUE_ACCESSOR,
          useExisting: forwardRef(() => InputBoxComponent),
          multi: true
        },
        /*{
          provide: NG_VALIDATORS,
          useExisting: forwardRef(() => InputBoxComponent),
          multi: true,
        }*/]
    })
    export class InputBoxComponent implements ControlValueAccessor {
      @Input() inputdata: any;
      private textValue: any = '';

      onChange(value) {
        this.textValue = value;
        this.propagrateChange(value);
      }

      private propagrateChange = (_: any) => { };

      writeValue(value: any) {
        if (this.inputdata.value) {
          this.textValue = this.inputdata.value;
          this.propagrateChange(this.inputdata.value);
        }

      }

      registerOnChange(fn: (value: any) => any) {
        this.propagrateChange = fn;
      }


      registerOnTouched() { }
    }

use in different component

<app-input-box  name="textBoxParent_{{index}}"  [inputdata]="goaldata"  ngModel ></app-input-box>
Generative answered 30/3, 2018 at 16:6 Comment(0)
M
10

If you want to access the FormControl inside a class that wants to implement the ControlValueAccessor you have to change your usual implementation. You cannot register the class as ControlValueAccessor in the providers array AND inject the FormControl in the constructor. Because the FormControl will try to inject your class, and you will try to inject the control, which will create a circular dependency.

So the changes you need to make are as follows:

  1. Remove NG_VALUE_ACCESSOR and NG_VALIDATORS from the providers array.
  2. Inject NgControl in the constructor and set the control's ControlValueAccessor and Validators.

    constructor(@Self() ngControl: NgControl) { 
       ngControl.valueAccessor = this;
       ngControl.setValidators([Validators.minLength(1), Validators.maxLength(30)]
       ngControl.updateValueAndValidity()
    }
    

If you want to keep the validators specified by the consumer of your custom form component you need to modify the constructor to this:

constructor(@Self() ngControl: NgControl) { 
  const control = ngControl.control;

  let myValidators = [Validators.required, Validators.minLength(5)];
  let validators = control.validator
  ? [control.validator, ...myValidators]
  : myValidators;

  control.setValidators(validators)
  control.updateValueAndValidity();
}

Check out the whole process explained by Kara Erickson (angular core) at AngularConnect 2017. Link

Margarettemargarida answered 30/3, 2018 at 17:4 Comment(1)
@TomaszKula, I watched that video which solves OP's use case without custom validation, but is there a way to also use a custom validation method that's defined in the custom form control's class?Pejsach
S
1

You could also leave your code as it is and simply get the NgControl from the Injector as follows:

constructor(private injector: Injector) { 
}

public ngAfterViewInit(): void {
  const ngControl = this.injector.get(NgControl);
  const control = ngControl.control;
  // Do whatever you want with validators now:
  control.setValidators(validators)
  control.updateValueAndValidity();
}

You will need to use the lifecycle hook ngAfterViewInit otherwise the control will not yet be available.

Like this you skip the issues with circular dependencies and you can leave the NG_VALUE_ACCESSOR and NG_VALIDATORS in the providers array.


See a related question about injecting NgControl here.
Using the injector is suggested in the answer from @yurzui here

Spermatium answered 7/10, 2021 at 7:49 Comment(1)
I battled with issues of injecting ngControl but control not being populated for a long time and moving it to ngAfterViewInit was the solution for me. Injecting via the constructor and removing the providers meant that validate() was never called/used and that ng-valid/invalid was never set on the component, which I needed.Bobsledding

© 2022 - 2024 — McMap. All rights reserved.