Estoy tratando de descubrir cómo usar una tubería dentro de una forma reactiva para que la entrada se fuerce en un formato de moneda. Ya creé mi propia tubería para esto, la cual probé en otras áreas del código, así que sé que funciona como una tubería simple. Mi nombre de tubería es 'udpCurrency'
La respuesta más cercana que pude encontrar en el desbordamiento de pila fue esta: Uso de tuberías dentro de ngModel en elementos INPUT en Angular2-View Sin embargo, esto no funciona en mi caso y sospecho que tiene algo que ver con el hecho de que mi forma es reactiva
Aquí está todo el código relevante:
La plantilla
<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>El componente
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] }); } }El error:
ERROR Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'undefined: '. Current value: 'undefined: undefined'Esto es lo que puede suceder cuando mezclas formularios basados en plantillas y formularios reactivos. Tienes dos ataduras luchando entre sí. Elija una forma basada en plantillas o reactiva. Si desea seguir la ruta reactiva, puede usar [value] para su canalización...
Tenga en cuenta que esta tubería es más bien solo para mostrar la salida deseada en la vista.
<form [formGroup]="myForm"> <input [value]="myForm.get('amount').value | udpCurrency" formControlName="amount" placeholder="Amount"> </form>Pensé que esto funcionaba, pero resultó que estaba equivocado (y acepté una respuesta incorrecta). Acabo de rehacer mi lógica de una manera nueva que funciona mejor para mí y responde a la preocupación de Jacob Roberts en los comentarios anteriores. Aquí está mi nueva solución:
La plantilla:
<form [formGroup]="myForm"> <input formControlName="amount" placeholder="Amount"> </form>El componente:
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}); } } }); } }El tubo:
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; } }Ahora hay varias cosas que aprendí aquí. Una era que lo que dijo Jacob era cierto, la otra forma solo funcionó inicialmente pero no se actualizaría cuando el valor hubiera cambiado. Otra cosa muy importante a tener en cuenta es que necesito un tipo de tubería completamente diferente para una máscara en comparación con una tubería de visualización. Por ejemplo, una canalización en una vista podría tomar este valor "100" y convertirlo en "$100,00". Por esta razón, creé mi tubería de máscara de moneda que simplemente elimina los números no numéricos y restringe el decimal a dos lugares.
Iba a escribir un control personalizado, pero descubrí que anular el "onChange" de la clase FormControl a través de ngModelChange era más fácil. emitViewToModelChange: false es fundamental durante la lógica de actualización para evitar bucles recurrentes de eventos de cambio. Toda la canalización a la moneda ocurre en el componente y no tiene que preocuparse por los errores de la consola.
<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 } }