Angular2- RC6 自定义表单控件不起作用

Posted

技术标签:

【中文标题】Angular2- RC6 自定义表单控件不起作用【英文标题】:Angular2- RC6 Custom Form Controls Not working 【发布时间】:2017-01-17 10:02:25 【问题描述】:

我已将 Angular2 RC5 应用程序更新为 RC6。我根据 Thoughtram 的 this 教程开发了一些自定义表单控件。

在 RC5 之前一切正常,但是在更新后,经过一番调查后验证不再有效,我发现控件的值未反映在关联的模型中。

您可以从 Thoughtram 的教程here 中找到原始 plunker。

要重现问题,请更新 systemjs.config.js 文件中的版本信息

var ngVer = '@2.0.0-rc.5'; 
var routerVer = '@3.0.0-rc.1'; 
var formsVer = '@0.3.0'; 
var routerDeprecatedVer = '@2.0.0-rc.2'; 

var ngVer = '@2.0.0-rc.6'; 
var routerVer = '@3.0.0-rc.2'; 
var formsVer = '@2.0.0-rc.6';

版本更新后,您将看到控件值未更新,因此验证不起作用。

但是,如果我将 angular 版本更新为 @2.0.0-rc.6 并保持表单版本不变,即 @0.3.0,它可以工作。

更新 1:该组件的代码是

import  Component, OnInit, forwardRef, Input, OnChanges  from '@angular/core';
import  FormControl, ControlValueAccessor, NG_VALUE_ACCESSOR, NG_VALIDATORS  from '@angular/forms';


export function createCounterRangeValidator(maxValue, minValue) 
  return (c: FormControl) => 
    let err = 
      rangeError: 
        given: c.value,
        max: maxValue || 10,
        min: minValue || 0
      
    ;

  return (c.value > +maxValue || c.value < +minValue) ? err: null;
  


@Component(
  selector: 'counter-input',
  template: `
    <button (click)="increase()">+</button> counterValue <button (click)="decrease()">-</button>
  `,
  providers: [
     provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => CounterInputComponent), multi: true ,
     provide: NG_VALIDATORS, useExisting: forwardRef(() => CounterInputComponent), multi: true 
  ]
)
export class CounterInputComponent implements ControlValueAccessor, OnChanges 

  propagateChange:any = () => ;
  validateFn:any = () => ;

  @Input('counterValue') _counterValue = 0;
  @Input() counterRangeMax;
  @Input() counterRangeMin;

  get counterValue() 
    return this._counterValue;
  

  set counterValue(val) 
    this._counterValue = val;
    this.propagateChange(val);
  

  ngOnChanges(inputs) 
    if (inputs.counterRangeMax || inputs.counterRangeMin) 
      this.validateFn = createCounterRangeValidator(this.counterRangeMax, this.counterRangeMin);
    
  

  writeValue(value) 
    if (value) 
      this.counterValue = value;
    
  

  registerOnChange(fn) 
    this.propagateChange = fn;
  

  registerOnTouched() 

  increase() 
    this.counterValue++;
  

  decrease() 
    this.counterValue--;
  

  validate(c: FormControl) 
    return this.validateFn(c);
  

主模块如下:

import  NgModule  from '@angular/core';
import  BrowserModule  from '@angular/platform-browser';
import  FormsModule, ReactiveFormsModule  from '@angular/forms';
import  AppComponent  from './app.component';
import  CounterInputComponent  from './counter-input.component.ts';

@NgModule(
  imports: [BrowserModule, FormsModule, ReactiveFormsModule],
  declarations: [AppComponent, CounterInputComponent],
  bootstrap: [AppComponent]
)
export class AppModule 

我正在我的 app.component 中使用该组件,例如

import  Component  from '@angular/core';
import  FormBuilder, FormGroup  from '@angular/forms';
import  createCounterRangeValidator  from './counter-input.component';

@Component(
  selector: 'my-app',
  template: `
    <h2>Inside Form</h2>
    <div>
      <label>Change min value:</label>
      <input [(ngModel)]="minValue">
    </div>
    <div>
      <label>Change max value:</label>
      <input [(ngModel)]="maxValue">
    </div>
    <form [formGroup]="form">
      <p>Control value: form.controls.counter.value</p>
      <p>Min Value: minValue</p>
      <p>Max Value: maxValue</p>
      <p>Form Value:</p>
      <pre> form.value | json </pre>

      <counter-input
        formControlName="counter"
        [counterRangeMax]="maxValue"
        [counterRangeMin]="minValue"
        [counterValue]="50"
        ></counter-input>
    </form>

    <p *ngIf="!form.valid">Form is invalid!</p>


    <h2>Standalone</h2>
    <counter-input
      counterMinValue="0"
      counterMaxValue="3"
      [counterValue]="counterValue"></counter-input>
  `
)
export class AppComponent 

  form:FormGroup;
  counterValue = 3;
  minValue = 0;
  maxValue = 12;

  constructor(private fb: FormBuilder) 

  ngOnInit() 
    this.form = this.fb.group(
      counter: this.counterValue
    );
  


