Angular: FormControl.setValidators...why is there no getValidators?

Created on 14 Dec 2016  ·  72Comments  ·  Source: angular/angular

I'm submitting a ... (check one with "x")

[ ] bug report => search github for a similar issue or PR before submitting
[x] feature request
[ ] support request => Please do not submit support request here, instead see https://github.com/angular/angular/blob/master/CONTRIBUTING.md#question

Current behavior
We have an AbstractControl with basic validators like (required, maxLength) in a ReactiveForm via TypeScript FormBuilder. To those basic validators we added a custom validator which dynamically change the validation strategy based on a dropdown value. Currently we are using the setValidators() method inside another component (AbstractControl is included via @Input()). The main problem is that it overwrites existing validators.
Example:

App Component

this.formBuilder.group({
      zip: ['', [Validators.required, Validators.maxLength(10)]] 
    });

App Template

<zip ...
        [control]="form.controls.zip">
</zip>

ZIP Component

@Input() control: AbstractControl;

this.control.setValidators([
      // redundant to formBuilder
      Validators.required,
      Validators.maxLength(10),
      // custom validation based on a dropdown value (via. valueChange Detection)
      validateZipFn(countryCode)]
    );

Expected behavior
To stay flexible we don't want to overwrite all validators. The below code would illustrate the behavior with a getValidators() method.

App Component

this.formBuilder.group({
      zip: ['', [Validators.required, Validators.maxLength(10)]]
    });

App Template

<zip ...
        [control]="form.controls.zip">
</zip>

ZIP Component

@Input() control: AbstractControl;

let listOfAllValidationRules = this.control.getValidators().push(validateZipFn(countryCode)]);
this.control.setValidators(listOfAllValidationRules);

What is the motivation / use case for changing the behavior?
Being more flexible with dynamic validation.

  • Angular version: ~2.1.2

  • Browser: all

  • Language: TypeScript / ES6

forms feature medium

Most helpful comment

I agree, having a read-only list of validators that could be obtained by a method on the current control would be very helpful.

All 72 comments

@Icepick there's nothing to return. validators are stored as a single object, not an array.

@DzmitryShylovich Does that mean this cannot be implemented? Personally, I only need a readonly copy of the validators with the metadata attached.

I am building a directive where an object of validation messages is passed in. This is simply an object with the validation rule name is the key and the value is the message to be displayed. I would like to compare this object and the set validators of the NgControl, so that the user is warned if a certain message is missing.

I would like to see this also for similar reasons to @Bidthedog. For example, if a Validator.requried is on a control then we'd like to programatically indicate this on the form. Read-only is all we require. Currently the best I can do is define an array on the component and pass that to the form control definition. Its going to get ugly pretty quickly!

Is this on the road map and if so is there any ETA on this functionality?

Exactly. I basically have three big nasty duplicate definitions in my form service now. The below code snippets are a tiny excerpt from a rather large, 6 page form I'm putting together. This is for page one (details) and the start of page 2 (medical):

To create / define:

  private createForm() {
    this.form = this.fb.group({
      details: this.fb.group({
        NHSNumber: [null, [Validators.required, Validators.pattern(/^\d{3}-\d{3}-\d{4}$/)]],
        titleId: [null, [Validators.required]],
        firstName: [null, [Validators.required, Validators.maxLength(10)]],
        middleNames: null,
        lastName: [null, [Validators.required]],
        DOB: [null, [Validators.required]],
        genderId: [null, [Validators.required]],
        regionId: [null, [Validators.required]],
        maritalStatusId: [null, [Validators.required]],
        occupationTypeId: [null],
        occupationId: null,
        telephoneNo: null,
        mobileNo: null,
        houseNumber: null,
        streetName: null,
        town: null,
        city: null,
        county: null,
        postcode: null,
        GPId: null,
        nextOfKinId: null,
        livesWith: null
        //vehicleLicenseTypeId?: number[]
      }),
      medical: this.fb.group({
        gpId: [null, [Validators.required]]
      })
    });
  }

