Silverlight to Angular – 5 (IValueConverter – filter)

IValueConverter is without doubt one of the most used feature (at least in our side of the world) in Silverlight. We have lot of data, which, if we show them as it is to the user, it is completely useless. To make it more meaningful, we use Value Converter to convert and display it to the user. IValueConvert provides a way to add logic to the binding data without changing the data. IValueConverter has two methods, ‘Convert’ to convert the original data that is bound but translate with some logic to give different result that then get rendered in the UI. ‘ConvertBack’ is to take user input and apply some logic to revert it to some other form to store it at the back end. Most of the time, whenever we use IValueConverter, we always end up using the ‘Convert’ and seldom use ‘ConvertBack’, now I am going to use that as an excuse since Angular provides only one way convert, it does not have provision to convert it back. Even though I say, we don’t use ‘convertback’ but it is very useful feature and is used a lot, one good example is ‘Color Picker’. We will try to explore convert back at a later time. For now, we will see what is available in Angular out of the box that we can use.

Problem: Multiply two numbers and display the result with two decimal points.

Silverlight:

Only difference between our previous solution to this is that we have value converter and it is used in the binding to change the incoming data to more meaningful data. Lets look at the view

View:

1 <Grid.Resources> 2 <converter:TwoDigitConverter x:Key="Converter" /> 3 </Grid.Resources> 4 <TextBlock>Number 1</TextBlock> 5 <TextBox Text="{Binding NumberOne, Mode=TwoWay}" Grid.Column="1"></TextBox> 6 <TextBlock Grid.Row="1">Number 2</TextBlock> 7 <TextBox Text="{Binding NumberTwo, Mode=TwoWay}" Grid.Row="1" Grid.Column="1"></TextBox> 8 <Button Grid.Row="1" Grid.Column="2" Command="{Binding Multiply}" CommandParameter="">Multiply</Button> 9 <TextBlock Grid.Row="2" Grid.Column="1" Text="{Binding Total, Converter={StaticResource Converter}}"></TextBlock>

Line (2 – 4) provides us the hook into the value converter to be used in the XAML. Line (9) binds the result, even though the it binds to the result, we are asking the binding to use the associated converter to apply the conversion based on the convert logic and use the result to display in the ‘TextBlock’.

ValueConverter:

1 public class TwoDigitConverter:IValueConverter 2 { 3 4 public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) 5 { 6 var result = value as float?; 7 result = result ?? 0; 8 return string.Format("{0:N2}", result); 9 } 10 11 public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) 12 { 13 throw new NotImplementedException(); 14 } 15 }

When we create Value Converter, it has to implement IValueConverter, which have only two methods that we need to implement. Convert and ConvertBack as we discussed in the beginning. Since Angular only supports one way conversion, we created this sample conveniently to show case only the ‘Convert’ part of ValueConverter.  Convert method does not do anything special, it takes the input number and returns the result with 2 decimal digits.

Our view model exposes the three variables and a command, nothing else.

Angular:

Angular provides filters, a way to format data for display. There are couple of ways we can use filters. You can use filters in line or create custom filters. If you were to create custom filters, you can reuse them in any controllers in a given module, it is like using resource file in Silverlight. One additional benefit of the creating custom filters, you can chain them to pass the input to multiple functions before creating final result. Lets first look at the inline approach to solve the problem and then we will write custom filter.

In-Line formatting:

View:

Since it is inline formatting, the formatting happens in the View code itself.

1 <!DOCTYPE html> 2 <html ng-app> 3 <head> 4 <title></title> 5 <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.1/angular.min.js"></script> 6 <script src="scripts/controller.js"></script> 7 </head> 8 <body> 9 <div ng-controller="controller"> 10 First Number: <input ng-model="NumberOne"/><br/> 11 Second Number: <input ng-model="NumberTwo"/><button ng-click="Multiply()">Multiply</button><br/> 12 Result : {{Result | number:2}} 13 </div> 14 </body> 15 </html>

Everything is standard here except line (12). In line 12, we get the result from the controller and angular will read the result and feed it to the expression in the pipe. In this case we are using number filter out of box to format number. There are few filters available out of box,  take a look at this AngularJS cheat sheet.

Custom formatting:

