Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I solve the same issue in Angular, that ng-messages solved in AngularJS?

Tags:

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?

like image 534
Alex Avatar asked Feb 10 '17 10:02

Alex


1 Answers

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);
  }
}
like image 146
David Walschots Avatar answered Oct 23 '22 16:10

David Walschots