To populate:

  private populateForm() {
    let m = this.model;
    this.form.setValue({
      details: {
        NHSNumber: this.getTextValue(m.NHSNumber),
        titleId: this.getLookupValue(m.titleId),
        firstName: this.getTextValue(m.firstName),
        middleNames: this.getTextValue(m.middleNames),
        lastName: this.getTextValue(m.lastName),
        DOB: this.getDateValue(m.DOB),
        genderId: this.getLookupValue(m.genderId),
        regionId: this.getLookupValue(m.regionId),
        maritalStatusId: this.getLookupValue(m.maritalStatusId),
        occupationTypeId: this.getLookupValue(m.occupationTypeId),
        occupationId: this.getLookupValue(m.occupationId),
        telephoneNo: this.getTextValue(m.telephoneNo),
        mobileNo: this.getTextValue(m.mobileNo),
        houseNumber: this.getTextValue(m.houseNumber),
        streetName: this.getTextValue(m.streetName),
        town: this.getTextValue(m.town),
        city: this.getTextValue(m.city),
        county: this.getTextValue(m.county),
        postcode: this.getTextValue(m.postcode),
        GPId: this.getLookupValue(m.GPId),
        nextOfKinId: this.getLookupValue(m.nextOfKinId),
        livesWith: this.getTextValue(m.livesWith)
      },
      medical: {
        gpId: this.getLookupValue(m.GPId) || 1
      }
    });

Validation rules. Not complete, but you can see how this is going to get messy, fast

  private formValidationMessages = {
    details: {
      NHSNumber: {
        required: '\'NHS Number\' is required',
        pattern: '\'NHS Number\' must be in the format \'NNN-NNN-NNNN\' where N is a digit'
      },
      titleId: {
        required: '\'Title\' is required'
      },
      firstName: {
        required: '\'First Name\' is required'
      }
    },
    medical: {
    }
  };

I started working on defining all the form rules in one object set, which I'm thinking of iterating through / projecting into the functions listed above (though I've not got that far yet):

  private formValidationMessages = {
    details: {
      NHSNumber: {
        required: '\'NHS Number\' is required',
        pattern: '\'NHS Number\' must be in the format \'NNN-NNN-NNNN\' where N is a digit'
      },
      titleId: {
        required: '\'Title\' is required'
      },
      firstName: {
        required: '\'First Name\' is required'
      }
    },
    medical: {
    }
  };

On top of this, of course, I have the data model definition, and then all the layers on the back-end; there has to be something we can do to reduce code bloat?

I also just got hit by not being able to retrieve the configured Validators for a FormControl.

I worked around it by adding a utility service which wraps the config object before it is passed into the FormBuilder, and keeps track of a mapping between control names and their given Validators.

It would naturally be great to remove the need for this extra service 😄

Well, since I posted here I've consolidated all my form configuration into one big object, and wrote a few helper methods that do the mapping between form and model based on the config. It all seems way too complicated, but it could just be the learning curve I've had.

Hi,
just wondering if there is any word on this feature. It seems to me that being able to visually mark certain items as required is a basic feature expected of any modern forms UI. Yet since there is no way to retrieve the information whether a field is required or not, it is impossible to implement this feature using reactive forms.

It's not impossible, it's just a LOT of work.

@Bidthegod do you mean it would be a LOT of work to implement this feature in angular, or it would be a LOT of work to retrieve the validators in an angular component.

If it is possible to retrieve the validators in a component, any input about how to do is welcome!

Or if it is easier for certain component types (input, select), it can be useful too.

@Toub What I ended up doing was building a "config" object structure that contained every individual form field, and all its settings (display, validators and rules etc). I then wrote a few helper methods to query the config, which builds up the FormGroup. Each form has a form service, and inherits from a base component.

I wouldn't say it's an easy fix, and it's tons of code, but it ended up being fairly elegant and easy to create a fairly complex form. Threw some of the code in a gist for you to look at, but I've not got time to modularise properly it at the moment. There are quite a few files missing from this - including all the custom form input components I wrote.

https://gist.github.com/Bidthedog/1dc7d10cda1759061c09f44f7b48cbf3

@Bidthedog it doesn't feet my needs but thanks for your answer, as the gist can be useful for others.

Hey guys, I created a simple helper to return the validators (just for the required, but can be extended for others):

getValidators(_f) {
  return Object.keys(_f).reduce((a, b) => {
    const v = _f[b][1];
    if (v && (v === Validators.required || v.indexOf(Validators.required) > -1)) {
      if (!a[b]) { a[b] = {}; }
      a[b]['required'] = true;
    }
    return a;
  }, {});
}

const _f = {
  id: [],
  name: [null, Validators.required],
  phone: [],
  email: [null, [Validators.required, Validators.email]]
};
this.frmMain = this._fb.group(_f);
console.log(this.getValidators(_f));    // {name: {"required": true}, email: {"required": true}}

Fits my needs 👍

I agree, having a read-only list of validators that could be obtained by a method on the current control would be very helpful.

+1 for this feature

+1 for read-only list

+1

I need that functionality to dynamically add validators without losing those that already exist. Something like:
Object.keys(this.myForm.controls).forEach(key => { if (map.get(key)) { this.myForm.get(key).setValidators(this.myForm.get(key).getValidators().push(Validators.required)) } });
Fully explained in: https://stackoverflow.com/questions/46852063/how-to-dynamically-add-validators-to-the-forms-in-angular-2-4-using-formbuilde.
This must be something common

It's actually not too hard to dynamically add validators with composeValidators (https://github.com/angular/angular/blob/master/packages/forms/src/directives/shared.ts#L139). I think removing validators is the bigger issue. You can do it with a boolean but then your validator is no longer a pure function.

