Call Angular $filter using expression string - angularjs

I am trying to call $filter using a filter expression via a filter string I've stored as metadata. For instance my filter string might look like this:
var filterForMyValue = "number : 2 | someOtherFilter";
This would be no problem I was invoking a similar hardcoded filter via markup:
<span>{{ somevalue | number : 2 | someOtherFilter</span>
However I want to programmatically apply this filter. Doing something like this $filter(myFilterString)(valueToFilter) doesn't work since you can't include the filter parameters or multiple chained filters as a single string. It will only allow you to pass the filter name and then the parameters must be passed separately which I don't want since this is a generic method that needs to apply any filter string to a value. I thought $parse might be of some use but I was unable to find any examples of how it might be combined with $filter to achieve this.

This is in the lines of bluetoft comment, maybe one step closer using parser service instead of compile.
http://plnkr.co/edit/ZyFZ42XAuuE4tRZbxKqn?p=preview
$scope.item = 1.123;
//Option 1
var filterFn = $parse('item |number:2|myFilter');
console.log(filterFn($scope));
//Option 2
$scope.item = $filter('number')($scope.item, 2);
$scope.item = $filter('myFilter')($scope.item);
console.log($scope.item);
There are 2 options. Option1 uses parser and option may be you can create custom filter chain service (this is what internally the parser service would do but this is more in terms of your expected pattern of passing input/filters).

I'm not aware of a way to do EXACTLY what you're asking, but I think the $compile service may come of some help going about this in another manor.
var app = angular.module('myApp',[]);
app.filter('myFilter',function(){
return function(val){
return val * 2;
}
});
app.directive('myDirective',function($compile){
return {
restrict:'E',
link: function(scope,element){
scope.filterForMyValue = "number : 2 | myFilter";
scope.item = 1.123;
var format = '<span>{{item |' + scope.filterForMyValue + '}}</span>';
var compiled = $compile(format)(scope);
element.append(compiled);
}
}
});
Plunkr

Related

Factory without inject the dependency

This is my code that currently works:
angular.module('myApp')
.controller('myCtrl', function (DocumentTypeManagerPdf, DocumentTypeManagerVideo) {
$scope.showPreview = function(document){
var previewModule = eval('DocumentTypeManager' + document.clientModule);
previewModule.show(document);
};
});
but... two things I would avoid:
Eval is evil
I am forced to inject every DocumentTypeManagerXYZ that I'll implement
In there a better solution tu use a Factory dynamically?
I think you should go with a factory pattern.
One service DocumentTypeManagerFactory
With one method like
var myDocumentTypeManager = DocumentTypeManagerFactory.instanciateWithType(document.clientModule);
myDocumentTypeManager.show(document);
Your controller will only inject one service (and the DocumentTypeManagerFactory should inject all)
In your DocumentTypeManagerFactory you should make a switch or if/else to avoid eval.
I think you can use arguments in the function. inJS every function has a variable named arguments which is a array of given parameters.
But I am not sure how your DocumentTypeManagerXYZ objects are structured. So just type debugger; beginning of your controller function and check arguments data by console then you can take a correct action.
the below one is the first one comes to my mind;
var previewModule;
for(var i = 0, len=arguments.lengh; i <len; i++) {
if (arguments[i].constructure.name === 'DocumentTypeManager' + document.clientModule) {
previewModule = arguments[i];
break;
}
}
this will be your basic approach.
as this is an angular application you can user $injector.get("moduleName")
for example;
var previewModule = $injector.get("'DocumentTypeManager' + document.clientModule");
please see $injector

angular: programmatically format a number using the $locale information

I have included the corresponding locale file and it works fine. In any template I can do things like:
{{ value | number: 2}}
and it correctly formats the number according to the locale info.
Now I need to use the same locale info from javascript code in a controller to build a string.
I'm using a javascript component (a d3 graph to be precise) and I want to build strings to attache to it, so the template system is useless for this, but I'd like to take the locale configuration of numbers and dates from it.
So I'd nee something like this pseudocode:
var formattedValue = $local.format(value, { 'number': 2 });
Or something like that
Anyone knows how can I achieve that?
Try this :
var formattedValue = $filter('number')(value,2);
Working : http://plnkr.co/edit/aC4p95y52YZyoUEdQVzo?p=preview
We can achieve this by implementing a filter.
var app = angular.module('app', []);
app.filter('yourFilter', function(){
return function(string){
// build the string whatever you are trying to achieve
return newString; // return the string you achieved
}
});
for reference, http://blog.trifork.com/2014/04/10/internationalization-with-angularjs/
I could inject the filter like this:
presuApp.run(function ($rootScope, numberFilter) {
var formattedValue = numberFilter(value, 2);
[...]
It's just the name of the filter followed by th 'Filter' suffix.

How to pass a lambda expression to an AngularJS directive

I'm trying to create a set of AngularJS directives that will process an array of objects and perform specific operations using either the objects themselves or perhaps a property or sub-property of the each instance.
For example, if the array contains strings, one such directive might render a comma-separated list of those strings. I anticipate using such a directive like this:
<csv-list items="myArray" />
However, as stated above, I want the implementation to be flexible enough to pass an array of objects to the directive, whereby the directive can be instructed to act on a specific property or sub-property of each instance. If I could pass a lambda expression to the directive, I would imagine using it something like this:
<csv-list items="myArray" member="element => element.name" />
I guess there's a recommended AngularJS pattern to solve such problems, but I am quite new to AngularJS, so I haven't found it yet. Any suggestions would be appreciated.
Thanks,
Tim
There are several ways to do this, Using the $parse service may be the easiest
var parser = $parse("name");
var element = {name:"thingA"};
var x = parser(element);
console.log(x); // "thingA"
Parse has been optimized to act quickly in these scenarios (single property look-ups). You can keep the same "parser" function around and invoke it on each element.
You could also split on the '.' and do the simple look-up yourself (reading in 'member' to your directive as a string), in simple form:
var paths = myPath.split('.');
var val = myObj;
for(var i = 0; i < paths.length; i++){
val = val[paths[i]];
}
return val;
There are also various linq-like libraries that support lambda expressions as strings (linqjs, fromjs). If you've gotta have a fat arrow function.
Your directive can look at other attributes, so you could add a property-name attribute and have your directive manually check that property. To be fancy you could use $parse like ng-repeat does to parse an expression.
<csv-list items="element in myArray" member="element.name">
Another way would be to create a 'property' filter that takes an array of objects and returns an array of property values from that object that you could use like so:
<csv-list items="myArray|property:name">
Here's what you're asking for syntactically (Show me the code - plunkr):
member="'e' |lambda: 'e.name'"
You can do this with something like (I wrote this just for the question, what I do in my apps is outlined below)
app.filter('lambda', [
'$parse',
function ($parse) {
return function (lambdaArgs, lambdaExpression, scope) {
var parsed = $parse(lambdaExpression);
var split = lambdaArgs.split(',');
var result = function () {
var args = {};
angular.extend(args, scope || {});
for (var i = 0; i < arguments.length && i < split.length; i++) {
args[split[i]] = arguments[i];
}
return parsed(args);
};
return result;
}
}
]);
Advanced usage:
(x, y, z) => x * y * z + a // a is defined on scope
'x,y,z' |lambda: 'x * y * z + a':this
The :this will pass the scope along to the lambda so it can see variables there, too. You could also pass in an aliased controller if you prefer. Note that you can also stick filters inside the first argument to the lambda filter, like:
('x'|lambda:'x | currency')(123.45) // $123.45 assuming en-US locale
HOWEVER I have thus far avoided a lambda filter in my apps by the following:
The first approach I've taken to deal with that is to use lodash-like filters.
So if I have an array of objects and your case and I want to do names, I might do:
myArray | pluck:'name'
Where pluck is a filter that looks like:
angular.module('...', [
]).filter('pluck', [
function () {
return function (collection, property) {
if (collection === undefined) {
return;
}
try {
return _.pluck(collection, property);
} catch (e) {
}
}
}
]);
I've implemented contains, every, first, keys, last, pluck, range (used like [] | range:6 for [0,1,2,3,4,5]), some, and values. You can do a lot with just those by chaining them. In all instances. I literally just wrapped the lodash library.
The second approach I've taken is to define functions inside a controller, expose them on the scope.
So in your example I'd have my controller do something like:
$scope.selectName = function (item) { return item.name };
And then have the directive accept an expression - & - and pass selectName to the expression and call the expression as a function in the directive. This is probably what the Angular team would recommend, since in-lining in the view is not easily unit-test-able (which is probably why they didn't implement lambdas). (I don't really like this, though, as sometimes (like in your case) it's strictly a presentation-thing - not a functionality-thing and should be tested in an E2E/Boundary test, not a unit test. I disagree that every little thing should be unit tested as that often times results in architecture that is (overly) complicated (imho), and E2E tests will catch the same thing. So I do not recommend this route, personally, though again I think the team would.)
3.
The third approach I've taken would be to have the directive in question accept a property-name as a string. I have an orderableList directive that does just that.

Inject $scope into filter (AngularJS)

What I'm trying to do:
Im using a ng-repeat directive on an associative array, which I want to filter. The build in angular filter isn't working on associative arrays/hashes. Because of that, I'm trying to write my own filter (inspired by http://angularjs.de/artikel/angularjs-ng-repeat; it's in german, but the important code is below the headline "Beispiel 2: Filtern von Hashes mittels eigenem Filter" which means "Example 2: Filter Hashes with your own filter").
The ng-repeat:
tr ng-repeat="(key, machine) in machines | customFilter | orderObjectBy:'name':false"
note: orderObjectBy is also a customFilter.
The filter:
toolApp.filter('customFilter', [function(){
return function(machines){
var result = {};
angular.forEach(machines, function(machine, key){
/*if(machine.name.contains(filter)){
result[key] = machine;
}*/
//if(machine.name.contains("a")){
result[key] = machine;
//}
});
return result;
};
}]);
The filter works with a hardcoded "filter-criterium". But I want to have a textbox above the list, in which the user can enter the "filter-criterium". My Problem is, that i don't know how to insert this value into the filter. I don't find much on the Internet and what i found, didn't work for me.
Has anyone an idea how to insert the value oder maybe a different approach.
Huge thanks in advance for every answer! If you need more information, let me know! I tried to keep it short :>.
Greetings from Germany
you may have parameters to your custom filter.
suppose you have an input field:
<input ng-model="myParam">
and an ng-repeat like yours:
<tr ng-repeat="(key, machine) in machines | customFilter:myParam | orderObjectBy:'name':false" >
then you may access this parameter in your custom filter:
toolApp.filter('customFilter', [function(){
return function(machines, myParam){
var result = {};
angular.forEach(machines, function(machine, key){
if(machine.name.contains(myParam)){
result[key] = machine;
}
});
return result;
};
}]);
note:
Pay attention that the myParam value is initialized with an "undefined" value. You have to set a default value in the controller or handle it in the customFilter. Otherwise you will only see your list, after you started typing.

How to extend or override existing filters in angularjs?

Is it possible to extend existing "standard" filters (date, number, lowercase etc)?
In my case I need to parse date from YYYYMMDDhhmmss format so I'd like to extend (or override) date filter instead of writing my own.
I prefer to implement the decorator pattern, which is very easy in AngularJS.
If we take #pkozlowski.opensource example, we can change it to something like:
myApp.config(['$provide', function($provide) {
$provide.decorator('dateFilter', ['$delegate', function($delegate) {
var srcFilter = $delegate;
var extendsFilter = function() {
var res = srcFilter.apply(this, arguments);
return arguments[2] ? res + arguments[2] : res;
}
return extendsFilter;
}])
}])
And then in your views, you can use both.. the standard output and the extended behavior. with the same filter
<p>Standard output : {{ now | date:'yyyyMMddhhmmss' }}</p>
<p>External behavior : {{ now | date:'yyyyMMddhhmmss': ' My suffix' }}</p>
Here is a working fiddle illustrating both techniques:
http://jsfiddle.net/ar8m/9dg0hLho/
I'm not sure if I understand your question correctly, but if you would like to extend functionality of existing filters you could create a new filter that decorates an existing one. Example:
myApp.filter('customDate', function($filter) {
var standardDateFilterFn = $filter('date');
return function(dateToFormat) {
return 'prefix ' + standardDateFilterFn(dateToFormat, 'yyyyMMddhhmmss');
};
});
and then, in your template:
{{now | customDate}}
Having said the above, if you simply want to format a date according to a given format this can be done with the existing date filter:
{{now | date:'yyyyMMddhhmmss'}}
Here is the working jsFiddle illustrating both techniques: http://jsfiddle.net/pkozlowski_opensource/zVdJd/2/
Please note that if a format is not specified AngularJS will assume that this is 'medium' format (the exact format depends on a locale). Check http://docs.angularjs.org/api/ng.filter:date for more.
The last remark: I'm a bit confused about the 'parse from' part of your question. The thing is that filters are used to parse an object (date in this case) to string and not vice verse. If you are after parsing strings (from an input) representing dates you would have to look into NgModelController#$parsers (check the "Custom Validation" part in http://docs.angularjs.org/guide/forms).

Resources