Angular2 — проверка FormControl при размытии

Я собираюсь добавить базовую проверку электронной почты, чтобы убедиться, что пользователь ввел правильный адрес электронной почты. В настоящее время используется метод, описанный ниже, проверка обновляется по мере ввода пользователем, что выглядит странно, когда возникают ошибки после ввода одного символа.

validEmail(c: Control){
if(!c.value.match('[a-z0-9!#$%&\'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&\'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?')){
  return {
    validEmail: true
  };
}
return null;
}    

ctrlEmailAddress: Control = new Control('', Validators.compose([
Validators.required, this.validEmail]));

Мне было интересно, можно ли запустить проверку размытия поля, как в angularJS, с помощью:

ng-model-options="{ updateOn: 'blur' }"

Мне известно о параметре размытия в поле ввода в html, но это не приводит к ошибке моего элемента управления, если только нет способа перевести элемент управления в состояние ошибки.

Может ли кто-нибудь помочь указать мне правильное направление?

Спасибо.

Изменить: я ищу решение angular2, а не решение angularJS.


person Reece Thompson    schedule 23.11.2015    source источник


Ответы (6)


ИЗМЕНИТЬ 2

Как Alex и официальная документация говорит, что Angular версии 5.0.0 имеет новую опцию для вашей ngModel updateOn: 'blur'

this.email = new FormControl(null, {
   validators: Validators.required,
   updateOn: 'blur'
});

Также вы можете использовать другие варианты обновления: change (по умолчанию), blur, submit.


Исходный

Я использую директиву, в которой удаляю всю проверку в фокусе и возвращаю ее обратно после события размытия. Он основан на ответе Кристиана Дешама.

Я обновляю достоверность только при размытии, поэтому, если значение было недопустимым до фокуса, оно будет недействительным после. Но если вы начнете ввод, валидность будет обновлена.

По некоторым причинам порядок очистки имеет смысл, поэтому я сначала очищаю асинхронные валидаторы.

Любое предложенное предложение будет полезно =)

import { Directive } from '@angular/core';
import { NgControl } from '@angular/forms';

@Directive({
  selector: '[validate-onblur]',
  host: {
    '(focus)': 'onFocus($event)',
    '(blur)': 'onBlur($event)'
  }
})
export class ValidateOnBlurDirective {
    private validators: any;
    private asyncValidators: any;
    constructor(public formControl: NgControl) {
    }
    onFocus($event) {
      this.validators = this.formControl.control.validator;
      this.asyncValidators = this.formControl.control.asyncValidator;
      this.formControl.control.clearAsyncValidators();
      this.formControl.control.clearValidators();
    }

    onBlur($event) {
      this.formControl.control.setAsyncValidators(this.asyncValidators);
      this.formControl.control.setValidators(this.validators);
      this.formControl.control.updateValueAndValidity();
    }
}

Кроме того, следите за обновлениями в этой ветке Angular 2 github о проверке onBlur.


ИЗМЕНИТЬ 1

Есть еще проблема - если я просто нажму на поле и после клика прочь - будет вызвана валидация. Если у вас есть какое-либо уведомление об этом (или обращения к серверу) - оно будет появляться каждый раз, когда вы это делаете. Таким образом, вы можете добавить свойство wasChanged и использовать его следующим образом:

    @Directive({
        selector: '[validate-onblur]',
        host: {
            '(focus)': 'onFocus($event)',
            '(blur)': 'onBlur($event)',
            '(keyup)': 'onKeyup($event)',
            '(change)': 'onChange($event)',
            '(ngModelChange)': 'onNgModelChange($event)'
        }
    })
    export class ValidationOnBlurDirective {
        private validators: any;
        private asyncValidators: any;
        private wasChanged: any;
        constructor(public formControl: NgControl) {
        }
        onFocus($event) {
            this.wasChanged = false;
            this.validators = this.formControl.control.validator;
            this.asyncValidators = this.formControl.control.asyncValidator;
            this.formControl.control.clearAsyncValidators();
            this.formControl.control.clearValidators();
        }
        onKeyup($event) {
            this.wasChanged = true; // keyboard change
        }
        onChange($event) {
            this.wasChanged = true; // copypaste change
        }
        onNgModelChange($event) {
            this.wasChanged = true; // ng-value change
        }
        onBlur($event) {
            this.formControl.control.setAsyncValidators(this.asyncValidators);
            this.formControl.control.setValidators(this.validators);
            if (this.wasChanged)
                this.formControl.control.updateValueAndValidity();
        }
    }
person Alex Shestakov    schedule 01.02.2017
comment
Проблема с этим подходом заключается в том, что если элемент управления недействителен и грязен, когда он получает фокус, то после первого нажатия клавиши (я имею в виду текстовое поле ввода) он будет действительным без валидаторов, а класс ng-valid будет применяться. Если вы стилизуете этот класс, чтобы поле ввода отображалось, например, зеленым, это проблема. Я думаю, что он должен каким-то образом оставаться в состоянии ожидания, пока элемент управления сохраняет фокус, поэтому ng-valid не применяется. - person Daniel Francisco Sabugal; 10.02.2017
comment
@DanielFranciscoSabugal Да, это может быть проблемой, но в моем случае это было хорошее решение. Пользователь не должен видеть ошибок, пока он делает ввод, и он будет проверен после того, как он закончит. - person Alex Shestakov; 15.02.2017
comment
@AlexShestakov Спасибо за этот пост. Для меня this.formControl.control.validator (и asyncValidator) возвращается нулевым. Я делаю это на существующей реактивной форме с рабочей проверкой. Любые идеи? - person snowBlind; 09.08.2017
comment
Кажется, я пробовал this.formControl.control.validators [множественное число] вместо this.formControl.control.validator - person snowBlind; 09.08.2017
comment
как это сделать, если вы используете FormBuilder? - person zer09; 15.08.2018
comment
@ zer09 - ты смог узнать, как использовать updateOn с FormBuilder? - person Manu Chadha; 03.09.2018
comment
@ManuChadha нет, это все еще есть проблема на github - person zer09; 04.09.2018