this function should work for FormGroups and FormControls to determine required Validators

      export const hasRequiredField = (abstractControl: AbstractControl): boolean => {
        if (abstractControl.validator) {
            const validator = abstractControl.validator({}as AbstractControl);
            if (validator && validator.required) {
                return true;
            }
        }
        if (abstractControl['controls']) {
            for (const controlName in abstractControl['controls']) {
                if (abstractControl['controls'][controlName]) {
                    if (hasRequiredField(abstractControl['controls'][controlName])) {
                        return true;
                    }
                }
            }
        }
        return false;
    };

@mtinner That was a really great find. That is getting me the initial set. I think it would still be worth adding to the API to allow for a return of an observable of the validators. That way finding a conditional required control would be very simple. I'm not sure the proposed workaround here can be applied to catch those instances.

In a very nice presentation of Angular Connect 2017 @kara ends by saying "custom forms controls are awesome & not scary". Good, but this important feature for custom controls is missing

+1

No time for a PR but here's what I came up with :

import {FormControl} from '@angular/forms';
import {AsyncValidatorFn, ValidatorFn} from '@angular/forms/src/directives/validators';
import {Observable} from 'rxjs/Observable';
import {Subject} from 'rxjs/Subject';
import {AbstractControlOptions} from '@angular/forms/src/model';

export class DescendingFormControl extends FormControl {

    private asyncValidationChangesSubject = new Subject<AsyncValidatorFn | AsyncValidatorFn[]>();
    private validationChangesSubject = new Subject<ValidatorFn | ValidatorFn[] | null>();

    public readonly validationChanges: Observable<ValidatorFn | ValidatorFn[] | null>;
    public readonly asyncValidationChanges: Observable<AsyncValidatorFn | AsyncValidatorFn[]>;

    constructor(formState?: any, validatorOrOpts?: ValidatorFn | ValidatorFn[] | AbstractControlOptions | null, asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[] | null) {
        super(formState, validatorOrOpts, asyncValidator);
        this.validationChanges = this.validationChangesSubject.asObservable();
        this.asyncValidationChanges = this.asyncValidationChangesSubject.asObservable();
    }

    public setValidators(newValidator: ValidatorFn | ValidatorFn[] | null): void {
        super.setValidators(newValidator);
        this.validationChangesSubject.next(newValidator);
    }

    public setAsyncValidators(newValidator: AsyncValidatorFn | AsyncValidatorFn[]): void {
        super.setAsyncValidators(newValidator);
        this.asyncValidationChangesSubject.next(newValidator);
    }
}

If anyone's up to do the whole PR job before I get some free time, go for it!

+1

+1

+1

The problem : It's trapped

An array of present validators exists but is not available to us...
Have a look at the Validators static method compose, which is the method used behind the scenes when you define an array of Validators on your form control :

 static compose(validators: (ValidatorFn|null|undefined)[]|null): ValidatorFn|null {
    if (!validators) return null;

    // Here it is
    const presentValidators: ValidatorFn[] = validators.filter(isPresent) as any; 

    if (presentValidators.length == 0) return null;

    return function(control: AbstractControl) {
      return _mergeErrors(_executeValidators(control, presentValidators));
    };
  }

https://github.com/angular/angular/blob/master/packages/forms/src/validators.ts#L342

As you can see, presentValidators is what we're looking for, but it's _trapped_ in a closure, so inaccessible, and only value you get from compose is an object with all the triggered errors for a given value.

So the only way to get infer the validators is to manually trigger the errors on your form control, which will reference the validators names.

The hack : Trigger an error on a different control

You don't want to trigger an error on your binded form control. You should instead create a new form control, free from any binding to your template, and apply the same Validator functions to him, then trigger the errors you want by setting a value :

// Your form control, actually binded to a form in your template

emailControl = new FormControl('', [Validators.required, Validators.email])

// Elsewhere in your code
// You use dummy form controls, on which you apply your original form control validators and various values, to trigger the errors.

const errorsWhenEmpty = new FormControl('', this.emailControl.validator, this.emailControl.asyncValidator).errors
const errorsWhenOneChar = new FormControl('x', this.emailControl.validator, this.emailControl.asyncValidator).errors

// Or you can simply reuse the first dummy control with `setValue()`

It is far from ideal, but it's the least dirty way I have found, until we can get our hands on the validators themselves.

I will suggest just to extend from the FormControl class and save in the fields of the children class a copy of the Validator functions passed into the constructor, something like this:

export class ExtFormControl extends FormControl {
  private _syncValidators: ValidatorFn | ValidatorFn[];

  private _asyncValidators: AsyncValidatorFn | AsyncValidatorFn[];

  constructor(formState?: any, validatorOrOpts?: ValidatorFn | ValidatorFn[] | null,
              asyncValidators?: AsyncValidatorFn | AsyncValidatorFn[] | null) {
    super(formState, validatorOrOpts, asyncValidators);
    this._syncValidators = validatorOrOpts;
    this._asyncValidators = asyncValidators;
  }


