如何在Angular 5反应形式输入中使用管道

时间:2018-03-27 21:17:01

标签: angular angular-reactive-forms angular-pipe

我试图弄清楚如何在反应形式中使用管道,以便将输入强制为货币格式。我已经为此创建了自己的管道,我已经在代码的其他区域进行了测试,因此我知道它可以作为一个简单的管道工作。我的管道名称是' udpCurrency'

我在堆栈溢出时能找到的最接近的答案就是这个:Image但是这不适合我的情况而且我怀疑它与我的表格是被动的事实有关

以下是所有相关代码:

模板

<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'

7 个答案:

答案 0 :(得分:32)

这是混合模板驱动形式和反应形式时可能发生的情况。你有两个绑定相互斗争。选择模板驱动或反应形式。如果您想要进入反应路线,可以使用[value]作为管道......

请注意,此管道仅用于在视图中显示所需的输出。

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

答案 1 :(得分:6)

我以为我可以这样做,但事实证明,我错了(并接受了错误的答案)。我只是以一种对我更有效的新方式重述了逻辑,并回答了雅各布·罗伯茨在上述评论中的关注。这是我的新解决方案:

模板:

<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;
    }
}

现在我在这里学到了几件事。一个是雅各布所说的正确的说法,另一种方法只是最初起作用,但是当值更改时不会更新。需要注意的另一个非常重要的事情是,与视图管道相比,我需要一种完全不同类型的蒙版管道。例如,视图中的管道可能会将此值“ 100”并将其转换为“ $ 100.00”,但是您不希望在键入该值时进行该转换,而只希望在完成键入后进行该转换。因此,我创建了货币屏蔽管道,该管道仅删除了非数字并将十进制限制在两个位置。

答案 2 :(得分:1)

我打算编写一个自定义控件,但是发现通过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
  }
}

答案 3 :(得分:0)

在不知道您的管道代码的情况下,由于您构建该表单的位置,它可能会抛出错误。

尝试使用Angular的更改检测挂钩在输入解析后设置该值:

export class MyComponent implements OnInit {
  myForm: FormGroup;

  constructor(private builder: FormBuilder) { }    

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

}

答案 4 :(得分:0)

这里的其他答案对我来说不合适,但是我找到了一种很好的方法。您需要在响应式表单valueChanges订阅中应用管道变换,但不要发出事件,因此它不会创建递归循环:

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

这还要求您的管道“取消格式化”其中存在的任何内容,通常就像在管道的transform函数中这样简单:

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

答案 5 :(得分:0)

<input type="text" name="name" class="form-control mw-120 text-right"
 [value]="finalRow.controls[i].get('rental').value |currency">

这是最有效的,因为您没有使用formControlName覆盖值。

添加formControlName不能总是正常工作,因此需要注意。

<input type="text" name="name" class="form-control mw-120 text-right"
 [value]="finalRow.controls[i].get('rental').value |currency" formControlName="rental">

这可能无法提供预期的结果。

答案 6 :(得分:0)

仅使用此代码

<input [value]="value | udpCurrency"  name="inputField" type="number" formControlName="amount" />