The Formatting Dance is Your Chance to Write Some Bugs

I love Javascript- but formatting numbers can be tricky.

Take money for example.  The Angular tracking application we’re working on has a grid to display data- and some of the columns contain dollar totals.  So, in our ngFor loop, we can just output the number in most cases.  You could even tack a dollar sign on the front: ${{data.money}}.  Works great if the amount is 19.99, but there are no trailing zeros allowed in numbers in JS, so if the amount is 20.00, the last two zeros are trimmed, leaving you with 20.

Not a big deal, but not exactly what people expect to see either- making the grid easy to scan and reducing confusion is very important in this app.  Not a problem- Angular comes with pipes.  It even has a built in pipe for currency.  Just pass your data, a ‘pipe’ character, and some config options in your template and you’re good to go:

{{data.money | currency:’USD’:true:’1.2-2′}}

That would tell your template it’s US dollars, show the $ character, and display at least 1 number to the left of the decimal and at least 2 but no more than 2 numbers to the right (this will round your number so be careful!).

Great!  Now it’s displaying as one would expect.  But we also have a search function on the grid.  Type in the input box and see your grid live update- thanks Angular magic!  But with a pipe, the data being displayed doesn’t match the data being searched through.  What was a number (5) is now a string (‘5.00’).  Search for ‘5’ and it works great, but a user may see the rest of the string and try searching for ‘5.00’- which will not work in this case.  No results found and a confused user.

Luckily, the awesome folks at Angular gave us an option for that as well.  Pipes can be used in a component instead of in a template.  Just import the pipe(s) you want to use at the top of your component file, inject them in your controller, and you can pipe away right in your methods.  It’s not quite as easy as just putting the logic in the template, but is a great option in this case.  We created a formatDataForSearch method:

formatDataForSearch(data) {
    data.map(d => {
        //loop through and convert specific items that need it- others remain as-is
        d.rate = this.numberPipe.transform(d.rate, '1.4-4');
        d.originalCost = this.numberPipe.transform(d.originalCost, '1.2-2');
        d.costBasis = this.numberPipe.transform(d.costBasis, '1.2-2');
    });
}

Works great- the data is formatted in the array that will be passed to our search function and everyone’s happy.  A user can type anything they see in the grid and search will find it, but the actual info stored in the database is the correct number type.

But wait- there’s one more problem.  We also have a sort function on the grid.  Click any table header and the grid live-sorts using that data.  I’ve chronicled my journey from buggy, terrible sort to less buggy, mediocre sort in a few blog posts, but the actual function is at a point where it works pretty well.  However, a string sort does not do the same thing as a number sort.  Pass [100, 2] to a sorting function and you’ll get [2, 100] (assuming an ascending order sort).  Pass [‘100’, ‘2’] to a sorting function and you’ll get [‘100’, ‘2’].  Why?  According to MDN’s entry on Array.prototype.sort(), “The default sort order is according to string Unicode code points”.

This means two things for our use case
1) If you don’t pass any comparison function to .sort, you’re probably ok for strings, but will not get the correct order for numbers.  Be sure to pass at least a simple comparison (a > b) will do for simple numbers.
2) Even with #1 covered, we will need to convert the data back to a number.  Otherwise, 100 will come before 2 in our grid and chaos reigns!

So, one more edit to our orderby.service.ts (which houses our sorting logic) was in order (ha).  And it gave me one of the very few legitimate (I think) use cases for == I’ve had in a couple years of writing Javascript.  We added a check in our sorting method’s data formatting logic:

if(a[param] == +a[param]) {
    first = +a[param];
    second = +b[param];
}

The first and second variables are initialized a little further up in the code- this conditional comes in a block checking the type of the data being sorted.  a and b are the things being compared in each loop of the .sort function (objects- with the ‘param’ variable being the title text of the clicked column in the grid).  So, if a comparison of the data in the sort column and that same data converted to a number (the ‘+’ operator is just a quick way of converting a number to a string) is truthy, we convert both back to numbers and compare.  The original display remains a string, so we keep those padded zeros in $5.00, but the grid sorts properly.