Начиная с Angular v 5.0.0 это теперь возможно, пометив updateOn: 'blur' в элементе управления формы.

Это также означает, что valueChanges не срабатывает для этого элемента управления формы, пока не произойдет событие размытия. Вот пример с minlength вместе с required:

this.form = new FormGroup({
  username: new FormControl('', {
  validators: [Validators.required, Validators.minLength(6)], updateOn: 'blur'} )
})

get username() {
  return this.form.get('username');
}

В шаблоне вы хотите отметить, что сообщение проверки не будет отображаться, если поле не равно touched:

<div *ngIf="username.hasError('minlength') || username.hasError('required') 
              && username.touched">Required and minlength 6!
</div>

DEMO

P.S. При необходимости вы также можете пометить это на всей форме, а не только на определенном элементе управления формы

person AJT82    schedule 05.11.2017
comment
При использовании этого синтаксиса второй параметр в конструкторе FormControl имеет тип AbstractControlOptions, а асинхронные валидаторы задаются следующим образом: this.form = new FormGroup({ username: new FormControl('', { validators: [Validators.required, Validators .minLength(6)], asyncValidators: yourAsyncFunction, updateOn: 'blur'} ) }) - person Programmer; 08.05.2018
comment
как это сделать, если вы используете FormBuilder? - person zer09; 15.08.2018
comment
любой, кто хочет, чтобы он работал с Validator.compose(), может ссылаться на эту stackoverflow.com/questions/50103342/ - person Snow Bases; 06.12.2019

Нашел способ, в rc6.

1- Создайте директиву: validate-onblur.directive.ts

@Directive({
  selector: '[validate-onblur]',
  host: {
    '(focus)': 'onFocus($event)',
    '(blur)': 'onBlur($event)'
  }
})
export class ValidateOnBlurDirective {
    constructor(public formControl: NgControl) {
    }

    onFocus($event) {
      this.formControl.control.markAsUntouched(false);
    }

    onBlur($event) {
      this.formControl.control.markAsTouched(true);
    }
}

Затем в вашем html-шаблоне просто добавьте директиву в форму, в моем примере используется модель ReactiveFormsModule.

Затем добавьте это в сообщение об ошибке:

<input type="text" formControlName="full_name" validate-onblur />

<span *ngIf="formAccountDetails.controls.full_name.touched && !formAccountDetails.controls.full_name.valid && !formAccountDetails.controls.full_name.pristine" class="errors">
        ...
</span>
person Cristian Deschamps    schedule 13.09.2016

Примерно так: используйте свойство touched объекта ngControl.

 <div class="form-group" [class.has-error]="!name.valid && name.touched">
        <label for="name">Name</label>
        <input #name="ngForm" ngControl="name" name="name" type="text" class="form-control" required>
 </div>
person tomasz    schedule 27.12.2015
comment
Это не поможет в следующий раз, когда элемент получит фокус. name.focus получает свое истинное значение при первом касании, а со второго раза - индикатор проверки будет отображаться при каждом нажатии клавиши. - person user5260143; 13.10.2016

Я немного улучшил решение Алекса Шестакова, которое, кстати, уже работало, чтобы избежать установки состояния элемента управления в действительное при изменении его значения, в то время как он сохраняет фокус.

@Directive({
    selector: '[validate-onblur]',
    host: {
        '(focus)': 'onFocus($event)',
        '(blur)' : 'onBlur($event)'
    }
})
export class ValidateOnBlurDirective {

    private validators: any;
    private asyncValidators: any;
    private hasFocus = false;

    constructor(public formControl: NgControl) {
    }

    onFocus($event) {
        this.hasFocus = true;
        this.validators = this.formControl.control.validator;
        this.asyncValidators = this.formControl.control.asyncValidator;
        this.formControl.control.clearAsyncValidators();
        this.formControl.control.clearValidators();
        this.formControl.control.valueChanges
            .filter(() => this.hasFocus)
            .subscribe(() => this.formControl.control.markAsPending());
    }

    onBlur($event) {
        this.hasFocus = false;
        this.formControl.control.setAsyncValidators(this.asyncValidators);
        this.formControl.control.setValidators(this.validators);
        this.formControl.control.updateValueAndValidity();
    }
}

Таким образом, элемент управления будет оставаться в состоянии ожидания до тех пор, пока он сохраняет фокус. Это поможет избежать случая, когда элемент управления недействителен до того, как он получит фокус, а затем, как только пользователь начнет печатать на нем, он будет помечен как действительный, до того, как произойдет событие размытия, когда снова будут установлены валидаторы и реальная действительность контроль должен быть определен.

person Daniel Francisco Sabugal    schedule 10.02.2017

Ты можешь

[class.has-error]="!form.controls['ctrlEmailAddress'].valid"

Это добавит класс has-error, как только вы измените модель. Так что в основном вам не нужно размывать, чтобы проверить проверку.

Это решило проблему с бета-версией angular 2.0.0;)

person przemcio    schedule 25.11.2015
comment
Это не отвечает на вопрос. Вопрос спрашивает, как проверить только на размытие. - person Shachar Har-Shuv; 20.03.2019