Bohms27
Bohms27

Reputation: 407

Angular 2 Pass parameter to a validator function

I have an array in my formcomponent class and want to be able to pass that array to a validator function. When adding multiple validators to the form, I am using Validators.compose function. This only accepts the name of the validator function though, and not any parameters to pass. Is it possible to add parameters to the function calls inside of the "compose" function?

export class ClientFormComponent
{
    clientForm: ControlGroup;
    npi: AbstractControl;
    name: AbstractControl;

    constructor(private _clientService: ClientService, _fb: FormBuilder) {
        this.clientForm = _fb.group({ 'name': ['', Validators.compose([Validators.required])], 'npi': ['', Validators.compose([Validators.required, npiNumValidator, Validators.maxLength(10), Validators.minLength(10)])]});
        this.name = this.clientForm.controls['name'];
        this.npi = this.clientForm.controls['npi'];
    }
    
    @Input() clientList;
    active = true;   
   
    onSubmit(value: Client) {
        this._clientService.addDeleteClient(value, true)
            .subscribe(
            client => this.clientList.push(client));        
    }    
}

function npiNumValidator(control: Control): { [s: string]: boolean } {
    if (isNaN(control.value)) {
        return { npiNAN: true };
    }
}

Thanks for any help!

Upvotes: 13

Views: 23346

Answers (6)

OliviaLynn
OliviaLynn

Reputation: 143

If you need to reference dynamic data from the class you can pass an object within the parameter instead of a primitive.

Example:

  valParams = {
    gte: 10
  }
  constructor(protected _fb:FormBuilder) { 
    this.numForm = _fb.group({
      num: ['', gte(this.valParams)]
    });
  }

Validation Function:

export function gte(valParams: {gte:number}): ValidatorFn {
  return (control: AbstractControl): ValidationErrors | null => {
    let val: number = +control.value;
 
    if (isNaN(val)) return { 'gte': true }   
    if (val <= +valParams.gte) return { 'gte': true }

    return null;
  }
}

Since you're passing an object JavaScript will reference the object in memory rather than an immutable primitive.

PS: As a bonus since you're passing the object reference, you can update the object directly from the validation function which can then be read by the component class.

Upvotes: 1

Zahema
Zahema

Reputation: 1415

The top answer meant I will have to create a class for each validator which I didn't like. Here is my approach:

public static npiNumValidator(
    x: any
  ): (AbstractControl) => ValidationErrors | null {
    return (control: AbstractControl): ValidationErrors | null => {
      return !!x ? null : { error: true};
    };
}

Use as:

this.form = this.formBuilder.group({
   email: [undefined, [ClassName.npiNumValidator('value')]],
});

Upvotes: 0

G&#252;nter Z&#246;chbauer
G&#252;nter Z&#246;chbauer

Reputation: 657496

Just move it to a class

class NpiNumValicator {
  constructor(private someField: someType) {}

  npiNumValidator(control: Control): { [s: string]: boolean } {
    if (isNaN(control.value)) {
        return { npiNAN: true };
    }
  }
}

then use it like

this.clientForm = _fb.group({ 'name': ['',
    Validators.compose([Validators.required])], 'npi': ['', 
    Validators.compose([Validators.required, 
        new NpiNumValidator(someArg).npiNumValidator, 
        Validators.maxLength(10), Validators.minLength(10)
    ])
]});

to be able to use this within npiNumValidator you can use

var npiNumVal = new NpiNumValidator(someArg);
this.clientForm = _fb.group({ 'name': ['',
    Validators.compose([Validators.required])], 'npi': ['', 
    Validators.compose([Validators.required, 
        npiNumVal.npiNumValidator.bind(npiNumVal), 
        Validators.maxLength(10), Validators.minLength(10)
    ])
]}); 

Upvotes: 12

Eric G
Eric G

Reputation: 935

In some cases the data that needs to be passed to the validator is dynamic and can be found in the class that is calling the validator. In those instances I just have simply used "bind(this)" when adding the validator. Here is a formBuilder example:

this.profileFormGroups.push( this.formBuilder.group({
    name: [profile.name,
            Validators.compose([
              Validators.required,
              Validators.maxLength(30),
              Validators.pattern('[a-zA-Z ]*')
              ]),
            this.myNameValidator.bind(this)
          ]
  }) );
}, this);

Then in your validator just reference the dynamic parameter:

//my Async Validator

myNameValidator(control: FormControl): any {
  let vm = this;

  return new Promise(resolve => {
   if ( vm.mydynamicvalue === control.name ) { ... }
      resolve({"Invalid User":true});
   else
      resolve(null)
  });

}

Upvotes: 4

Ankit Singh
Ankit Singh

Reputation: 24945

Is it possible to add parameters to the function calls inside of the "compose" function?

Validator Declaration: straight out of Angular Code

/* Validator that requires controls to have a value of a minimum length. */

static minLength(minLength: number): ValidatorFn {
    return (control: modelModule.AbstractControl): {[key: string]: any} => {
      if (isPresent(Validators.required(control))) return null;
      var v: string = control.value;
      return v.length < minLength ?
                 {"minlength": {"requiredLength": minLength, "actualLength": v.length}} :
                 null;
    };
  }

Usage:

Validators.compose([Validators.minLength(4)]

NOTE: to understand it better, see How do JavaScript closures work?

Upvotes: 15

Thierry Templier
Thierry Templier

Reputation: 202216

You also leverage a method of your component to create the validation function. This way you will be able to access properties of this component using an arrow function.

Here is a sample:

@Component({ ... })
export class MyComponent {
  constructor(private fb:FormBuilder) {
    this.form = this.fb.group({
      fieldName: [ '', this.createValidator() ]
    });
  }

  createValidator() {
    return (control) =>  {
      var arr = this.arr;
      (...)
    };
  }
}

Upvotes: 4

Related Questions