There are situations, where out of the box filters will not be sufficient and we will need some custom filters to format the data. Angular filters are very powerful on that regard. As I said previously,

  1. Since you can create filters as part of module, these filters are available across all the controllers in the module.
  2. Filters can be chained to create more customizable output data.
  3. Compare to Silverlight, we can pass in as many parameters as you want to the filters.
  4. If the filters are very simple and out of box, use the default filters.
  5. It written properly, you can call filters with only available arguments and rest can assume default values(like optional parameters in c#).

So in our case, even though we could use simple out of the box formatting, we will create a custom filter just for the sake of demonstration. So far in all our examples we left ng-app as empty string but when you want to use filters then you need to create a module and assign the filter to that module. With that in mind, here is the modified view.

1 <!DOCTYPE html> 2 <html ng-app='customFilters'> 3 <head> 4 <title></title> 5 </head> 6 <body> 7 <div ng-controller="controller"> 8 First Number: <input ng-model="NumberOne"/><br/> 9 Second Number: <input ng-model="NumberTwo"/><button ng-click="Multiply()">Multiply</button><br/> 10 Result : {{Result | twoDigits}} 11 </div> 12 <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.1/angular.min.js"></script> 13 <script src="scripts/controller.js"></script> 14 </body> 15 </html>

Line (2) now has the module name. We named it as ‘customFilters’. You will see in the view model, how we create a module and add the filters into the module.

**Note: It important that you put the module name in ng-app, if not none of the custom filters you write will work. Take it from me, I spend hours trying to figure out why my custom filter was not working, finally to find out, I missed the module name in the ng-app. **

Line (10) now feeds the result value to the custom filter ‘twoDigits’, which will do the magic of converting the result to a number with two decimal digits.

Controller:

1 angular.module('customFilters', []). 2 filter('twoDigits', function() { 3 return function(input) { 4 return input.toFixed(2); 5 }; 6 }); 7 8 function controller($scope) { 9 $scope.NumberOne = 0; 10 $scope.NumberTwo = 0; 11 $scope.Result = 0; 12 $scope.Multiply = function () { 13 $scope.Result = $scope.NumberOne * $scope.NumberTwo; 14 }; 15 }

So what is new compare to what we have seen so far?  Line (1-6) creates the module and adds the custom filter to the module.

Line (1) – creates the module named ‘customFilters’. During the angular context generation, angular get hold of ng-app for the first time, it will look for the definition of module in the controller to bootstraps the application. In the module definition, there are two parameters, first one is the name of the module and for sake of simplicity, lets ignore the second parameter ‘[]’ for now.

Line (2) – Please note that, there is a ‘.’ at the end of the line (1), with that we are directly adding a filter called ‘twoDigits’ to the filter factory.

Line (3) – Actual function which takes input and perform some task, in here format number to two decimal digits (line 4) and return the result.

Even though I presented ‘filters’ as way to format data, it is much more than that, it can do what ‘filter’ is supposed to do like filtering. We will look at that in one of the later post. Lets take this example one more level up to show how to pass parameters to the filter.

Problem: Two two number and multiple and set the result precession based on user input.

View:

The view is exactly same as the last one except now, we have one input field to enter number of decimal digits user wants to see in the result.

1 <div ng:app="customFilters"> 2 <div ng-controller="filterCntl"> 3 First Number: <input ng-model='NumberOne'></input><br> 4 Second Number: <input ng-model='NumberTwo'></input><button ng-click='Multiply()'>Multiply</button> 5 Decimals : <input ng-model='Decimals'></input><br> 6 Result: {{ Result | twoDigits: Decimals }} 7 </div> 8 </div>

The interesting part in the view is Line (6), this time, instead of feeding result to twoDigit filter, we are also passing the Decimals value as parameter to the filter.

Controller:

1 angular.module('customFilters', []). 2 filter('twoDigits', function() { 3 return function(input, decimals) { 4 return input.toFixed(decimals); 5 }; 6 }); 7 function filterCntl($scope) { 8 $scope.NumberOne = 0; 9 $scope.NumberTwo = 0; 10 $scope.Result = 0; 11 $scope.Decimals = 2; 12 $scope.Multiply = function () { 13 $scope.Result = $scope.NumberOne * $scope.NumberTwo; 14 }; 15 }

Line (3) of the controller now takes two parameters, first argument will always be the input and second one is number of digits. For sake of simplicity I am not checking isNaN etc., in the ‘twoDigits’ method. Here is the jsFiddle of the solution.

Suppose you may want to pass more than one argument to the filter function, then you will simple concatenate the arguments as

{{ input | filter: arg1 : arg2 : arg2}} 

I found filter to be very powerful and useful.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s