-->

Angular Validators: Can I add styling to form elem

2019-09-02 10:42发布

问题:

I'm using Angular with Forms version 4.1.0 and Material version 2.0.0-beta.8. I have fields that need to be marked as required based on a configuration stored in a database table, so the specific fields that are required can change depending on that configuration and are not known at development time. I am dynamically setting Validators.required on my form controls based on that configuration, and that is all working fine (i.e. the appropriate fields are turning red if you leave them blank after visiting them).

However, I also have a requirement to mark all required fields with an asterisk after their labels. I have the CSS to do this based on the HTML "required" attribute, but unfortunately, Angular does not apply this attribute (or any class that I can see) to a field simply based on it having Validator.required applied to it. The validator puts an ng-invalid class on it while it's invalid, but nothing that persists when it's either valid or untouched.

Is there any way to have the validator apply an attribute or class automatically? Maybe by extending Validators.required or otherwise creating a custom validator? Out of desperation, I've resorted to creating an isRequired(fieldName) method in my controller and marking every single field with [required]="isRequired('fieldName') just in case it becomes required in a future configuration, and it works, but it's really unwieldy and prone to being forgotten as development of new screens continues.

I'm fairly new to Angular, so I've never created a custom validator before. In fact I suspect that this is common enough that it's some simple tweak to include the ::after CSS in Angular's styling. Any guidance would be greatly appreciated!

Edited to provide an example of what I have:

<div>
  <form [formGroup]="formGroup">
    <md-input-container>
      <input mdInput type="text" placeholder="name" formControlName="name">
    <md-input-container>
    <!-- more inputs here -->
  </form>
</div>

In my controller:

generateValidators(formGroup: FormGroup) {
  // Go get the database configuration for which fields should have
  // validators, then apply appropriate validators
  Object.keys(this.formGroup.controls).forEach(fieldName) => {
    let validators = [];
    if(database configuration indicates this field is required) {
      validators.push(Validators.required);
    }
    if(database configuration indicates this field has a max length) {
      validators.push(Validators.maxLength(length));
    }
    // etc.
    formGroup.controls[fieldName].setValidators(validators);
  }
}

All of this successfully applies Validators.required to the fields marked as required in the database, and they behave as expected when the user fails to fill them in. But that doesn't add the HTML "required" attribute, which is how I'm adding the asterisk to the label with CSS. I need to somehow mark the fields that get a Validators.required attached to them so that I can use the CSS to apply the asterisk, either by adding the "required" attribute or by adding a known class that I can use in my CSS. Something like this:

.required:after { content:" *"; }

As a workaround, I've created an isRequired(string) method in my controller to go look up the database configuration by field name, and updated my HTML like this:

<input mdInput type="text" placeholder="name" formControlName="name" 
       [required]="isRequired('name')">

It works, but it's not very clean and requires all the other developers to adhere to this; whereas the code that adds the validators is in a common location where all components already see and use it. I could also update the generateValidators() method to add a class or attribute at the same time it pushes Validators.required onto the validators list, if someone knows how to do that. I'm open to suggestions.