I’ve always avoided the double equals check, as it doesn’t really do a true comparison.  It does some type coercion and can give you unexpected results (0 == false, for example).  It’s generally safer to use the strict comparison operator (===) but type coercion does seem to have it’s use case.

What started off as a simple edit request in my job queue (display this number data in this specific way) became a rabbit hole of conversions and back again. To be honest, it was pretty fun.  And the journey wasn’t quite over.  I was feeling pretty good after this victory (however small), when I got an update on this job: “The current shares column should be formatted with commas.  For example: 2,004,100.00.”

Next week- the saga continues!  I can use the currency pipe to convert and add the commas, but those commas are going to break my nice x == +x check in the sort function.  Spoiler: my solution is ugly, but it does work- stay tuned!

Filter Fridays

Performance is a huge aspect of Angular 2.  It seems that one of the main complaints about version 1 was the performance.  While performance is important, it’s also important to remember that this team created a framework that has to constantly check your app, monitoring for changes, in order to maintain bindings and data properly.  I compare it to adding a function to the onscroll event – it’s going to fire on every single pixel scroll of a user’s screen.  Not efficient at all, but sometimes necessary for the effect/functionality you want.

So some overhead is to be expected- but with version 2, they did seem to take into account the criticisms of 1’s performance.  One result- they removed the built in sort/filter pipes from ngFor.  While those pipes were very useful, they also caused some performance issues (as well as adding to the size of the framework- another complaint being addressed in version 2).

The upshot is, if you want to sort or filter you list items, you have to create a service for it yourself.  Which was actually pretty fun.

First up: search/filter.  It seemed to work well as a service instead of a pipe, so that’s the direction we took.  The input field is just a simple text input with a binding to an ngFormControl element.  By using this built in element, we can hijack the Observable that comes with it for some cool shortcuts.

So, as a user types, the list is filtered according to the key they press.  Done in a service, we set up a function to take the item (the string being searched for), group (the greater json array being searched through), and an optional property (allowing search by name or by email).  That function returns a promise- this promise uses the array.filter function that loops over each letter to check for a match.  First, though, we convert everything to lowercase (seemed like a case insensitive search would be best):

return new Promise(
    function (resolve, reject) {
        var filtered = group.filter((g) => {
        var lowerItem = item.toLowerCase();
        var lowerProp = g[property].toLowerCase();

        if (lowerProp.search(lowerItem) !== -1) return g[property];
    });
    resolve(filtered);
});

It would also work to regex search the string, but this seemed easier for now (than creating a regex with a variable input).  This gets passed back to that ngFormControl aspect.  We can use the valueChanges built in check- this will give us an Observable to subscribe to.  We can then pass that subscription on to the filter service function, and we are checking on each keystroke.  If there’s a match, the list is filtered.  If not, no results show (until the input is cleared, then all listings are back).

this.term.valueChanges
  .debounceTime(400)
  .subscribe(term => this._searchService.findItems(term, this.originalContacts, this.filterParam)
  .then((filteredContacts) => {
    if (term.length > 0) {
      this.contacts = filteredContacts;
    } else {
      this.contacts = this.originalContacts;
    }
}));

There’s one more aspect that I thought was really cool.  In the original example of bad performance sometimes being necessary, I used the onscroll event.  But there’s a way to mitigate that performance hit: a debounce function.  Essentially, this puts a delay on a function, so instead of firing on every single pixel scroll, it will fire, then ‘sleep’ for a set amount of scroll lines, then fire again.  This prevents unnecessary function calls.

Angular 2 Observables (like valueChanges on the form module) come with a built in debounce option (that’s the debounceTime chained in the above code).  Just pop it on and give it the number of milliseconds to wait before firing again.  So, this function will wait 400ms after the user stops typing before actually firing.

Super useful stuff- thanks Angular team!  Next week: the orderBy pipe reborn as a service!