Order another round

So, back to Angular 2.  Again, some things were removed in the name of size and performance.  Among them, the ‘orderBy’ option on ng-for.  So we get to make our own!

At first glance, it seemed like a great way to introduce myself to pipes in A2.  After all, that’s basically how it worked in version 1 – throw the ‘orderBy’ attribute into your ng-for loop and you’re good to go (or set it to a variable on your controller if you want to be able to change it on the fly).

But that didn’t make much sense for our use case.  The data we were looping over was in a table, and we wanted the header for each row to be a clickable ‘sort’ link.  Side note: in another of my older projects, the team had to do this with fresh calls to the database each click.  So, a user clicks to sort by email, a request is fired off to the server which runs a sql query that ‘orders by’ the email field.  Not very efficient!

Anyway, a pipe might not work if we want to be able to have the clickable ‘sort’ links in multiple places, so I moved on to a service.  That service exports a class (as usual) with one method.  That method takes two parameters: param (possibly a bad name, but in this case, it’s just the search parameter), and group (the group that’s being searched through).  Then, it just does a simple array.sort with a conditional inside to get the type.  Array.sort is a great tool, but can be tricky when it comes to sorting different types.  The default return is x > y ? x : y   but this won’t work in most numerical cases, because it’s actually comparing the strings.  For example: in this setup, if I pass 100 and 2, 100 will come before 2 in the sorted array.  So sometimes, we have to pass an actual function to sort.  It gets a bit more sticky when case it taken into account.  The lowercase character code doesn’t match up with the uppercase version, so when comparing string input, you would also have to choose one case and convert before comparing.

It results in a somewhat convoluted conditional that works pretty well:

return group.sort((a, b): any => {
  if (typeof a[param] === 'number' || a[param] == +a[param]) {
    return a[param] - b[param];
  } else if (typeof a[param] === 'string') {
    return a[param].toLowerCase() > b[param].toLowerCase();
  } else {
    return a[param] < b[param];
  }
});

One thing to note: we are using Typescript and it’s great, but for a while (longer than I’d care to admit), the above method didn’t work.  I kept getting a ‘does not return one type’ error, which prevented the Typescript from compiling.  And it makes sense- sometimes the group.sort function will be given strings, sometimes numbers, etc.  Also, sometimes it will return a boolean, sometimes a number.  That last part of the first line: group.sort((a, b): any  – the ‘any’ decorator tells TS that we’re going to be allowed to return ‘any’ type.

There’s another small branch above this to deal with sorting by the ‘name’ field- it splits out the last name to sort by that (which make more sense to most users than sorting by the first name).

All that’s left?  Inject the service into any component you want to use a sort option on, and call it on the data accordingly for your initial load.  Then, in the template for that component, include a link for any column that’s sortable, call the sort function in your service (we called it ‘orderByParam’) and pass it the proper arguments.

I’m sure my method it inefficient and maybe there’s a better way to do it, but it was pretty fun to write and see it actually working!

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