入力が通貨形式に強制されるように、リアクティブフォーム内でパイプを使用する方法を見つけようとしています。私はすでにこのための独自のパイプを作成しており、コードの他の領域でテストしたので、単純なパイプとして機能することがわかります。私のパイプ名は「udpCurrency」です
スタックオーバーフローで私が見つけた最も近い答えはこれでした: Angular2-View のINPUT要素でngModel内でパイプを使用します私のフォームがリアクティブであるという事実
関連するすべてのコードは次のとおりです。
テンプレート
<form [formGroup]="myForm" #f="ngForm">
<input class="form-control col-md-6"
formControlName="amount"
[ngModel]="f.value.amount | udpCurrency"
(ngModelChange)="f.value.amount=$event"
placeholder="Amount">
</form>
コンポーネント
import { Component, OnInit, HostListener } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
export class MyComponent implements OnInit {
myForm: FormGroup;
constructor(
private builder: FormBuilder
) {
this.myForm = builder.group({
amount: ['', Validators.required]
});
}
}
エラー:
ERROR Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'undefined: '. Current value: 'undefined: undefined'
テンプレート駆動型とリアクティブ型を混在させると、これが起こります。互いに戦う2つのバインディングがあります。テンプレート駆動型または事後対応型を選択します。リアクティブルートを使用する場合は、[value]
をパイプに使用できます...
このパイプは、ビューに目的の出力を表示するためだけのものです。
<form [formGroup]="myForm">
<input
[value]="myForm.get('amount').value | udpCurrency"
formControlName="amount"
placeholder="Amount">
</form>
私はこれが機能していると思ったが、結局のところ、私は間違っていた(そして間違った答えを受け入れた)。私は、自分にとって都合の良い新しい方法でロジックを再編集し、上記のコメントでジェイコブロバーツの懸念に答えています。これが私の新しいソリューションです。
テンプレート:
<form [formGroup]="myForm">
<input formControlName="amount" placeholder="Amount">
</form>
コンポーネント:
import { Component, OnInit, HostListener } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { UdpCurrencyMaskPipe } from '../../../_helpers/udp-currency-mask.pipe';
export class MyComponent implements OnInit {
myForm: FormGroup;
constructor(
private builder: FormBuilder,
private currencyMask: UdpCurrencyMaskPipe,
) {
this.myForm = builder.group({
amount: ['', Validators.required]
});
this.myForm.valueChanges.subscribe(val => {
if (typeof val.amount === 'string') {
const maskedVal = this.currencyMask.transform(val.amount);
if (val.amount !== maskedVal) {
this.myForm.patchValue({amount: maskedVal});
}
}
});
}
}
パイプ:
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'udpCurrencyMask'
})
export class UdpCurrencyMaskPipe implements PipeTransform {
amount: any;
transform(value: any, args?: any): any {
let amount = String(value);
const beforePoint = amount.split('.')[0];
let integers = '';
if (typeof beforePoint !== 'undefined') {
integers = beforePoint.replace(/\D+/g, '');
}
const afterPoint = amount.split('.')[1];
let decimals = '';
if (typeof afterPoint !== 'undefined') {
decimals = afterPoint.replace(/\D+/g, '');
}
if (decimals.length > 2) {
decimals = decimals.slice(0, 2);
}
amount = integers;
if (typeof afterPoint === 'string') {
amount += '.';
}
if (decimals.length > 0) {
amount += decimals;
}
return amount;
}
}
ここで学んだことがいくつかあります。 1つは、ジェイコブが言ったことが真実であるということでした。もう1つの方法は、最初は機能していましたが、値が変わっても更新されませんでした。注意すべきもう1つの非常に重要なことは、ビューパイプと比較して、マスクにまったく異なるタイプのパイプが必要なことです。たとえば、ビュー内のパイプはこの値「100」を取得して「$ 100.00」に変換しますが、値を入力しているときにその変換を実行したくない場合は、入力後にのみ実行する必要があります。このため、数値以外の数字を削除し、小数部を2桁に制限する通貨マスクパイプを作成しました。
カスタムコントロールを作成するつもりでしたが、ngModelChange
を介してFormControlクラスの "onChange"をオーバーライドする方が簡単であることがわかりました。 emitViewToModelChange: false
は、変更イベントの繰り返しループを回避するために、更新ロジック中に重要です。通貨へのパイプ処理はすべてコンポーネント内で行われ、コンソールエラーの発生を心配する必要はありません。
<input matInput placeholder="Amount"
(ngModelChange)="onChange($event)" formControlName="amount" />
@Component({
providers: [CurrencyPipe]
})
export class MyComponent {
form = new FormGroup({
amount: new FormControl('', { validators: Validators.required, updateOn: 'blur' })
});
constructor(private currPipe:CurrencyPipe) {}
onChange(value:string) {
const ctrl = this.form.get('amount') as FormControl;
if(isNaN(<any>value.charAt(0))) {
const val = coerceNumberProperty(value.slice(1, value.length));
ctrl.setValue(this.currPipe.transform(val), { emitEvent: false, emitViewToModelChange: false });
} else {
ctrl.setValue(this.currPipe.transform(value), { emitEvent: false, emitViewToModelChange: false });
}
}
onSubmit() {
const rawValue = this.form.get('amount').value;
// if you need to strip the '$' from your input's value when you save data
const value = rawValue.slice(1, rawValue.length);
// do whatever you need to with your value
}
}
ここでの他の答えは私には適切に機能しませんでしたが、私は非常にうまく機能する方法を見つけました。リアクティブフォームvalueChangesサブスクリプション内でパイプ変換を適用する必要がありますが、再帰ループを作成しないように、イベントを発行しないでください。
this.formGroup.valueChanges.subscribe(form => {
if (form.amount) {
this.formGroup.patchValue({
amount: this.currencyMask.transform(form.amount)
}, {
emitEvent: false
});
}
});
また、これは、パイプがそこにあるものを「アンフォーマット」することを必要とします。これは通常、パイプの変換関数内で次のような単純なものです
value = value.replace(/\$+/g, '');
パイプコードを知らなくても、フォームを構築している場所のためにエラーがスローされる可能性があります。
入力が解決された後、Angularの変更検出フックを使用してその値を設定してみてください。
export class MyComponent implements OnInit {
myForm: FormGroup;
constructor(private builder: FormBuilder) { }
ngOnInit() {
this.myForm = builder.group({
amount: ['', Validators.required]
});
}
}