Using Pipes within ngModel on INPUT Elements in Angular
Asked Answered
P

7

196

I've an HTML INPUT field.

<input 
    [(ngModel)]="item.value" 
    name="inputField" 
    type="text" 
/>

and I want to format its value and use an existing pipe:

.... 
[(ngModel)]="item.value | useMyPipeToFormatThatValue" 
....

and get the error message:

Cannot have a pipe in an action expression

How can I use pipes in this context?

Punctuation answered 22/9, 2016 at 15:17 Comment(0)
F
277

You can't use Template expression operators(pipe, save navigator) within template statement:

(ngModelChange)="Template statements"

(ngModelChange)="item.value | useMyPipeToFormatThatValue=$event"

https://angular.io/guide/template-syntax#template-statements

Like template expressions, template statements use a language that looks like JavaScript. The template statement parser differs from the template expression parser and specifically supports both basic assignment (=) and chaining expressions (with ; or ,).

However, certain JavaScript syntax is not allowed:

  • new
  • increment and decrement operators, ++ and --
  • operator assignment, such as += and -=
  • the bitwise operators | and &
  • the template expression operators

So you should write it as follows:

<input [ngModel]="item.value | useMyPipeToFormatThatValue" 
      (ngModelChange)="item.value=$event" name="inputField" type="text" />

Plunker Example

Flieger answered 22/9, 2016 at 15:31 Comment(13)
Can someone explain why it has to be split out like this? I am trying to bind a date to an input with type date: [(ngModel)]="model.endDate | date:'y-MM-dd'" and the pipe will not work. However, if I do away with the banana syntax and use the split out syntax above it works fine.Franklinfranklinite
Did this really work? it didn't work for me. it says Cannot have a pipe in an action expressionTritanopia
This worked for me to! @BlakeRivell "[]" binds the property one-way from data source to view target at that point you can change how it is displayed with a pipe. When using the "()" binding it is the other way around changing the format would be useless here. So I guess thats why the banana's in a box "[()]" don't work with a pipe and splitting them is the way to go. You can read more about it here: angular.io/docs/ts/latest/guide/…Potentilla
Do you know if this is the recommended approach? or should you rather apply the @Pipe on the controller?Busybody
Beware that in the example the pipe only works to one direction. Let's say item.value is a number, and you use DatePipe to convert it into a date string. When the date is edited, the $event will also be a date string and will not fit back into item.value You have to reverse what the pipe did in your (ngModelChange) expression - i.e. turn the date string back to a number.Tomtoma
@Tomtoma "You have to reverse what the pipe did in your (ngModelChange) expression - i.e. turn the date string back to a number." - How do I do this with ngModelChange?Muezzin
@Muezzin (ngModelChange)="updateItemValue($event)", then create an updateItemValue(date: string) method and inside it item.value = someConversionFunction(date); Now if you're asking what should you use as the conversion function, I don't know. Maybe Date.parse() might work.Tomtoma
@Flieger what if I want to append a string to the value my ngModel interpolates, for example, if I want to add "The value is" to the expression: i.e. [ngModel]=" ' The value is ' + item.value | useMyPipeToFormatThatValue" (but this is giving me errors)Bradeord
@Bradeord Can you reproduce it on stackblitz?Flieger
@Flieger thanks but I've figured it out. Solved it by doing ngModel=" The value is {{item.value | useMyPipeToFormatThatValue}}"Bradeord
Drawback - Write fast and you're gonna get a long unexpected string in the inputAppetency
I would like to unvote, this answer is only correct if the input is not changeable by the user. It's impossible to change the value of the input field by the user because the pipe will format what the user is writing after each input because of (ngModelChange) .Falciform
(ngModelChange) will fire after each change in input, that is after every letter change. I don't think that's desirable. (ngModelChange)="modelChangeFn($event) is great when using in conjunction with Drop down select or some custom input component. Pipe also prevent any value tare not Pipe-compatible from updating.Erminois
C
131
<input [ngModel]="item.value | useMyPipeToFormatThatValue" 
      (ngModelChange)="item.value=$event" name="inputField" type="text" />

