如何手动将Angular表单字段设置为无效?

332

我正在处理一个登录表单,如果用户输入了无效的凭据,我们想要将电子邮件和密码字段标记为无效,并显示一条消息,说明登录失败。在可观察回调中,我该如何设置这些字段为无效?

模板:

<form #loginForm="ngForm" (ngSubmit)="login(loginForm)" id="loginForm">
  <div class="login-content" fxLayout="column" fxLayoutAlign="start stretch">
    <md-input-container>
      <input mdInput placeholder="Email" type="email" name="email" required [(ngModel)]="email">
    </md-input-container>
    <md-input-container>
      <input mdInput placeholder="Password" type="password" name="password" required [(ngModel)]="password">
    </md-input-container>
    <p class='error' *ngIf='loginFailed'>The email address or password is invalid.</p>
    <div class="extra-options" fxLayout="row" fxLayoutAlign="space-between center">
     <md-checkbox class="remember-me">Remember Me</md-checkbox>
      <a class="forgot-password" routerLink='/forgot-password'>Forgot Password?</a>
    </div>
    <button class="login-button" md-raised-button [disabled]="!loginForm.valid">SIGN IN</button>
     <p class="note">Don't have an account?<br/> <a [routerLink]="['/register']">Click here to create one</a></p>
   </div>
 </form>

登录方式:

 @ViewChild('loginForm') loginForm: HTMLFormElement;

 private login(formData: any): void {
    this.authService.login(formData).subscribe(res => {
      alert(`Congrats, you have logged in. We don't have anywhere to send you right now though, but congrats regardless!`);
    }, error => {
      this.loginFailed = true; // This displays the error message, I don't really like this, but that's another issue.
      this.loginForm.controls.email.invalid = true;
      this.loginForm.controls.password.invalid = true; 
    });
  }

除了将输入无效标志设置为true外,我还尝试将email.valid标志设置为false,并将loginForm.invalid设置为true。但这些都不能使输入显示其无效状态。


你的后端和 Angular 是否在不同的端口上?如果是,这可能是一个 CORS 问题。你在后端使用哪个框架? - Mike3355
你可以使用 setErrors 方法。提示:你应该在组件文件中添加所需的验证器。此外,使用 ngModel 和响应式表单有特定的原因吗? - developer033
@developer033 在这里有点晚了,但那些看起来不像是响应式表单,而是模板驱动表单。 - thenetimp
10个回答

457

在组件中:

formData.form.controls['email'].setErrors({'incorrect': true});

在HTML中:

<input mdInput placeholder="Email" type="email" name="email" required [(ngModel)]="email"  #email="ngModel">
<div *ngIf="!email.valid">{{email.errors| json}}</div>

27
那么如何之后移除错误呢?对我来说 setErrors({'incorrect': false}) 或者 setErrors({}) 都不起作用。 - Robouste
4
我可以将整个响应式表单设置为有效或无效,而不是重置字段吗? - xtremist
49
@Robouste 你可以通过手动调用 setErrors(null) 来移除错误。 - Idrees Khan
18
除了这个答案之外:如下面@M.Farahmand所提到的,如果没有formData.form.controls ['email'] .markAsTouched();,那么这段代码对我来说是无效的。 使用 setErrors({'incorrect': true}) 只会为输入设置 ng-invalid css类。希望它能帮助到某些人。 - Barabas
12
如果有更多的验证器,例如“required”,那么setErrors(null)会删除该错误吗? - NaN
显示剩余8条评论

152

在Julia Passynkova的回答基础上补充:

设置组件的验证错误:

formData.form.controls['email'].setErrors({'incorrect': true});

取消组件中的验证错误:

formData.form.controls['email'].setErrors(null);

使用null取消错误时要小心,因为这会覆盖所有错误。如果您想保留某些错误,可能需要先检查其他错误是否存在:

if (isIncorrectOnlyError){
   formData.form.controls['email'].setErrors(null);
}

9
可以使用类似于formData.form.controls['email'].setErrors({'incorrect': false});的方式取消验证错误吗? - rudrasiva86
5
响应式表单呢? - seidme
2
整段代码都是响应式表单的完整示例,亲。 - Rebai Ahmed
不,你不能设置为false,因为Angular检查错误不是通过值。我认为这取决于用户是否显示错误。 private _calculateStatus():FormControlStatus { 如果(this._allControlsDisabled())返回DISABLED; 如果(this.errors)返回INVALID; 如果(this._hasOwnPendingAsyncValidator || this._anyControlsHaveStatus(PENDING))返回PENDING; 如果(this._anyControlsHaveStatus(INVALID))返回INVALID; 返回VALID; }但是,当为false时,您可以将其设置为false并且不显示错误。 - Grant mitchell
继续:如果其他所有条件都未满足,并且将错误设置为null会使事情变得复杂,那么只需从错误对象中删除您正在处理的属性并将setErrors设置为修改后的对象即可。 - Mary Obiagba
显示剩余2条评论

47

在新版的Material 2中,控件名称以mat前缀开头,setErrors()方法不再起作用。相反,可以将Juila的答案更改为:

formData.form.controls['email'].markAsTouched();

9
将一个控件标记为“已触摸”并不会使其无效。 - sudharsan tk
@sudharsantk 这是真的,它并不会使控件无效,但是这段代码确实可以解决一个问题,即当一个无效字段没有被报告为无效时。例如,在你以编程方式将必填字段设置为空后,就需要使用上述提到的代码。 - Steve Giles

41

我试图在模板表单中的ngModelChange处理程序中调用setErrors()。直到我使用setTimeout()等待一次时钟滴答才奏效:

模板:

<input type="password" [(ngModel)]="user.password" class="form-control" 
 id="password" name="password" required (ngModelChange)="checkPasswords()">

<input type="password" [(ngModel)]="pwConfirm" class="form-control"
 id="pwConfirm" name="pwConfirm" required (ngModelChange)="checkPasswords()"
 #pwConfirmModel="ngModel">

<div [hidden]="pwConfirmModel.valid || pwConfirmModel.pristine" class="alert-danger">
   Passwords do not match
</div>

组件:

@ViewChild('pwConfirmModel') pwConfirmModel: NgModel;

checkPasswords() {
  if (this.pwConfirm.length >= this.user.password.length &&
      this.pwConfirm !== this.user.password) {
    console.log('passwords do not match');
    // setErrors() must be called after change detection runs
    setTimeout(() => this.pwConfirmModel.control.setErrors({'nomatch': true}) );
  } else {
    // to clear the error, we don't have to wait
    this.pwConfirmModel.control.setErrors(null);
  }
}

像这样的问题让我更喜欢响应式表单。


无法找到名称为'NgModel'的内容。对于行@ViewChild('pwConfirmModel') pwConfirmModel: NgModel;出现了错误。是否有任何解决此问题的方法? - Deep 3015
使用setTimeOuts是怎么回事?我也注意到了,似乎控件不会立即更新自己。这引入了很多hacky的代码来解决这个限制。 - Jake Shakesworth
2
谢谢。我知道setErrors,但直到我使用了setTimeout才起作用。 - Sampgun
我建议使用timer()操作符代替timeout。 - Rebai Ahmed
在使用setError之后,我建议使用updateValueAndValidity()方法。 - ghaschel

14

在我的响应式表单中,如果另一个字段被选中,我需要将某个字段标记为无效。在ng 7版中,我执行了以下操作:

    const checkboxField = this.form.get('<name of field>');
    const dropDownField = this.form.get('<name of field>');

    this.checkboxField$ = checkboxField.valueChanges
        .subscribe((checked: boolean) => {
            if(checked) {
                dropDownField.setValidators(Validators.required);
                dropDownField.setErrors({ required: true });
                dropDownField.markAsDirty();
            } else {
                dropDownField.clearValidators();
                dropDownField.markAsPristine();
            }
        });

所以,当我选择该复选框时,它将设置下拉框为必填项并将其标记为已更改。如果您不进行标记,则在提交表单或与其交互之前,它将不会无效(错误)。

如果复选框设置为false(未选中),则我们将清除下拉列表上的必填验证器并将其重置为原始状态。

另外,请记得取消订阅监视字段更改!


markAsDirty() 是关键。 - undefined

13

您也可以将ViewChild的“类型”更改为NgForm,如下:

@ViewChild('loginForm') loginForm: NgForm;

然后,以@Julia提到的相同方式引用您的控件:
 private login(formData: any): void {
    this.authService.login(formData).subscribe(res => {
      alert(`Congrats, you have logged in. We don't have anywhere to send you right now though, but congrats regardless!`);
    }, error => {
      this.loginFailed = true; // This displays the error message, I don't really like this, but that's another issue.

      this.loginForm.controls['email'].setErrors({ 'incorrect': true});
      this.loginForm.controls['password'].setErrors({ 'incorrect': true});
    });
  }

将错误设置为null会清除UI上的错误:

this.loginForm.controls['email'].setErrors(null);

2
这是一个可行的例子:

Here is an example that works:

MatchPassword(AC: FormControl) {
  let dataForm = AC.parent;
  if(!dataForm) return null;

  var newPasswordRepeat = dataForm.get('newPasswordRepeat');
  let password = dataForm.get('newPassword').value;
  let confirmPassword = newPasswordRepeat.value;

  if(password != confirmPassword) {
    /* for newPasswordRepeat from current field "newPassword" */
    dataForm.controls["newPasswordRepeat"].setErrors( {MatchPassword: true} );
    if( newPasswordRepeat == AC ) {
      /* for current field "newPasswordRepeat" */
      return {newPasswordRepeat: {MatchPassword: true} };
    }
  } else {
    dataForm.controls["newPasswordRepeat"].setErrors( null );
  }
  return null;
}

createForm() {
  this.dataForm = this.fb.group({
    password: [ "", Validators.required ],
    newPassword: [ "", [ Validators.required, Validators.minLength(6), this.MatchPassword] ],
    newPasswordRepeat: [ "", [Validators.required, this.MatchPassword] ]
  });
}

这可能有些“hacky”,但我喜欢它,因为您不必设置自定义ErrorStateMatcher来处理Angular Material输入错误! - David Melin

2
尽管有些晚,但以下解决方案对我有效。
(注:原文中的“form”应为“for me”)
    let control = this.registerForm.controls['controlName'];
    control.setErrors({backend: {someProp: "Invalid Data"}});
    let message = control.errors['backend'].someProp;

1
这太棒了!我刚刚更新了我的表单错误组件,以便根据自定义的 backend.message 字符串进行处理 :) - bmcminn

0

这篇Angular文档中的示例或许会有所帮助: <input type="text" id="name" name="name" class="form-control"

      required minlength="4" appForbiddenName="bob"
      [(ngModel)]="hero.name" #name="ngModel">

<div *ngIf="name.invalid && (name.dirty || name.touched)"
    class="alert">

  <div *ngIf="name.errors?.['required']">
    Name is required.
  </div>
  <div *ngIf="name.errors?.['minlength']">
    Name must be at least 4 characters long.
  </div>
  <div *ngIf="name.errors?.['forbiddenName']">
    Name cannot be Bob.
  </div>

</div>

-13

进行单元测试:

spyOn(component.form, 'valid').and.returnValue(true);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接