Angular - controlValueAccessor方法中的writeValue()未被调用

12

Angular 版本: 6.1

我正在尝试使用 Kara Erickson 所称的复合 ControlValueAccessor 实现自定义控件,如演示中所示,Angular Forms – Kara Erickson – AngularConnect 2017。该控件的值应设置为两个子输入值之一。

问题在于 writeValue() 似乎仅在最初调用,而不在进一步的值更改时调用。

这里是一个 stackblitz 演示

form.html

<form #f="ngForm" (ngSubmit)="f.form.valid && Submit(f)">
    <confirm-password name='password' ngModel #pwd='ngModel'></confirm-password>
    <div>{{pwd.status}}</div>
</form>

confirm-password.html

<div [formGroup]='pass'>
  <label>Password:
    <input type="password" formControlName='pwd1' (blur)='onTouched()'>
  </label>
  <div *ngIf="controlDir.touched && controlDir.control.errors?.required" 
         class="error">Password is required.</div>

  <label>Please re-enter password:
    <input type="password" formControlName='pwd2' (blur)='onTouched()'>
  </label>
  <div class='error' *ngIf='controlDir.touched && controlDir.control.errors?.notEqual && 
                            !controlDir.errors?.required'>Passwords do not match.</div>
</div>

confirm-password.ts

import { Component, Self, OnInit, OnDestroy } from '@angular/core';
import { ControlValueAccessor, AbstractControl, NgControl,
         ValidatorFn, Validators, FormGroup, FormControl } from '@angular/forms';
import { Subscription } from 'rxjs';

@Component({
  selector: 'confirm-password',
  templateUrl: './confirm-password.component.html',
  styleUrls: ['./confirm-password.component.css']
})
export class ConfirmPasswordComponent implements ControlValueAccessor, OnInit, OnDestroy
{
  password: string; // the value of the control should be set to this value
  pass = new FormGroup({
    pwd1: new FormControl(),
    pwd2: new FormControl()
  });
  private onChange: (value: string) => void;
  private onTouched: (value: string) => void;
  private valueChanges: Subscription;

  constructor(@Self() public controlDir: NgControl)
  {
    controlDir.valueAccessor = this;
  }

  ngOnInit()
  {
    const control = this.controlDir.control;

    let myValidators = [
            Validators.required, 
            equalValidator(this.pass.controls.pwd1,
                           this.pass.controls.pwd2)
        ]
    // ovoid overwriting any existing validators
    let validators = control.validator
      ? [control.validator, ...myValidators]
      : [...myValidators];
    control.setValidators(validators);
    control.updateValueAndValidity();
  }

  writeValue(val: any)
  {
    /* whether everything inside of this method is commented out or not 
       doesn't seem to affect anything */
    console.log(val);
    //val && this.pass.setValue(val, {emitEvent: false});
    this.password = val;
  }

  registerOnChange(fn: (val: any) => void)
  {
    this.valueChanges = this.pass.valueChanges.subscribe(fn);
  }

  registerOnTouched(fn: () => void)
  {
    this.onTouched = fn;
  }

  setDisabledState(disabled: boolean)
  {
    disabled ? this.pass.disable() : this.pass.enable();
  }

  ngOnDestroy()
  {
    this.valueChanges.unsubscribe();
  }

}

export function equalValidator(el1, el2): ValidatorFn
{
  return (ctrl: AbstractControl): {[key: string]: any} | null => {
    const notEqual = el1.value !== el2.value;

    return notEqual ?  {"notEqual": true} : null;
  };
}

请提供指向 StackBlitz 编辑器的链接。 - Antoniossss
哎呀,我没意识到我链接了错误的一个。现在应该可以了。 - slanden
对我来说运行良好:https://stackblitz.com/edit/angular-ukowfl?file=src/app/app.component.html - 点击按钮 - Antoniossss
你改过它吗?对我来说它完全不工作,控制台上出现了几个错误。 - slanden
2个回答

19

1
在阅读了大约十几篇文章和教程之后,你的是唯一一个让我终于明白了的。谢谢!关键部分是,“您不需要自己提供onTouch和onChange函数的实现。它们将由Angular提供...您可以决定何时调用这些函数。” - Roobot
我读了很多关于那个主题的文章,但只有你能更好地解释事情。对我来说,关键部分是理解我们如何从父组件改变formcontrol的值(例如重置、设置值,你提到过),以及通过使用writevalue和onchange来理解Angular是如何交互的。谢谢! - Apoleytaa

8
当您更改控件值(例如通过设置后备ngModel属性或控件值)而不是用户输入时,会调用writeValue。我已经删除了由于误用ngModel而导致的错误。如您所见,如果您在额外的输入中键入新值并按按钮,则会调用writeValue。总之,当控制器更改模型值时,会调用writeValue,而writeValue的目的是通过自定义控件执行必要的操作以反映模型更改。请参考:https://stackblitz.com/edit/angular-hu2bfs?file=src/app/app.module.ts

谢谢,我现在明白了。虽然你回答了我的问题,但我希望我能重新表述一下,并不要假设 writeValue 可以解决我的问题,因为它实际上并不能帮助我实现将“确认密码”值设置为仅一个子控件的值,而不是包含两个子控件值的对象。尽管如此,还是非常感谢你。 - slanden
但是如果FormControl是从父组件传递下来的,我该如何访问它呢?从聪明/愚蠢组件的角度来看,将其从上层组件传递下来似乎更有意义,而不是在组件内部创建它。您还在Stackblitz中混合使用响应式+模板表单。您不应该同时使用ngModel和FormControl。 - Emobe

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