189

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?

7 Answers7

269

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

yurzui
  • 205,937
  • 32
  • 433
  • 399
  • 5
    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. – Blake Rivell Jan 21 '17 at 17:57
  • Did this really work? it didn't work for me. it says Cannot have a pipe in an action expression – NoStressDeveloper Jan 25 '17 at 07:20
  • 5
    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: https://angular.io/docs/ts/latest/guide/template-syntax.html#!#binding-syntax – Mike Bovenlander Jan 30 '17 at 12:18
  • Do you know if this is the recommended approach? or should you rather apply the `@Pipe` on the controller? – zurfyx Mar 15 '17 at 22:56
  • 12
    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. – Tuupertunut May 02 '17 at 14:52
  • @Tuupertunut "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? – Protagonist May 29 '17 at 17:27
  • 3
    @Protagonist `(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. – Tuupertunut May 30 '17 at 19:03
  • @yurzui 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) – CodyBugstein Jan 18 '18 at 18:34
  • @CodyBugstein Can you reproduce it on stackblitz? – yurzui Jan 18 '18 at 18:37
  • @yurzui thanks but I've figured it out. Solved it by doing `ngModel=" The value is {{item.value | useMyPipeToFormatThatValue}}"` – CodyBugstein Jan 18 '18 at 19:01
  • Drawback - Write fast and you're gonna get a long unexpected string in the input – Mayank Kataria Mar 22 '21 at 08:51
  • 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)` . – Patronaut May 05 '22 at 10:23
  • (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. – ISONecroMAn Sep 13 '22 at 00:39
127
<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.

KnowHoper
  • 4,352
  • 3
  • 39
  • 54
28
<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.

Tibin Thomas
  • 742
  • 5
  • 12
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();
  }
cabaji99
  • 1,305
  • 11
  • 9
  • 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 much – Angela P Mar 22 '18 at 14:06
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.

hamid_reza hobab
  • 925
  • 9
  • 21
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">
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.

Mohammad Reza Mrg
  • 1,552
  • 15
  • 30