首页 文章

如何在Angular中解决相同的问题,在AngularJS中解决了ng-messages?

提问于
浏览
17

在AngularJS中有一个名为ng-messages的表单指令,它帮助我们实现它,以便不会同时显示所有表单错误 . 因此,例如,如果输入有3个错误:required,minlength,maxlength . 然后只需要显示,在需要后有效,然后显示minlength . 如果没有ng-messages,我们需要做一些非常复杂和丑陋的逻辑,以便只显示必需而不显示其余部分,同时还要考虑只有在窗体控件也脏/触摸且无效时才会显示错误 .

在AngularJS中,这将是:

<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>

我们怎样才能以优雅的方式在Angular中实现这一目标?

3 回答

  • 19

    有关可用于此目的的库,请参阅我的其他答案 . 这个答案的其余部分用于制作您自己的组件 .

    下面我提供一个例子(没有编译或运行它,但它应该给你足够的信息来开始) . 只有在触摸,弄脏等时才显示消息的逻辑可以很容易地添加到此 .

    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);
      }
    }
    
  • 1

    在我的另一个答案旁边,您还可以使用我创建的名为 angular-reactive-validation 的库,可以找到here .

    您指出在任何给定时间每个控件最多显示一条验证消息对您很重要 . 该库支持该行为 . 它还减少了您需要编写以显示验证消息的HTML数量,因为验证消息的声明将移动到您声明 FormControlsComponent . 另一个方便的功能是通过将函数传递给 Validator 来获得动态验证值 .

    下面我提供了一个示例表单及其背后的模型,以便您了解基本用法 .

    <form [formGroup]="form">
      <div formGroupName="name">
        <label>First name:
          <input formControlName="firstName">
        </label>
        <label>Middle name:
          <input formControlName="middleName">
        </label>
        <label>Last name:
          <input formControlName="lastName">
        </label>
        
    <arv-validation-messages [for]="['firstName', 'middleName', 'lastName']"> </arv-validation-messages> </div> <label>Age: <input type="number" formControlName="age"> </label> <arv-validation-messages for="age"></arv-validation-messages>
    <input type="submit" /> </form>
    import { Validators } from 'angular-reactive-validation';
    ...
    
    form = this.fb.group({
      name: this.fb.group({
        firstName: ['', [Validators.required('A first name is required'),
          Validators.minLength(1, minLength => `The minimum length is ${minLength}`),
          Validators.maxLength(50, maxLength => `Maximum length is ${maxLength}`)]],
        middleName: ['', [Validators.maxLength(50, maxLength => `Maximum length is ${maxLength}`)]],
        lastName: ['', [Validators.required('A last name is required'),
          Validators.maxLength(50, maxLength => `Maximum length is ${maxLength}`)]]
      }),
      age: [null, [
        Validators.required('An age is required'),
        Validators.min(0, 'You can\'t be less than zero years old.'),
        Validators.max(150, max => `Can't be more than ${max}`)
      ]]
    });
    
  • 0

    基于@David Walschots提供的代码,我注意到它有2个问题(在我的例子中) .

    • 它没有't validate on submit, but I didn' t想要禁用提交按钮

    • 它没有验证模糊,只会出现错误颜色,但没有消息 .

    因此,经过一段时间的尝试,我找到了一个解决方案,也适用于上述情况 .

    为此,我需要为blur事件添加一个额外的监听器,并确保form would emit on submit (without creating boiler plate code) .


    The extra listener

    注入的 FormControl 不直接绑定到输入字段,并且没有连接到它的模糊事件 . 所以我们需要找到连接到它的html输入元素 . 为此,我们可以使用Angular提供的 Renderer2 ,但首先我们需要找到控件的名称,以便我们可以创建一个CSS选择器:

    /**
       * Tries to find the name of the given control
       * Since `Angular 4.0.4` the `FormControl` can have access to it's parent
       * @param {FormControl} control - The control of which the name should be determined
       * @returns {string | null} The name of the control or null if no control was found
       */
      private static getControlName(control: FormControl): string | null {
        const formGroup = control.parent.controls;
        return Object.keys(formGroup).find(name => control === formGroup[name]) || null;
      }
    

    在此之后,我们可以创建一个CSS选择器并在页面上找到该元素:

    if (this.control.updateOn === ValMessagesComponent.UPDATE_ON_BLUR) {
          const controlName = ValMessagesComponent.getControlName(this.control);
          const input = this.renderer.selectRootElement('input[formControlName=' + controlName + ']');
        }
    

    我们现在有 FormControl 绑定的HTML元素,所以我们可以添加模糊事件并用它做一些事情:

    this.inputSubscription = this.renderer.listen(input, 'blur', () => {
              // Blur happened. Let's validate!
          })
    

    当我们将此代码合并到@David Walschots的答案中时,我们得到以下代码:

    @Component({
      selector: 'val-messages',
      template: '<ng-content></ng-content>'
    })
    export class ValMessagesComponent implements OnInit, OnDestroy {
    
      /**
       * The form control on which the messages should be shown
       * @type {FormControl}
       */
      @Input()
      private control: FormControl;
    
      /**
       * Whether or not the form should be validated on submit
       * @type {boolean}
       * @default
       */
      @Input()
      private onSubmit: boolean = true;
    
      /**
       * All the children directives that are defined within this component of type `sh-message`
       * These children hold the `when` and the `message` that should be shown
       * @type {ValMessageComponent}
       */
      @ContentChildren(ValMessageComponent)
      private messageComponents: QueryList<ValMessageComponent>;
    
      /**
       * All subscriptions that are used to monitor the status of the FormControl
       * @see control
       * @type {Subscription[]}
       */
      private controlSubscriptions: Subscription[] = [];
    
      /**
       * A listener for a change on the input field to which the formControl is connected
       * @type {() => void}
       */
      private inputSubscription: () => void;
    
      /**
       * The key that indicates that the model is updated on blur
       * @type {string}
       * @default
       */
      private static readonly UPDATE_ON_BLUR = 'blur';
    
      constructor(private renderer: Renderer2) {
      }
    
      public ngOnInit(): void {
        this.controlSubscriptions.push(this.control.valueChanges.subscribe(() => {
          this.hideAllMessages();
          this.matchAndShowMessage(this.control.errors);
        }));
    
        this.controlSubscriptions.push(this.control.statusChanges.subscribe(() => {
          this.hideAllMessages();
          this.matchAndShowMessage(this.control.errors);
        }));
    
        if (this.control.updateOn === ValMessagesComponent.UPDATE_ON_BLUR) {
          const controlName = ValMessagesComponent.getControlName(this.control);
          const input = this.renderer.selectRootElement('input[formControlName=' + controlName + ']');
          this.inputSubscription = this.renderer.listen(input, 'blur', () => {
            this.hideAllMessages();
            this.matchAndShowMessage(this.control.errors);
          })
        }
      }
    
      public ngOnDestroy(): void {
        if (this.inputSubscription) {
          this.inputSubscription();
        }
    
        for (const subscription of this.controlSubscriptions) {
          subscription.unsubscribe();
        }
      }
    
      /**
       * Checks if the model is invalid and if it is, finds and shows the corresponding error message
       * @param {ValidationErrors} errors - Any errors that are thrown on the model
       */
      private matchAndShowMessage(errors: ValidationErrors): void {
        if (errors) {
          const messageComponent = this.messageComponents.find(messageComponent => {
            return messageComponent.shouldShowError(Object.keys(errors));
          });
    
          if (messageComponent) {
            messageComponent.showMessage();
          }
        }
      }
    
      /**
       * Hides all the messages on the model
       */
      private hideAllMessages(): void {
        this.messageComponents.forEach(messageComponent => messageComponent.hideMessage());
      }
    
      /**
       * Tries to find the name of the given control
       * Since `Angular 4.0.4` the `FormControl` can have access to it's parent
       * @param {FormControl} control - The control of which the name should be determined
       * @returns {string | null} The name of the control or null if no control was found
       */
      private static getControlName(control: FormControl): string | null {
        const formGroup = control.parent.controls;
        return Object.keys(formGroup).find(name => control === formGroup[name]) || null;
      }
    }
    

相关问题