Change the colour of a label when an input is invalid

Viewed 1557

I am making a template driven form in angular and I want to be able to change the colour of the label of an input e.g. a select, when the select is invalid.

Here is an example of of a select input from the form:

<div class="select__wrapper">
    <label for="car-year">
        <span>Year of manufacture</span>
    </label>
    <select
            [(ngModel)]="vehicle.year"
            name="year"
            id="car-year"
            required
            class="form-control">
        <option *ngFor="let year of years" [ngValue]="year.value">
            {{ year.display }}
        </option>
    </select>
</div>

When a select is invalid, the class ng-invalid is added to the element.

It is easy enough to change the select styling with:

.select__wrapper {
    .ng-invalid {
        border-color: red;  
        color: red;
    } 
}

I also want to change the label colour when the select is invalid. I found the following on stackoverflow:

How to select parent pseudo-class from within child using scss

This uses sibling combinators but then I have to reorder my elements and hack them around with positions. I was wondering if there is a better was to do it with SCSS and/or angular?

Thanks

4 Answers

Update:

just saw your comment about looking to do this with css. if that's the case then you probably want to use the :has() selectors.

here is the documentation

with this you could use div.select__wrapper:has(' > .ng-invalid') > label as a selector to find all label children that are direct descendants of div with a select__wrapper class AND that container the ng-invalid class as direct descendents

---Original Answer---

I would suggest using reactive forms instead of the two way binding you are currently using. this will allow you to add any class you want based on the validity of a form or FormControl.

Here is the official guide to reactive forms from Angular

at a high level, doing suching such as the following in your class.

export class SampleComponent implements OnInit {
   form: FormGroup;

   constructor(private fb: FormBuilder) {}
   
   ngOnInit(): void {
       this.form = this.fb.group({
          formField: ['', Validators.required]
       });
   }    
}

then in your template you can do something along the lines of

<div [formGroup]="form" class="select__wrapper">
    <label for="car-year" [class.invalid]="form.invalid">
        <span>Year of manufacture</span>
    </label>
    <select
            formControlName="formField"
            id="car-year"
            required
            class="form-control">
        <option *ngFor="let year of years" [ngValue]="year.value">
            {{year.display}}
        </option>
    </select>
</div>

by using reactive forms, you can see that you can add classes to your labels based on if the form is valid [class.invalid]="form.invalid"

reactive forms are fairly useful, especially if your forms will grow beyond just a single form field

You can use same CSS as you have, just add ng-invalid class to your label when your form control is invalid. You can do that by using form variable, something like:

<div class="select__wrapper">
  <label for="car-year" [ngClass]="{ 'ng-invalid': year.invalid && (year.dirty || year.touched) }">
    <span>Year of manufacture</span>
  </label>
  <select [(ngModel)]="vehicle.year"
          name="year"
          id="car-year"
          required
          class="form-control"
          #year="ngModel"> <!-- this will create variable you can access inside form -->
    <option *ngFor="let year of years" [ngValue]="year.value">
      {{year.display}}
    </option>
  </select>
</div>

You can use a directive to add 'ng-invalid' class when the form control becomes invalid and add the form-control as an input to the directive.

Add directive to labels

<div class="select__wrapper">
<label for="car-year" checkInvalid  [control]="year">
<span>Year of manufacture</span>
</label>
<select [(ngModel)]="vehicle.year"
      name="year"
      id="car-year"
      required
      class="form-control"
      #year="ngModel"> <!-- this will create variable you can access inside form -->
<option *ngFor="let year of years" [ngValue]="year.value">
  {{year.display}}
</option>

Define directive

import { Directive, ElementRef, HostListener, Input } from   "@angular/core";

 @Directive({
   selector: "[checkInvalid]"
})
export class CheckInvalidDirective {
  @Input() control;

  constructor(private el: ElementRef) {
    this.control.valueChanges.subscribe(()=> {
      if(this.control.dirty && !this.control.valid)
         this.el.nativeElement.style.color = red;
         // Add class instead of this code if thats what u prefer
    })
  }

  
  
  

}

Try this:

/* The color of error message */
.fv-help-block {
    color: #dc3545;
}

/* The color of valid icon */
.has-danger .fv-plugins-icon {
    color: #dc3545;
}

/* The color of invalid icon */
.has-success .fv-plugins-icon {
    color: #28a745;
}

Override these colors for all forms or particular form as below:

.my-form.fv-plugins-bootstrap .fv-help-block {
    color: #f39c12;
}
.my-form.fv-plugins-bootstrap .has-danger .fv-plugins-icon {
    color: #f39c12;
}
.my-form.fv-plugins-bootstrap .has-success .fv-plugins-icon {
    color: #18bc9c;
}
Related