web-dev-qa-db-ja.com

Angular 5リアクティブフォーム入力でパイプを使用する方法

入力が通貨形式に強制されるように、リアクティブフォーム内でパイプを使用する方法を見つけようとしています。私はすでにこのための独自のパイプを作成しており、コードの他の領域でテストしたので、単純なパイプとして機能することがわかります。私のパイプ名は「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'
31
Dallas Caley

テンプレート駆動型とリアクティブ型を混在させると、これが起こります。互いに戦う2つのバインディングがあります。テンプレート駆動型または事後対応型を選択します。リアクティブルートを使用する場合は、[value]をパイプに使用できます...

このパイプは、ビューに目的の出力を表示するためだけのものです。

<form [formGroup]="myForm">
  <input 
    [value]="myForm.get('amount').value | udpCurrency"
    formControlName="amount" 
    placeholder="Amount">
</form>
44
AJT_82

私はこれが機能していると思ったが、結局のところ、私は間違っていた(そして間違った答えを受け入れた)。私は、自分にとって都合の良い新しい方法でロジックを再編集し、上記のコメントでジェイコブロバーツの懸念に答えています。これが私の新しいソリューションです。

テンプレート:

<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桁に制限する通貨マスクパイプを作成しました。

7
Dallas Caley

カスタムコントロールを作成するつもりでしたが、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
  }
}
2
Drew

ここでの他の答えは私には適切に機能しませんでしたが、私は非常にうまく機能する方法を見つけました。リアクティブフォームvalueChangesサブスクリプション内でパイプ変換を適用する必要がありますが、再帰ループを作成しないように、イベントを発行しないでください。

this.formGroup.valueChanges.subscribe(form => {
  if (form.amount) {
    this.formGroup.patchValue({
      amount: this.currencyMask.transform(form.amount)
    }, {
      emitEvent: false
    });
  }
});

また、これは、パイプがそこにあるものを「アンフォーマット」することを必要とします。これは通常、パイプの変換関数内で次のような単純なものです

value = value.replace(/\$+/g, '');
0
inorganik

パイプコードを知らなくても、フォームを構築している場所のためにエラーがスローされる可能性があります。

入力が解決された後、Angularの変更検出フックを使用してその値を設定してみてください。

export class MyComponent implements OnInit {
  myForm: FormGroup;

  constructor(private builder: FormBuilder) { }    

  ngOnInit() {
    this.myForm = builder.group({
      amount: ['', Validators.required]
    });
  }

}
0
joh04667