web-dev-qa-db-fra.com

Comment utiliser des tuyaux dans Angular 5 saisie sous forme réactive

J'essaie de comprendre comment utiliser un canal dans un formulaire réactif pour que l'entrée soit forcée dans un format monétaire. J'ai déjà créé mon propre canal, que j'ai testé dans d'autres parties du code. Je sais donc que cela fonctionne comme un canal. Mon nom de pipe est 'udpCurrency'

La réponse la plus proche que j'ai pu trouver sur le dépassement de pile était celle-ci: tilisation de Pipes dans ngModel sur des éléments INPUT dans Angular2-View Toutefois, cela ne fonctionne pas dans mon cas et je soupçonne que cela a quelque chose à voir avec le fait que ma forme est réactive

Voici tout le code pertinent:

Le modèle

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

Le composant

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

L'erreur:

ERROR Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'undefined: '. Current value: 'undefined: undefined'
31
Dallas Caley

C’est ce qui peut se produire lorsque vous mélangez forme dirigée par modèle et forme réactive. Vous avez deux liaisons qui se combattent. Choisissez un formulaire basé sur un modèle ou réactif. Si vous voulez suivre la voie réactive, vous pouvez utiliser [value] pour votre pipe ...

Notez que ce tuyau sert plutôt à montrer la sortie souhaitée dans la vue.

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

Je pensais que cela fonctionnait, mais il s’est avéré que j’avais tort (et accepté une mauvaise réponse). Je viens de refaire ma logique d'une nouvelle manière qui fonctionne mieux pour moi et répond à l'inquiétude de Jacob Roberts dans les commentaires ci-dessus. Voici ma nouvelle solution:

Le modèle:

<form [formGroup]="myForm">
  <input formControlName="amount" placeholder="Amount">
</form>

Le composant:

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

Le tuyau:

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

Maintenant, il y a plusieurs choses que j'ai apprises ici. L'un était ce que Jacob disait vrai, l'autre sens ne fonctionnait que initialement mais ne se mettait pas à jour lorsque la valeur avait changé. Une autre chose très importante à noter est que j'ai besoin d'un type de tuyau complètement différent pour un masque par rapport à un tuyau en vue. Par exemple, un canal dans une vue peut prendre cette valeur "100" et la convertir en "100,00 $". Toutefois, vous ne voudriez pas que cette conversion se produise lorsque vous saisissez la valeur, vous ne voudriez que cela se produise après la fin de la saisie. Pour cette raison, j'ai créé mon tube de masque de devise qui supprime simplement les nombres non numériques et limite le nombre décimal à deux positions.

7
Dallas Caley

J'allais écrire un contrôle personnalisé, mais j'ai constaté qu'il était plus facile de remplacer "onChange" à partir de la classe FormControl via ngModelChange. Le emitViewToModelChange: false est essentiel lors de la logique de mise à jour pour éviter les événements récurrents en boucle. Toutes les données de conversion sont générées dans le composant et vous n'avez pas à vous soucier des erreurs de la console.

<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

Les autres réponses ici ne fonctionnaient pas correctement pour moi mais j'ai trouvé un moyen qui fonctionne très bien. Vous devez appliquer la transformation de canal dans l'abonnement valueChanges du formulaire réactif, mais n'émettez pas l'événement afin qu'il ne crée pas de boucle récursive:

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

Cela nécessite également que votre pipe "ne formate pas" tout ce qui était là, ce qui est généralement aussi simple que quelque chose comme ceci dans la fonction de transformation de votre pipe:

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

Sans connaître votre code de canal, il est probable que des erreurs soient générées en raison de l'endroit où vous construisez ce formulaire.

Essayez d'utiliser les points d'ancrage de détection de changement d'Angular pour définir cette valeur une fois les entrées résolues:

export class MyComponent implements OnInit {
  myForm: FormGroup;

  constructor(private builder: FormBuilder) { }    

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

}
0
joh04667