  getSyncValidators(): ValidatorFn[] {
    if (typeof this._syncValidators === 'function') {
      return [this._syncValidators];
    } else {
      return this._syncValidators;
    }
  }

  getAsyncValidators(): AsyncValidatorFn[] {
    if (typeof this._asyncValidators === 'function') {
      return [this._asyncValidators];
    } else {
      return this._asyncValidators;
    }
  }

  setValidators(newValidator: ValidatorFn | ValidatorFn[] | null): void {
    super.setValidators(newValidator);
    this._syncValidators = newValidator;
  }

  setAsyncValidators(newValidator: AsyncValidatorFn | AsyncValidatorFn[] | null): void {
    super.setAsyncValidators(newValidator);
    this._asyncValidators = newValidator;
  }

}

I am all for a read-only list of validators. There are cases where, using ReactiveForms, one would expect to configure constraints like maxLength, via FormBuilder code rather than template. But it is currently necessary to place maxlength in HTML, in order to be able to define some behaviors (like moving focus to next field, a typical case in credit card inputs), or automatically marking labels as required, etc.

I have a need to programmatically set validators that update as the form gets filled in. It would be really nice to be able to read the validators on a FormControl. I agree with @moniuch that using ReactiveForms one would expect to be able to do this. Just voicing my hope it gets implemented.

any updates, folks?

Hmm I'm seeing this compound an interesting issue in 19851
We can't see set validators or prioritize them. :(

I solved it for me with check the "validator" property of the control. It gives back the object with ACTIVE validators. If validator's are set and if the condition for the validator to be true is fulfilled.
In my example it is even a REACTIVE FORM used in a NESTED COMPONENT.

Parent Component

The validators in ReactiveFormGroup in PARENT component

return this.formBuilder.group({
      username: ['', [Validators.required, Validators.min(3), Validators.email]],
     ...
});

The names are only for testing purposes

<app-input name="txtfield" 
[parentForm]="loginForm" parentFormControlName="txtfield" 
></app-input>

Nested Component

 <div [formGroup]="parentForm" class="form-control-group">
   ...
        <input #inputField  name="{{parentFormControlName}}" 
            formControlName="{{parentFormControlName}}" 
            matInput 
            placeholder="{{placeholderTxt}}"
        >
  ...
    </div>

In nested component I check only once after AfterViewIinit, because I only need "required" validator.

