I'm in my first weeks of moving to Angular 7, I've been doing basic forms in my project using template based basic validations, but I need to validate the form now based on that the value of one field must be higher than the other
I've tried using the values themselves in the component controller, but while I'm able to confirm that the values are valid or not, I'm unable to show the user what the problem is, using this code
if (issueThresholdForm.value.lowScore > issueThresholdForm.value.highScore) {
// Show user error
// This is the messing part, I guess
}
Here's the template that I'm using
<div *ngIf="_issueCategory">
<form (submit)="submitIssueThreshold(issueThresholdForm)" #issueThresholdForm="ngForm">
<mat-form-field class="half-width" floatLabel="always">
<mat-label [translate]="'issueThreshold.modals.highScore'"></mat-label>
<input name="highScore" type="number" matInput placeholder="0" [(ngModel)]="_issueCategory.highScore"
required #highScore="ngModel">
</mat-form-field>
<mat-form-field class="half-width" floatLabel="always">
<mat-label [translate]="'issueThreshold.modals.lowScore'"></mat-label>
<input name="lowScore" type="number" matInput placeholder="0" [(ngModel)]="_issueCategory.lowScore"
required #lowScore="ngModel">
</mat-form-field>
<mat-form-field class="full-width" floatLabel="always">
<mat-label [translate]="'issueThreshold.modals.description'"></mat-label>
<textarea name="description" matInput [(ngModel)]="_issueCategory.thresholdDescription">
</textarea>
</mat-form-field>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal" [translate]="'modal-confirm.cancel'"></button>
<button type="submit" class="btn btn-primary primary" [disabled]="issueThresholdForm.invalid || issueThresholdForm.pristine" [translate]="'modal-confirm.submit'"></button>
</div>
</form>
</div>
EDIT:
Edited with the same solution the reactive way. So create form group and add a custom validator attached to the form group:
_issueCategory = { lowScore: 1, highScore: 2 };
issueThresholdForm: FormGroup;
constructor(private fb: FormBuilder) {
this.issueThresholdForm = this.fb.group({
highScore: [this._issueCategory.highScore, [Validators.required]],
lowScore: [this._issueCategory.lowScore, [Validators.required]]
}, { validators: validateScore })
}
Validator function:
export function validateScore(
control: AbstractControl
): ValidationErrors | null {
if (control && control.get("highScore") && control.get("lowScore")) {
const highscore = control.get("highScore").value;
const lowscore = control.get("lowScore").value;
return (lowscore > highscore) ? { scoreError: true } : null
}
return null;
}
Then you can remove ngModel (important!) since they should not be mixed with reactive forms. Also you can remove all validation like required
for the form, so in the end an input can look simply like:
<input type="number" matInput placeholder="0" formControlName="lowScore">
ORIGINAL:
I strongly, strongly suggest Reactive forms, they might feel confusing at first, but totally worth it. You have better control over the form, and as mentioned in comment by Nithin Kumar Biliya, unit testing is easier.
That being said....
Here is a solution using template driven form, since that is what you currently are using.
You could make a directive that you attach to the form tag, and inside that directive have a validator to compare the values of highscore and lowscore and attach an error to the form, or return null
(which is considered valid in forms). So the validator would look like this:
import { Directive } from "@angular/core";
import {
AbstractControl,
NG_VALIDATORS,
Validator,
ValidationErrors
} from "@angular/forms";
@Directive({
selector: "[scoreValidation]",
providers: [
{
provide: NG_VALIDATORS,
useExisting: ScoreValidatorDirective,
multi: true
}
]
})
export class ScoreValidatorDirective implements Validator {
constructor() {}
// here control is the formgroup
validate(control: AbstractControl): ValidationErrors | null {
if (control && control.get("highScore") && control.get("lowScore")) {
// the form controls and their value
const highscore = control.get("highScore").value;
const lowscore = control.get("lowScore").value;
// not valid, return an error
if (lowscore > highscore) {
return { scoreError: true };
}
// valid
return null;
}
// form controls do not exist yet, return null
return null;
}
}
Add the directive to the declarations array in your appmodule, and use it by just attaching this directive to the form tag:
<form .... scoreValidation>
and error can be shown using *ngIf="issueThresholdForm.hasError('scoreError')