Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

ng-click and filter don't work together #4168

Closed
juhoz1024 opened this issue Sep 26, 2013 · 12 comments
Closed

ng-click and filter don't work together #4168

juhoz1024 opened this issue Sep 26, 2013 · 12 comments

Comments

@juhoz1024
Copy link

Live example: http://jsfiddle.net/joshdmiller/HB7LU/

Error on the second button below. Also tested with v1.2.0-rc2.

Error: Syntax Error: Token '|' is unexpected, expecting [)] at column 13 of the expression [salute(name | uppercase)] starting at [| uppercase)].
throwError@http://code.angularjs.org/angular-1.0.1.js:5833
consume@http://code.angularjs.org/angular-1.0.1.js:5868
_functionCall@http://code.angularjs.org/angular-1.0.1.js:6118
primary@http://code.angularjs.org/angular-1.0.1.js:6054
unary@http://code.angularjs.org/angular-1.0.1.js:6029
multiplicative@http://code.angularjs.org/angular-1.0.1.js:6012
additive@http://code.angularjs.org/angular-1.0.1.js:6003
relational@http://code.angularjs.org/angular-1.0.1.js:5994
equality@http://code.angularjs.org/angular-1.0.1.js:5985
logicalAND@http://code.angularjs.org/angular-1.0.1.js:5976
logicalOR@http://code.angularjs.org/angular-1.0.1.js:5964
_assignment@http://code.angularjs.org/angular-1.0.1.js:5946
expression@http://code.angularjs.org/angular-1.0.1.js:5942
_filterChain@http://code.angularjs.org/angular-1.0.1.js:5908
statements@http://code.angularjs.org/angular-1.0.1.js:5888
parser@http://code.angularjs.org/angular-1.0.1.js:5821
@http://code.angularjs.org/angular-1.0.1.js:6387
ngEventDirectives[directiveName]</<@http://code.angularjs.org/angular-1.0.1.js:12493
nodeLinkFn@http://code.angularjs.org/angular-1.0.1.js:4224
compositeLinkFn@http://code.angularjs.org/angular-1.0.1.js:3838
nodeLinkFn@http://code.angularjs.org/angular-1.0.1.js:4217
compositeLinkFn@http://code.angularjs.org/angular-1.0.1.js:3838
compositeLinkFn@http://code.angularjs.org/angular-1.0.1.js:3841
compile/<@http://code.angularjs.org/angular-1.0.1.js:3750
bootstrap/</<@http://code.angularjs.org/angular-1.0.1.js:932
Scope.prototype.$eval@http://code.angularjs.org/angular-1.0.1.js:7770
Scope.prototype.$apply@http://code.angularjs.org/angular-1.0.1.js:7850
bootstrap/<@http://code.angularjs.org/angular-1.0.1.js:930
invoke@http://code.angularjs.org/angular-1.0.1.js:2788
bootstrap@http://code.angularjs.org/angular-1.0.1.js:929
angularInit@http://code.angularjs.org/angular-1.0.1.js:904
@http://code.angularjs.org/angular-1.0.1.js:14323
trigger@http://code.angularjs.org/angular-1.0.1.js:1695
createEventHandler/eventHandler/<@http://code.angularjs.org/angular-1.0.1.js:1930
forEach@http://code.angularjs.org/angular-1.0.1.js:110
createEventHandler/eventHandler@http://code.angularjs.org/angular-1.0.1.js:1929
<input value="Salute! (doesn't)" ng-click="salute(name | uppercase)" type="button">
@juhoz1024
Copy link
Author

Link that works:
http://jsfiddle.net/HB7LU/530/

@petebacondarwin
Copy link
Contributor

The problem is that the parser doesn't expect filtered expressions to appear directly in the parameter list.
We may be able to fix this by changing https://github.com/angular/angular.js/blob/master/src/ng/parse.js#L687 to

        argsFn.push(filterChain());

But it might break other stuff...

@thebigredgeek
Copy link
Contributor

@juhoz1024 why not simply sterilize the input within the controller? Why do you want to be able to apply a filter like that?

esarbanis added a commit to esarbanis/angular.js that referenced this issue Sep 27, 2013
Added a condition to allow the parser to understand whether the parameter is a filter

Closes angular#4168
@juhoz1024
Copy link
Author

Hello,

My use case is a bit strange. I have a mapping of number to string:

$scope.mapping = {
    0: 'zero',
    1: 'one',
    2: 'two',
};

and I generate a Bootstrap dropdown with one menu item for each (number, string) pair:

<div class="btn-group">
  <div class="dropdown">
    <a class="btn btn-default dropdown-toggle" data-toggle="dropdown" href="">Set value <span class="caret"></span></a>
    <ul class="dropdown-menu">
      <li ng-repeat="(value, name) in mapping"><a href="" ng-click="setValue(value)">{{name}}</a></li>
    </ul>
  </div>
</div>