ngAfterViewInit() {
    // Lookup if this field is required or not
    if (this.parentForm.controls[this.parentFormControlName].validator != null) {
    // MAYBE CHECK FOR required field in returned OBJECT!!
      console.log('Validators of ' + this.parentFormControlName + ' = ', 
           JSON.stringify(
             this.parentForm.controls[this.parentFormControlName]
                      .validator(this.parentForm.controls['username']);)
            );

    } else {
      console.log('No validator set for component ' + this.parentFormControlName); 
    }

Output in browser console while debugger stopped:

[DEBUG] STATUS of username =  {"required":true}
>this.parentForm.controls[this.parentFormControlName].validator(this.parentForm.controls[this.parentFormControlName])
{required: true}
required: true
__proto__: Object

Otherwise - means if no validator is set - it shows "null"!

Other situations

If input text field has a default value

If the input-text-field has a default value, this would NOT show the required value to be true, but I only wanna highlight the field (= line bold) if the field is required and not filled.

Other value than required

You can always check any other validator status, if you watch changes with maybe "DoCheck" or "OnChanges" to get maybe "email" or "min" validators.... if you need this.

@Angular team , it's really necessary to have the API to check current validator list and remove specific validator at run time. This solve many issues.

Why "control.errors" and "controls.validators" doing preaty mutch the same thing?
".errors" should give all errors than an error state is reached.
".validators" should give back all registered validators, for this control.
As simple as that?

I'd very much like to see this feature as well. But! One of the big issues I'm seeing is that validators are (usually) pure functions - meaning they can be nested, even deeply.

In our projects we have lots of domain-related validators which, then, are constructed out of other validators (and so on). Our IBAN validator, for instance, uses a exactLength, a pattern and a IBAN check digit validator under the hood. Following this pattern leads to to more specific validation errors, and allows us to use error messages which describe the issue in greater detail (aka better for Usability).

So, when considering a new method for retrieving all validators, the questions is: Will only be the "top validators" be returned? Plus, what's the name of those validators? Overall, it seems to me that, with the current implementation of validation, there is no easy solution ...

TL;DR: it's impossible without changing tons of stuff in Angular forms module and breaking your existing code.

So folks, the relevant source code is this:
packages/forms/src/model.ts
packages/forms/src/validators.ts
and maybe packages/forms/src/directives/validators.ts

I found the following problems:

  • first of all the validator is just a stupid function object with no way of identifying it. Even if you could get an array of functions, what would you do then? How would you be able to delete a regex validator if a regex validator is just a function? We need some way to identify that this one is a regex validator, and this one is a required validator if we want to add them or remove them individually. So, you can implement addValidator function, but you can't implement removeValidator function.
  • secondly, when you setValidators, the compose function is eventually called. It lumps all validator function together into a single function and actually calls them by calling internal _executeValidators function. So in the end we don't have the original array of validators, we only have a huge mega-validator with the list of all errors merged.

From that I concluded that it's impossible to implement getValidators or removeValidator without introducing some breaking changes in the notion of validators. It's seems possible to implement addValidators, but who needs it if I can only add more validators and can't remove the existing ones?

So what should you do if you want to implement conditional, dynamic validation in your app, for example, to make this email field required if this checkbox is set without losing all existing email validators? I found two approaches:

  • You can store the default validators for your fields somewhere in a variable. When it's time to make email required, you will use setValidator function and pass to it the Validator.required + all default validators.
  • Instead of using form control required validator, you can create a form group validator which will monitor both the checkbox and the changes in your field. When the checkbox is set to true, it will behave as if email field is required.

@ganqqwerty … Correct. That's the reason why addValidator(...) or removeValidator(...) doesn't exist. There is no identification. The validator itself doesn't know which control is running on.

@ganqqwerty I'm not saying this is easy to implement, but I don't think your reasoning is valid here.

How would you be able to delete a regex validator

By passing a reference to the validator. The same way you do remove event listeners.

So in the end we don't have the original array of validators

This is just an internal design decision and is currently not exposed anywhere in the public API. Choosing to keep the original array instead of composing the functions over and over again would in fact be a possible performance benefit, as you wouldn't have an additional function which would anyway have to keep your array of validators in a closure.

to make this email field required if this checkbox is set without losing all existing email validators: [...] Instead of using form control required validator, you can create a form group validator which will monitor both the checkbox and the changes in your field.

Isn't this the intended behavior? Your form control with the email field is valid on its own if it has a valid email address. Your _group_ is valid if the email is present in case the value of the checkbox is true. I don't see this as a workaround for validators, but their proper usage on the correct level of the form.

Thanks for clarifying this to me! Does it mean that such feature can be scheduled for implementation?

By passing a reference to the validator.

So if we keep the reference to a validator, it will be possible to remove it. If we just provide an anonymous function as a validator, we will not be able to remove it. Hmmm, indeed it looks like removeEventListener

Without this feature it impossible to remove a specific validator from a specific control.

This is what I need right now and I cant solve this. Its crasy how sometimes super basic stuff is just not possible to do with angular.

this.myAbstractFormControl.removeValidatorAndKeepTheRestAsIs(Validators.required)

@andreElrico What's your use-case for removing a validator? The more I think about this, the less I see the point in ever changing validators on a form. If you need to change validators dynamically based on a different field in the form, then your placement of the validator is wrong: what you want is a form group validator, not form control validator.

Im trying to use the same formGroup that is provided via e.g. input dynamically in two senarios. One where I CAN have required on each field and the second case where I remove the required IF present and apply a atLeastOneField validator to the formGroup.

Here is my stackblitz Im working on.

I dont understand why some protect the design flaw that the angular team has produced regarding existing validation handling.
Angular Form-Validation should be CRUD.

@andreElrico As I said, if your form has two inputs that have validations depndent on each other, then your validator should be in the form group, not in the form contol.

For example, if you have two password fields, you shouldn't change the validator of the second control each time the first control is change. Instead, the validator should go on a group which contains two password fields. Each password field has validation that password must be at least 8 characters long, but the group has the validator that they need to be the same. On its own, each password field isn't aware of existance of the other one. The group holds them together.

If you have something like

( ) a
( ) b
( ) other [ _____ ]

where if user chooses "other", the input field is required next to it, then, again, the validator "required" shouldn't be toggled on the input field. Instead, the whole form group needs validation: the radio button control needs to be one of "a", "b" or" other", and if "other", then input field must be present.

You shouldn't have a listener on radio button and then toggle input field's "required" validator based on the value of the radio buttons.

I don't see why you believe validation should have a CRUD API. I'm not defending anything, I'm speaking reasonable and with arguments. I'm not saying methods for getting, removing and toggling validators should't be implemented, I'm saying that almost everyone here has a use-case for CRUD validations which are actually anti-patterns. A control is a standalone unit and it doesn't change its validation rules during its lifetime. When controls are dependent, then they are put in a group and the group is validated.

thank you @lazarljubenovic for your comments and time.
I feel you havent looked closely at my stackblitz. Im applying the "wrapping" logic in the formGroup for one scenario. I just have two distinct scenarios for my form that need the validators to change dynamically. And at that "stage" where I deal with the formGroup my validators are already "composed", so no CRUD for me :D.

I can work around this problem and hope this gets fixed someday.
167 👍 seem to reflect a high desire for this feature.

If you have time, I would love to find out more:

"A control is a standalone unit and it doesn't change its validation rules during its lifetime."

do you have any resources on that?

"When controls are dependent, then they are put in a group and the group is validated."

This acually is has its limits as well.

Also not related to this ISSUE:
If the group takes over the validation of the controls the errors will be set to the group and therefore not be reflected visually on the controls. So we have to set and remove the errors manually to the child controls. (Thats a big pain in the *).

I wish all a good weekend.

I certainly understand @lazarljubenovic's perspective.

However, I work on highly dynamic, multi-part, enterprise forms that require complex composition... lots of hiding/showing, validation requirement dependency chains, etc.

The way we handle this complexity is to subscribe to valueChanges on any form control, group, or array that is responsible for triggering dependencies, then using setValidators or clearValidators for each scenario. Since there's no way to target specific validators, we store each set of validation rules that might apply at any given time and reapply them. It sure would be nice to be able to retrieve all validators, add individual validators, or target specific validators for removal, which seems like it could all still be architected with immutability in mind.

"A control is a standalone unit and it doesn't change its validation rules during its lifetime."

do you have any resources on that?

Nope; it's really just my interpretation of the design of forms.

If the group takes over the validation of the controls the errors will be set to the group and therefore not be reflected visually on the controls.

To be honest, it's dubious even in terms of UI where those errors are supposed to go. When two fields don't play together, but are far apart in the form, where _should_ you write the error? It seems to me like you should take a step back and fix the UX of the form firstly.

Of course, forms are a wild beast and they cannot fit every use-case. When I have a VERY complex form with _really_ crazy controls, I don't use @angular/forms at all. I see @angular/forms as a great utility which covers 95% of use-cases, and allows me to quickly build a form without much effort.

Just like I like using Array#map, Array#filter and Array#reduce, sometimes a good old for loop with an index variable is the best solution for a more complex iteration.

I'd just like to make it clear again that I'm not _against_ adding these methods; I just feel like the _majority_ of people would misuse them. Maybe it would be a footgun. Then again, I don't think that a useful shouldn't be added to a framework just because someone can misuse it. You can also brush your teeth with toilet water and we don't ban toilets because of that.

When I have a VERY complex form with really crazy controls, I don't use @angular/forms at all. I see @angular/forms as a great utility which covers 95% of use-cases, and allows me to quickly build a form without much effort.

Is this the official market positioning of the Angular Forms? I thought of it more as of all-purpose tool that is useful for huge projects.

When I have a VERY complex form with really crazy controls, I don't use @angular/forms at all. I see @angular/forms as a great utility which covers 95% of use-cases, and allows me to quickly build a form without much effort.

Is this the official market positioning of the Angular Forms? I thought of it more as of all-purpose tool that is useful for huge projects.

"Reactive Forms" are meant to be for the complicated, highly customised use-cases... such as being able to completely control your validation ;)

