2

I have a form with input and select tags.

When all tags are invalid they should show a red left border.

That works only for input tags, but no select tags.

The invalid input-tag has these css classes applied during runtime:

class="form-control ng-pristine ng-invalid ng-touched"

The invalid select-tag has these css classes applied during runtime:

class="form-control ng-valid ng-touched ng-dirty"

As you can see on the screenshot the both dropdowns/select elements do not have the red/green left border added via this css:

.ng-valid[required], .ng-valid.required  {
  border-left: 5px solid #42A948; /* green */
}

.ng-invalid:not(form)  {
  border-left: 5px solid #a94442; /* red */
}

enter image description here

I should mention that the validation for the select-tags is a custom validation and its errors are retrieved from the schooylearForm not from the schoolyearForm.bestGrade property. Maybe that is the problem, that I still have to manually set the control state to invalid, that the ng-invalid classes is added to the select-tag???

HTML

<div class="form-group row">
  <label class="col-xs-3 col-form-label" for="power">Beste Note für einen Test</label>
  <div class="col-xs-3">
    <select formControlName="bestGrade" class="form-control">
        <option *ngFor="let t of allGrades" [ngValue]="t">{{t}}</option>
    </select>
  </div>
  <div class="col-xs-6" *ngIf="schoolyearForm.controls.bestGrade.touched">
    <div class="form-error form-control" *ngIf="schoolyearForm.hasError('equalGrades')">
      Beste Note darf nicht gleich schlechteste Note sein.
    </div>
  </div>
</div>

Component

   this.schoolyearForm = this.formBuilder.group({
      name: [this.createSchoolyear.name, [Validators.minLength(3), Validators.required]],
      endDate: [this.createSchoolyear.endDate, Validators.required],
      startDate: [this.createSchoolyear.startDate, Validators.required],
      bestGrade: [this.createSchoolyear.bestGrade],
      worstGrade: [this.createSchoolyear.worstGrade,]
    },  {validator: matchingGrades('bestGrade', 'worstGrade')});

Validator

export function matchingGrades(bestGrade: string, worstGrade: string) {
  return (group: FormGroup): {[key: string]: any} => {
    let bestGradeObject = group.controls[bestGrade];
    let worstGradeObject = group.controls[worstGrade];

    if (bestGradeObject.value == worstGradeObject.value) {

      // bestGradeObject.markAsInvalid = true // DOES NOT EXIST !
      return {
        equalGrades: true
      };
    }
  }
}

UPDATE

I ound out when I change this css:

.ng-invalid:not(form)  {
  border-left: 5px solid #a94442; /* red */
}

to

.ng-invalid  {
  border-left: 5px solid #a94442; /* red */
}

then indeed the whole form gets that red border when both select-tags has the same value selected.

Then I change my question to:

How can I make the equalGrades custom validation depended on both properties bestGrade/worstGrade and not on the whole.

UPDATE 2

I made it work with that code:

  if (bestGradeObject.value == worstGradeObject.value)
        {
            bestGradeObject.setErrors({ "equalGrades": true });
            worstGradeObject.setErrors({ "equalGrades": true });
            return {
                equalGrades: true
            };
        }
        else 
        {
            bestGradeObject.setErrors(null);
            worstGradeObject.setErrors(null);
        }

UPDATE 3

enter image description here

Now I used the code from user 'developer033'

which uses Validation on the FormControl level not FormGroup level.

Initially the one dropdown has NO red border set due to the level change which I do not like.

How can this be fixed?

P.S. Its a chicken egg problem, I can not now pass the worstGradeCtrl to the constructor of the bestGradeCtrl...

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Pascal
  • 12,265
  • 25
  • 103
  • 195

1 Answers1

0

Well, you're setting the validator to the formGroup, so, the validator that you have is working the way that it's supposed to.

If you want to set errors for a specific formControl, you can do the following:

Component:

this.nameCtrl = new FormControl(this.createSchoolyear.name, [Validators.minLength(3), Validators.required]);
this.endDateCtrl = new FormControl(this.createSchoolyear.endDate, Validators.required);
this.startDateCtrl = new FormControl(this.createSchoolyear.startDate, Validators.required);
this.bestGradeCtrl = new FormControl(this.createSchoolyear.bestGrade);
this.worstGradeCtrl = new FormControl(this.createSchoolyear.worstGrade, matchingGrades(this.bestGradeCtrl));

this.schoolyearForm = this.formBuilder.group({
  name: this.nameCtrl,
  endDate: this.endDateCtrl,
  startDate: this.startDateCtrl,
  bestGrade: this.bestGradeCtrl,
  worstGrade: this.worstGradeCtrl
});

Validator:

export const matchingGrades = (equalControl: FormControl): ValidatorFn => {
  let subscribe: boolean = false;

  return (control: FormControl): { [key: string]: boolean } => {
    if (!subscribe) {
      subscribe = true;

      equalControl.valueChanges.subscribe(() => {
        control.updateValueAndValidity();
      });
    }

    return equalControl.value === control.value ? { 'equalGrades': true } : null;
  };
}
developer033
  • 24,267
  • 8
  • 82
  • 108
  • According to this: http://stackoverflow.com/a/35642259/401643 I could also make it work with the FormGroup approach, but it did not work for me and it did not work for the guys commenting to this answer. So I will try your approach! – Pascal Feb 16 '17 at 23:56
  • Well, you can, but I don't like to add a general validator and also manipulate the `errors` with `setErrors` method. – developer033 Feb 16 '17 at 23:58
  • I just made it work. See my Update 2. But I will still try your code and learn :-) – Pascal Feb 17 '17 at 00:07
  • Yep, as I said: it's possible, but I don't like that solution because it runs the validator everytime, not only when the `bestGrade` and `worstGrade` are changed. – developer033 Feb 17 '17 at 00:08
  • Do you have an answer for my UPDATE 3? without manipulating the errors/setErrors ;-) well setErrors is a public api method its meant to be used ;-) – Pascal Feb 17 '17 at 06:54