In AngularJS there was a form directive named ng-messages which helped us to make it so that not all form errors showed at the same time. So that for example if an input has 3 errors: required, minlength, maxlength. Then only required shows up, after required is valid, then minlength shows up. Without ng-messages we would need to do some really complex and ugly logic in order to just show required and not the rest while also taking into consideration that errors should only be shown if the form control is also dirty/touched and not valid.
In AngularJS this would be something like:
<div ng-messages="form.username.$error" ng-if="form.username.$touched || form.username.$dirty">
<div ng-message="required">Please enter a username.</div>
<div ng-message="minlength">Username must be at least 3 characters.</div>
<div ng-message="maxlength">Username can't exceed 30 characters.</div>
</div>
How can we achieve this in Angular in an elegant way?
See my other answer for a library you could use for this purpose. The remainder of this answer goes into making your own components.
Below I supply an example (didn't compile or run it, but it should give you enough information to get going). The logic for only showing messages when touched, dirty, etc can be added to this easily.
Usage
<validation-messages [for]="control">
<validation-message name="required">This field is required</validation-message>
</validation-messages>
Implementation
import { Component, OnInit, ContentChildren, QueryList, Input, OnDestroy } from '@angular/core';
import { FormControl } from '@angular/forms';
import { Subscription } from 'rxjs';
@Component({
selector: 'validation-messages',
template: '<ng-content></ng-content>'
})
export class ValidationMessagesComponent implements OnInit, OnDestroy {
@Input() for: FormControl;
@ContentChildren(ValidationMessageComponent) messageComponents: QueryList<ValidationMessageComponent>;
private statusChangesSubscription: Subscription;
ngOnInit() {
this.statusChangesSubscription = this.for.statusChanges.subscribe(x => {
this.messageComponents.forEach(messageComponent => messageComponent.show = false);
if (this.for.invalid) {
let firstErrorMessageComponent = this.messageComponents.find(messageComponent => {
return messageComponent.showsErrorIncludedIn(Object.keys(this.for.errors));
});
firstErrorMessageComponent.show = true;
}
});
}
ngOnDestroy() {
this.statusChangesSubscription.unsubscribe();
}
}
@Component({
selector: 'validation-message',
template: '<div *ngIf="show"><ng-content></ng-content></div>'
})
export class ValidationMessageComponent {
@Input() name: string;
show: boolean = false;
showsErrorIncludedIn(errors: string[]): boolean {
return errors.some(error => error === this.name);
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With