You can completely control it. You just need to properly determine WHAT you're validating: a group or a control.

has this made any traction? I would really love to dynamically change my validators with only a formGroup as an input to keep everything decoupled!!! Reactive forms are awesome but this is my only hold up.

Sooooooooo how am I supposed to unit test my code that dynamically adds validators?

It appears the obvious unit test won't work since I can't _read_ any of the controls applied validators.

_this won't work_
expect(component.formControl.validators.length).toEqual(2);

Update:
Actually, you can do it this way. Reading validators would obviously be cheaper, however:

component.myCtrl.setValue('');
expect(component.myCtrl.errors.required).toBeTrue();

I'm a big fan of Reactive Forms and love what I can do with them, but small things like not being able to ask the FormControl about its validators seems like an oversight. Given that there are so many state properties on a FormControl, what prevents at a minimum making a .required property available? Imagine if this was the case in everyday life...

Me: Hi, I'm here for my appointment.
Receptionist: Sorry we can't see you.
Me: Why not? I have an appointment now.
Receptionist: Shrugs I don't know.

I'm generalizing a bit, but if I'm handing the FormControl property values I should also be able to access those values as well.

what prevents at a minimum making a .required property available

The fact that you can create your own required validator. Angular cannot know what your function does.

100% get that, I'm not saying it be based on a function. Knowing that a field is required is just as basic as knowing its enabled/disabled which is configurable, accessible, and reflected in the UI.
Is there anything that can be done so that developers can get similar functionality for a field being required so they can do things like conditionally show required markers just by working with the FormControl?

Hi, this is our latest hack, to automatically mark inputs required: https://stackblitz.com/edit/material-input-auto-required-asterisk. Same concept can apply to mat-select, or any other library.

const _clearValidators = AbstractControl.prototype.clearValidators;
AbstractControl.prototype.clearValidators = function(){
  (this as any).isRequired = false;
  _clearValidators.call(this);
}

const _setValidators = AbstractControl.prototype.setValidators;
AbstractControl.prototype.setValidators = function(newValidator: ValidatorFn | ValidatorFn[] | null): void {
  (this as any).isRequired = false;
  _setValidators.call(this, newValidator);
}

