Angular cross field validation with template forms

<form>
<label>Start date:
<input type="date" name="startDate" [(ngModel)]="startDate" required>
</label>
<br>
<label>End date:
<input #endDateInput="ngModel" type="date" name="endDate" [(ngModel)]="endDate" required>
</label>
</form>
import { Directive, Input } from "@angular/core";
import { Validator, AbstractControl, NG_VALIDATORS } from "@angular/forms";
@Directive({
selector: "[afterDate]",
providers: [
{ provide: NG_VALIDATORS, useExisting: AfterDateDirective, multi: true }
]
})
export class AfterDateDirective implements Validator {
@Input()
afterDate: Date;
validate(c: AbstractControl): { [key: string]: any } {
if (c.value && this.afterDate && c.value < this.afterDate) {
return {
afterDate: true
};
}
return null;
}
}
<form>
<label>Start date:
<input type="date" name="startDate" [(ngModel)]="startDate" required>
</label>
<br>
<label>End date:
<input #endDateInput="ngModel" type="date" name="endDate" [(ngModel)]="endDate" required [afterDate]="startDate">
</label>
<span class="error" *ngIf="endDateInput.errors?.afterDate">Must be after start date</span>
</form>
import { Directive, Input, OnChanges, SimpleChanges } from "@angular/core";
import { Validator, AbstractControl, NG_VALIDATORS } from "@angular/forms";
@Directive({
selector: "[afterDate]",
providers: [
{ provide: NG_VALIDATORS, useExisting: AfterDateDirective, multi: true }
]
})
export class AfterDateDirective implements Validator, OnChanges {
@Input()
afterDate: Date;
validate(c: AbstractControl): { [key: string]: any } {
if (c.value && this.afterDate && c.value < this.afterDate) {
return {
afterDate: true
};
}
return null;
}
onChange: () => void; registerOnValidatorChange(fn: () => void): void {
this.onChange = fn;
}
ngOnChanges(changes: SimpleChanges): void {
if ('afterDate' in changes && this.onChange) {
this.onChange();
}
}
}
import { SimpleChanges, OnChanges, Directive, Inject } from '@angular/core';@Directive({ selector: 'validatorBase' })
export class ValidatorBaseDirective implements OnChanges {
private inputs: string[];
onChange: () => void; constructor(@Inject([]) ...inputs: string[]) {
this.inputs = inputs;
}
registerOnValidatorChange(fn: () => void): void {
this.onChange = fn;
}
ngOnChanges(changes: SimpleChanges): void {
if (this.inputs.some((input) => input in changes) && this.onChange) {
this.onChange();
}
}
}
import { Directive, Input, SimpleChanges } from "@angular/core";
import { Validator, AbstractControl, NG_VALIDATORS } from "@angular/forms";
import { ValidatorBaseDirective } from './validator-base.directive';@Directive({
selector: "[afterDate]",
providers: [
{ provide: NG_VALIDATORS, useExisting: AfterDateDirective, multi: true }
]
})
export class AfterDateDirective extends ValidatorBaseDirective implements Validator {
@Input()
afterDate: Date;
constructor() {
super('afterDate');
}
validate(c: AbstractControl): { [key: string]: any } {
if (c.value && this.afterDate && c.value < this.afterDate) {
return {
afterDate: true
};
}
return null;
}
}

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store