更新 2:我已经在 Github here报告了这个问题

【问题讨论】:

【参考方案1】:

感谢您更新您的来源。我有一个控件,基本上也可以完成您在这里所做的工作:

这是子组件微调器:

@Component(
  selector: 'kg-spinner',
  templateUrl: './app/shared/numberSpinner/kgSpinner.component.html',
  styleUrls: ['./app/shared/numberSpinner/kgSpinner.component.css']
)

export class KgSpinnerComponent implements OnInit 
  @Input('startValue') curValue: number;
  @Input() range: number[];
  @Input() increment: number;
  @Input() spinName;
  @Input() precision: number;
  @Input() theme: string;

  @Output() onChanged = new EventEmitter<SpinnerReturn>();

  lowerLimit: number;
  upperLimit: number;
  name: string;
  curTheme: Theme;
  errorMessage: string;
  sr: SpinnerReturn;
  appPageHeaderDivStyle: ;
  unit: string = "(g)";

  constructor(private ts: ThemeService) 
    this.sr = new SpinnerReturn();
  

  ngOnInit() 
    this.lowerLimit = this.range[0];
    this.upperLimit = this.range[1];
    this.appPageHeaderDivStyle = this.ts.getAppPageHeaderDivStyle();
    if(this.spinName === "carbGoal") 
      this.unit = "(g)";
     else if (this.spinName === "proteinGoal") 
      this.unit = "(g)";
     else 
      this.unit = "(%)";
    
  

html:

<div class="ui-grid-col-8 spinnerMargin">
                      <kg-spinner spinName="proteinGoal" [range]=[.6,1.2] [increment]=.1 [startValue]=.6 [precision]=1 (onChanged)="onChanged($event)"></kg-spinner>
                    </div>

父组件onChanged:

  onChanged(sr: SpinnerReturn) 
        if (sr.spinName === "carbGoal") 
            (<FormControl>this.macroForm.controls['carbGoal']).setValue(sr.spinValue);
         else if (sr.spinName === "proteinGoal") 
            (<FormControl>this.macroForm.controls['proteinGoal']).setValue(sr.spinValue);
         else if (sr.spinName === "calorieDifference") 
            (<FormControl>this.macroForm.controls['calorieDifference']).setValue(sr.spinValue);
        

这一切都在 RC6 上完美运行。希望这可以帮助您解决问题。

【讨论】:

如您所见,我没有在提供的源代码中使用任何已弃用的东西。所以我相信这是其他一些问题。 这里有区别。我的组件是一个自定义表单控件,它可以根据值使表单有效/无效。在您的情况下,您正在编写一个自定义组件。我面临的问题只有在您编写自定义表单控件时才会出现。自定义表单控件的链接是blog.thoughtram.io/angular/2016/07/27/… 好的......但我看不出两者之间的区别。我只允许子组件中的有效值,所以它们的输出总是有效的。 非常感谢您为解决我的问题所做的努力。但正如我之前所说,我的组件是一个表单控件,这意味着您可以将其放在表单中并在其上设置内置/自定义验证器,如果验证失败,则表单将变为无效。如果您阅读我之前在上面的评论中发送的链接,您将了解全貌。 你没有理解我的意思。如果您正在实现微调器,请提供有效范围,就像我在示例中所做的那样。我以多种形式使用它,并且效果很好。我只允许它输入有效范围并设置有效的默认值。表单始终保持有效,因为这些控件始终有效。我看不出允许用户输入无效数据的意义……【参考方案2】:

RC.6 中为验证器指令引入了一个可选的 registerOnChange() 函数,并且在 controlValueAccessor 中已经存在一个同名的函数,它导致了冲突,并且我的组件中的 registerOnChange 被调用了两次。

这已在issue 下得到修复。

建议的临时解决方法是

@Component(
  ...
  ...
)
export class CounterInputComponent implements ControlValueAccessor, OnChanges 

  isPropagate: boolean = false;

  /*Rest of the class implementation
  ...
  ...
  */

  registerOnChange(fn) 
    if (this.isPropagate) 
      return;
    

    this.propagateChange = fn;
    this.isPropagate = true;
  

  //.....

【讨论】:

以上是关于Angular2- RC6 自定义表单控件不起作用的主要内容,如果未能解决你的问题,请参考以下文章

Angular 2 - 自定义表单控件 - 禁用

标签的 .text 字段不起作用

Angular 2 - 单元测试绑定到嵌套的自定义表单控件

Angular 2 自定义表单输入

自定义表单验证注释不起作用

在我的 angular2 代码中,我想更新表单的值,但我的点击功能不起作用