export function isRequired(control: AbstractControl): ValidationErrors | null{
  (control as any).isRequired = true;
  return Validators.required(control);
}

@Directive({ selector: '[matInput]:not([required])' })
export class MatInputRequiredDirective implements DoCheck {
  constructor(private readonly input: MatInput) { }

  ngDoCheck() {
    const isRequired = (this.input.ngControl && this.input.ngControl.control as any).isRequired || false;
    if(isRequired !== this.input.required){
      this.input.required = isRequired;
      this.input.ngOnChanges();
    }
  }
}

100% get that, I'm not saying it be based on a function. Knowing that a field is required is just as basic as knowing its enabled/disabled which is configurable, accessible, and reflected in the UI.

I'm not so sure. “Requiredness” is very specific to the applications' business logic. Does an empty string pass a "required" condition? What about a zero? An empty array for a multiple select? False for a checkbox? Null, undefined?

Is there anything that can be done so that developers can get similar functionality for a field being required so they can do things like conditionally show required markers just by working with the FormControl?

You can already conditionally show/hide errors based on any condition you like. The presence of the error doesn't necessarily mean you have to display it anywhere. I don't see how querying for validators instead of simply querying for errors (which is available) would help you here.

I'm not so sure. “Requiredness” is very specific to the applications' business logic. Does an empty string pass a "required" condition? What about a zero? An empty array for a multiple select? False for a checkbox? Null, undefined?

I could argue that a field being enabled/disabled is also very specific to an application' business logic too but the FormControl provides functionality for a developer to manage that via the constructor and accessors. Being required is a fuzzy line and I get where you're coming from. This may be personal preference, but it's as much a presentational state (like enabled/disabled, valid/invalid) as it is an indication of the value's validity.

You can already conditionally show/hide errors based on any condition you like. The presence of the error doesn't necessarily mean you have to display it anywhere. I don't see how querying for validators instead of simply querying for errors (which is available) would help you here.

Yes I can conditionally hide/show errors, but once those errors are resolved any presentational pieces that hinge off of the expected state are no longer available. I can't show an asterisk based on the validators because once those validators are satisfied they are no longer available.
I guess for now I'll just have to work around it.

a field being enabled/disabled is also very specific to an application' business logic too

I can't see how.

I can't show an asterisk based on the validators

So instead add a validator based on your own "required" semantics. Not everything can be a one-liner.

I'm looking for any reasonable way to support the scenario, not necessarily a one-liner. I'm also looking to understand your perspective in case mine is misaligned.

What if my semantics were just vanilla and only needed to leverage the Validators.required validation? In the StackBlitz example below, if I add it to a text field and enter a value, the only way to know about it is via the field.errors object and look for the property associated with the Validators. However, when the Validator is satisfied, that property no longer exists. Maybe my approach is incorrect, is there a better way to leverage the FormControl to achieve this that I'm not taking advantage of?

StackBlitz example

You're not supposed to toggle validators based on validity of a different field. Create a group validator and check the value of the object containing both the name and secondary name. See this comment for more info.

As for the star... Well, your approach is not "incorrect" in a sense "that's a terrible idea", but it's an idea which won't work with the way validators are currently envisioned. So I'd rather say your approach is _incompatibile_ with how forms are envisioned to be used right now.

You'll need to create your own mini form creation library if you want to quickly achieve the effect you describe. Here's a quick sketch of how I'd approach the problem if I was faced with this problem. For example, you can have a function createForm which accepts a spec object like this:

spec = {
  name: {
    initialValue: '',
    isRequired: (formValue) => true, // always required
  },
  secondName: {
    initialValue: '',
    isRequired: (formValue) => isFilledIn(formValue.name), // required only if name is filled in
  }

isFilledIn is your function which determined whether a value is filled in or not. Probably something like val => val != null && value != ''.

This createForm would return the same form as if you've created it like this:

this.fb.group({
  name: [''],
  secondName: [''],
}, {
  validators: [
    (group) => {
      const value = group.value
      const errors = {}
      if (spec.name.isRequired(value) && !isFilled(value.name)) {
        errors['name-required'] = true
      }
      if (spec.secondName.isRequired(value) && !isFilled(value.secondName)) {
        errors['secondName-required'] = true
      }
      return errors // or null if no errors found
    }
  ]
})

The errors will now be on the group itself. The error for secondName indeed belongs there -- you cannot validate secondName on its own, since its validity depends on a different field (so you validate the group of those fields instead). The error for name should belong to name, but this sketch of implementation would put it on the form group. This might or might not work for you. You could introduce a field on the spec object which would further tweak the configuration like this.

Now in the template you use your form as usually, since you have the @angular/forms object at hand. You'll need to print some errors from the form group and some from the controls (or maybe all from the group if that works for you). The condition for toggling the star would be spec[key].isRequired(form.value).

The possibilities if how precisely you're going to implement this are endless, and it all depends on such tiny decisions that I don't think any forms API would satisfy everyone. So depending on what you expect of a form in your own project, in my opinion it's best to introduce a “semantic” wrapper around the form builder, which will do additional stuff based on your requirements.

Thanks for the approach and for spending the time referencing the previous comment, the thread history is quite long :)

