Angular2- RC6 自定义表单控件不工作
Angular2- RC6 Custom Form Controls Not working
我已将我的 Angular2 RC5 应用程序更新为 RC6。我根据 Thoughtram 的 this 教程开发了一些自定义表单控件。
在 RC5 之前一切正常,但是在更新后验证不再工作,经过一些调查我发现控件的值没有反映在关联的模型中。
您可以从 Thoughtram 的教程中找到原始插件 here。
要重现问题,请更新 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
上报告了这个问题
感谢您更新您的来源。我有一个控件,基本上也可以完成您在这里所做的事情:
这是子组件微调器:
@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 上完美运行。希望这能帮助您解决问题。
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 RC5 应用程序更新为 RC6。我根据 Thoughtram 的 this 教程开发了一些自定义表单控件。
在 RC5 之前一切正常,但是在更新后验证不再工作,经过一些调查我发现控件的值没有反映在关联的模型中。
您可以从 Thoughtram 的教程中找到原始插件 here。
要重现问题,请更新 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
上报告了这个问题感谢您更新您的来源。我有一个控件,基本上也可以完成您在这里所做的事情:
这是子组件微调器:
@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 上完美运行。希望这能帮助您解决问题。
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;
}
//.....
}