The problem is that object keys are coerced to string (I know it has nothing to do with AngularJS), so setValue would be called with "0", "1", and "2".

To solve the problem, I created an int filter:

.filter('int', function() {
  return function(string) {
    return parseInt(string);
  };  
})  

and I'm calling setValue like so setValue((value | int)).

Now I know that a filter that returns a number isn't philosophically correct, that filters should only be used to stringify models for display in templates, but a filter seemed like a more elegant solution than using parseInt in setValue, becauseI also have a select directive with options from the same mapping:

<select class="form-control" ng-model="my.model.value" ng-options="value | int as name for (value, name) in mapping">
  <option value=""></option>
</select>

and as you can see, the comprehension permits filter chains, not just plain expressions. Really nice.

And another thing, I've noticed that expression is parsed as an assignment:
https://github.com/angular/angular.js/blob/master/src/ng/parse.js#L490
so the following function call expression myFunction(myVar = 3) would also be valid. I don't know about you, but an assignment expression as a function argument seems "stranger" to me than a filter expression.

I also found filterChain in primary, which allows me to do setValue((value | int)):
https://github.com/angular/angular.js/blob/master/src/ng/parse.js#L601
so you can call a function with a filter expression if you really want to, but it's ugly.

From my experience with top-down recursive look-ahead parsers, no other stuff would break with your proposed fix. I'm not a hundred percent sure, but I don't think the grammar would allow for any ambiguities that would break previously correct expressions.

So, to sum it up. I know that my use case is strange, but I've shown that there are other "strange" use cases that are already possible, and I think there's no real danger of breaking existing code. So, if it's not too much trouble, please create a pull request with your proposed modification, if not, I could create one myself, but I've just created my github account and I'm pretty sure it would take me significantly more time to do so.

Thanks,

@juhoz1024
Copy link
Author

Thanks @esarbanis.

Regarding your first commit, the parser can already handle a parenthesized filter expression, because primary can be "(" filterChain ")".
The second commit looks ok.

Thank you very much @esarbanis, I haven't really considered that you also have to submit tests, you saved me a lot of work:)

@thebigredgeek, see the example with select in my previous comment. I think the comprehension with the filter expression is just beautiful. Also, as you can see, the parser already allows for ugly corner cases, like assignments in function arguments.

@esarbanis
Copy link
Contributor

@juhoz1024 My first commit was simply ... dump. I hope the second one works for you :)

@petebacondarwin
Copy link
Contributor

Of course a simple workaround would be to put your "filter" function on the scope as:

scope.toInt = function(string) {
  return parseInt(string);
};  

then you can do:

<select class="form-control" ng-model="my.model.value" ng-options="toInt(value) as name for (value, name) in mapping">
  <option value=""></option>
</select>

@juhoz1024
Copy link
Author

@petebacondarwin, actually the comprehension in ng-options does accept a filter expression, so

<select class="form-control" ng-model="my.model.value" ng-options="value | int as name for (value, name) in mapping">
  <option value=""></option>
</select>

is perfectly valid and works as expected.

For setValue, you're right, I could expose the filter through the scope:

$scope.int = $filter('int');

and do setValue(int(value)).

@petebacondarwin
Copy link
Contributor

Right OK, either works in an ngOptions comprehension, but my suggestion
still works for function parameters, and is quite succinct.

On 27 September 2013 14:52, juhoz1024 [email protected] wrote:

@petebacondarwin https://github.com/petebacondarwin, actually the
comprehension in ng-options does accept a filter expression, so

is perfectly valid and works as expected.

For setValue, you're right, I could expose the filter through the scope:

$scope.int = $filter('int');

and do setValue(int(value)).


Reply to this email directly or view it on GitHubhttps://github.com//issues/4168#issuecomment-25246341
.

esarbanis added a commit to esarbanis/angular.js that referenced this issue Jun 25, 2014
Added a condition to allow the parser to understand whether the parameter is a filter

Closes angular#4168
esarbanis added a commit to esarbanis/angular.js that referenced this issue Jun 25, 2014
Added a condition to allow the parser to understand whether the parameter is a filter

Closes angular#4168
esarbanis added a commit to esarbanis/angular.js that referenced this issue Jun 26, 2014
Added a condition to allow the parser to understand whether the parameter is a filter

Closes angular#4168
@esarbanis
Copy link
Contributor

Could someone review the PR, it's been open for ages.

@noer180895
Copy link

i have same problem, i can't get data binding for function in ng-click from ng-repeat, any solution?

@JobaDiniz
Copy link

+1

@Narretz Narretz removed the PRs plz! label Jun 6, 2015
petebacondarwin added a commit to petebacondarwin/angular.js that referenced this issue Jun 6, 2016
petebacondarwin added a commit that referenced this issue Jun 6, 2016
Thanks to @esarbanis for the original PR that got out-dated due to the
big $parse overhaul.

Closes #4175
Closes #4168
Closes #14720
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.