I think that's where part of my issue has been; properly managing field dependencies between controls that are grouped and ones that live outside of the group. The spec approach has been my fallback in most areas so I'm glad it seems to be how you'd approach it as well.

that I don't think any forms API would satisfy everyone

True, not sure any provided API meets that sort of coverage!

Any update? almost 4 years since the creation of this issue.
We are currently creating custom utility functions to store validations, functions that derive too much from the 'vanilla' angular just to getValidators of a formControl.

would really need this feature

This would be very helpful for those of use who wrap reactive forms

Very valuable thread. However, I am a little confused as to what purpose it solves by not giving access to validators on a form control. I bumped into this requirement where we have our custom component that needs to show an "*" if it is required and maybe a relevant error message right below it (if the user wants that). The other alternate is adding to the API of the control itself by making the user set a property from the template as well. But that should not be needed as in a reactive form the user already provided his required validator (or for that matter any other validator). My custom component should be able to know that such a validator is set and it could then show/hide elements based on that. Seems a very common requirement.

A solution with a custom FormControl with custom configuration to have all informations about validators and potentially full control on those validators :

export class CustomFormControl extends FormControl {

  public listValidator: CustomValidator[] = [];

  private constructor(formState?: any,
                      validatorOrOpts?: ValidatorFn | ValidatorFn[] | CustomCtrlOpts | null,
                      asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[] | null) {

    super(formState, validatorOrOpts as ValidatorFn[], asyncValidator);

    if (validatorOrOpts instanceof CustomCtrlOpts) {
      this.listValidator = validatorOrOpts.customConf;
    }
  }

  // Builder ith default value
  public static init(formState?: any): CustomFormControl {
    return new CustomFormControl(isNil(formState) ? null : formState);
  }

  public required(): CustomFormControl {
    return this.and('required');
  }

  public email(): CustomFormControl {
    return this.and('email');
  }

  public custom(): CustomFormControl {
    return this.and('custom');
  }

  public min(min: number): CustomFormControl {
    return this.and('min', min);
  }

  public max(max: number): CustomFormControl {
    return this.and('max', max);
  }

  private and(key: TypeCustomValidator, value?: any): CustomFormControl {
    this.listValidator.push({key: key, value: value});
    this.setValidators(new CustomCtrlOpts(this.listValidator).validators as ValidatorFn[]);
    return this;
  }
}

// Add others if needed
export type TypeCustomValidator = 'min' | 'max' | 'required' | 'email' | 'custom' ;

export interface HandledValidator {
  key: TypeCustomValidator;
  function: Function;
  params?: boolean;
}

export const listHandledValidators: HandledValidator[] = [
  {key: 'required', function: Validators.required},
  {key: 'email', function: Validators.email},
  {key: 'min', function: Validators.min, params: true},
  {key: 'max', function: Validators.max, params: true},
  {key: 'custom', function: custom}
];

export interface CustomValidator {
  key: TypeCustomValidator;
  value?: any;
}

export class CustomCtrlOpts implements AbstractControlOptions {

  validators?: ValidatorFn | ValidatorFn[] | null;
  asyncValidators?: AsyncValidatorFn | AsyncValidatorFn[] | null;
  updateOn?: 'change' | 'blur' | 'submit';

  private _customConf?: CustomValidator[];

  constructor(value: CustomValidator[]) {
    this.customConf = value;
  }

  get customConf(): CustomValidator[] {
    return this._customConf;
  }

  set customConf(value: CustomValidator[]) {

    this._customConf = value;

    if (!value) {
      return;
    }

    this.validators = [];

    value.forEach(customValidator => {
      const validator = listHandledValidators.find(it => it.key === customValidator.key);
      if (validator.params) {
        (this.validators as ValidatorFn[]).push(validator.function(customValidator.value) as ValidatorFn);
      } else {
        (this.validators as ValidatorFn[]).push(validator.function as ValidatorFn);
      }
    });
  }
}

with formGroup initialisation like

this.form = this.fb.group({
    amount: CustomFormControl.init(10).required().min(0), 
    ...

This would be a really nice feature to have. Using workarounds for this feels like a waste of time.

would really need this feature

Yeah this is really needed. Simply, just think if there is a way to set something, then there must be also a similar way to get it no? If you can set something easily, but for getting them back you have to spend lot of effort, then there is clearly missing something that _really needed_ .

Any progress on this issue/feature?

@BenKatanHighLander It's on the way: https://github.com/angular/angular/pull/37263#issuecomment-723333060

Was this page helpful?
0 / 5 - 0 ratings