如何在Angular 5被动表单输入中使用管道

如何在Angular 5被动表单输入中使用管道,angular,angular-reactive-forms,angular-pipe,Angular,Angular Reactive Forms,Angular Pipe,我试图弄清楚如何在被动表单中使用管道,以便将输入强制转换为货币格式。我已经为此创建了自己的管道,并在代码的其他区域进行了测试,所以我知道它是一个简单的管道。我的管道名称是“udpCurrency” 关于堆栈溢出,我能找到的最接近的答案是:然而,这在我的情况下不起作用,我怀疑这与我的表单是被动的这一事实有关 以下是所有相关代码: 模板 <form [formGroup]="myForm" #f="ngForm"> <input class="form-control col-

我试图弄清楚如何在被动表单中使用管道,以便将输入强制转换为货币格式。我已经为此创建了自己的管道,并在代码的其他区域进行了测试,所以我知道它是一个简单的管道。我的管道名称是“udpCurrency”

关于堆栈溢出,我能找到的最接近的答案是:然而,这在我的情况下不起作用,我怀疑这与我的表单是被动的这一事实有关

以下是所有相关代码:

模板

<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>
错误:

ERROR Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'undefined: '. Current value: 'undefined: undefined'
<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;
    }
}

在不知道管道代码的情况下,它很可能会因为构建表单的位置而抛出错误

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

export class MyComponent implements OnInit {
  myForm: FormGroup;

  constructor(private builder: FormBuilder) { }    

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

}

这就是混合使用模板驱动表单和反应式表单时可能发生的情况。你有两个绑定在一起互相攻击。选择模板驱动或反应式表单。如果你想走反应路线,你可以为你的管道使用
[value]

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


我原以为我能做到这一点,但事实证明,我错了(并接受了错误的答案)。我只是用一种更适合我的新方式重新定义了我的逻辑,并在上面的评论中回答了雅各布·罗伯茨的担忧。以下是我的新解决方案:

模板:

ERROR Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'undefined: '. Current value: 'undefined: undefined'
<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;
    }
}
管道:

ERROR Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'undefined: '. Current value: 'undefined: undefined'
<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;
    }
}

现在我在这里学到了一些东西。一种是Jacob所说的是真的,另一种方法只在最初起作用,但在值发生变化时不会更新。另一个需要注意的非常重要的事情是,与视图管道相比,我需要一种完全不同类型的管道作为遮罩。例如,视图中的管道可能采用该值“100”并将其转换为“$100.00”,但是您不希望在键入该值时发生转换,您只希望在键入后发生转换。出于这个原因,我创建了我的货币掩码管道,它只删除非数字数字,并将小数限制在两位。

这里的其他答案对我来说并不合适,但我找到了一种非常有效的方法。您需要在被动表单valueChanges订阅中应用管道转换,但不要发出事件,这样它就不会创建递归循环:

this.formGroup.valueChanges.subscribe(form => {
  if (form.amount) {
    this.formGroup.patchValue({
      amount: this.currencyMask.transform(form.amount)
    }, {
      emitEvent: false
    });
  }
});
这还要求管道“取消格式化”任何存在的内容,这通常与管道的变换函数中的类似内容一样简单:

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

我打算编写一个自定义控件,但发现通过
ngModelChange
重写FormControl类中的“onChange”更容易。
emitViewToModelChange:false
在更新逻辑期间非常重要,以避免重复发生更改事件。所有到货币的管道都发生在组件中,您不必担心控制台错误

<input matInput placeholder="Amount" 
  (ngModelChange)="onChange($event)" formControlName="amount" />

@组件({
提供者:[CurrencyPipe]
})
导出类MyComponent{
表单=新表单组({
金额:新FormControl(“”,{validators:validators.required,updateOn:'blur'})
});
构造函数(专用CurrencyPipe:CurrencyPipe){}
onChange(值:字符串){
const ctrl=this.form.get('amount')作为FormControl;
if(isNaN(value.charAt(0))){
const val=强制EnumberProperty(value.slice(1,value.length));
ctrl.setValue(this.currPipe.transform(val),{emitEvent:false,emitViewToModelChange:false});
}否则{
ctrl.setValue(this.currPipe.transform(value),{emitEvent:false,emitViewToModelChange:false});
}
}
onSubmit(){
const rawValue=this.form.get('amount').value;
//如果在保存数据时需要从输入值中删除“$”
const value=rawValue.slice(1,rawValue.length);
//用你的价值去做任何你需要做的事
}
}

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

添加formControlName不会始终正常工作,因此需要小心。

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

这可能不会给出预期的结果。

仅使用此代码

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


表达式错误是来自管道还是组件?组件html。特别是具有以下内容的行“[ngModel]”f1.value.amount | udpCurrency“我的表单构建是在构造函数中进行的,但是它依赖于从http请求检索的其他数据,所以即使它不在ngOnInit钩子中,我相信它是在ngOnInit之后调用的。作为一个测试,我把大楼移到了恩戈尼尼特的范围内,我仍然认为这是非常有用的,谢谢。我发现很多关于这个主题的帖子都过于复杂了,但这很管用。这在最初是管用的,但是当你编辑这个值时,管道不会重新应用于模糊。“这是必须明确处理的事情吗?”@JacobRoberts抱歉,我有几个月没有看到你的评论了。我终于意识到你说的是对的。下面我有一个新的答案,如果您仍然有相同的问题,它可能会有所帮助。这并不像预期的那样有效,因为表单值更改在管道转换之前触发。显示的值看起来会正确,但表单值会不同。这不起作用,因为表单在视图上更新模型后,货币管道会再次更新模型,并最终尝试通过管道传递字符串值,这就是我得到错误的地方。请查看下面我的解决方案以避免出现这种情况。请详细说明您的答案,而不是仅仅粘贴在代码中,让提问者有机会了解您在做什么。