The solution here is to split the binding into a one-way binding and an event binding - which the syntax [(ngModel)] actually encompasses. [] is one-way binding syntax and () is event binding syntax. When used together - [()] Angular recognizes this as shorthand and wires up a two-way binding in the form of a one-way binding and an event binding to a component object value.

The reason you cannot use [()] with a pipe is that pipes work only with one-way bindings. Therefore you must split out the pipe to only operate on the one-way binding and handle the event separately.

See Angular Template Syntax for more info.

Cynical answered 6/2, 2017 at 7:45 Comment(1)
How do I add the condition expression like | number:'3.2-5' ?Muezzin
H
29
<input [ngModel]="item.value | currency" (ngModelChange)="item.value=$event"
name="name" type="text" />

I would like to add one more point to the accepted answer.

If the type of your input control is not text the pipe will not work.

Keep it in mind and save your time.

Hardener answered 21/8, 2018 at 6:56 Comment(2)
kindly consider adding more information in your answerHolophytic
check ngx-locale-mask angular library which I made to mask input box for a particular currency based on angular localeHardener
T
7

I tried the solutions above yet the value that goes to the model were the formatted value then returning and giving me currencyPipe errors. So i had to

  [ngModel]="transfer.amount | currency:'USD':true"
                                   (blur)="addToAmount($event.target.value)"
                                   (keypress)="validateOnlyNumbers($event)"

And on the function of addToAmount -> change on blur cause the ngModelChange was giving me cursor issues.

removeCurrencyPipeFormat(formatedNumber){
    return formatedNumber.replace(/[$,]/g,"")
  }

And removing the other non numeric values.

validateOnlyNumbers(evt) {
  var theEvent = evt || window.event;
  var key = theEvent.keyCode || theEvent.which;
  key = String.fromCharCode( key );
  var regex = /[0-9]|\./;
  if( !regex.test(key) ) {
    theEvent.returnValue = false;
    if(theEvent.preventDefault) theEvent.preventDefault();
  }
Tendance answered 25/8, 2017 at 19:14 Comment(1)
we also tried the chosen answer for Percent pipe and wrote a method like toDecimal() for the (ngModelChange), and the 2 methods chase each other. so you can't type more than 1 digit. surprising that it's upvoted so muchR
S
2

you must use [ngModel] instead of two way model binding with [(ngModel)]. then use manual change event with (ngModelChange). this is public rule for all two way input in components.

because pipe on event emitter is wrong.

Santamaria answered 9/11, 2019 at 15:23 Comment(0)
P
1

My Solution is given below here searchDetail is an object..

<p-calendar  [ngModel]="searchDetail.queryDate | date:'MM/dd/yyyy'"  (ngModelChange)="searchDetail.queryDate=$event" [showIcon]="true" required name="queryDate" placeholder="Enter the Query Date"></p-calendar>

<input id="float-input" type="text" size="30" pInputText [ngModel]="searchDetail.systems | json"  (ngModelChange)="searchDetail.systems=$event" required='true' name="systems"
            placeholder="Enter the Systems">
Plenum answered 4/1, 2018 at 9:54 Comment(0)
U
0

because of two way binding, To prevent error of:

ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was 
checked.

you can call a function to change model like this:

<input 
  [ngModel]="item.value" 
  (ngModelChange)="getNewValue($event)" 
  name="inputField" 
  type="text" 
/>

import { UseMyPipeToFormatThatValuePipe } from './path';

  //...
  constructor(
    private useMyPipeToFormatThatValue: UseMyPipeToFormatThatValuePipe,
  )
  //....
  getNewValue(ev: any): any {
    item.value= this.useMyPipeToFormatThatValue.transform(ev);
  }  

it'll be good if there is a better solution to prevent this error.

Unassuming answered 3/2, 2020 at 13:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.