jones.busy

technical musings of a caffeine converter

NAVIGATION - SEARCH

Ordering array of objects in Select Dropdown with Angular

As a newbie to angular and, in some sense, to javascript, when populating a select dropdown from an array of objects returned from my api, I fell into the trap of expecting the “| orderBy” filter to work as described. I’d already come a cropper with dynamically populated selects and trying to get the correct property to be shown so I probably should have been less surprised than I was.

Here’s the unordered select, populated from an array, ‘tenantTypes’ which itself is populated from an api call returning an array of a .net class, TenantType with the properties Id: long and Name: string.

<select ng-model="controller.registration.organisationType" ng-options="key as value.name for (key, value) in controller.tenantTypes" class="form-control"> <option value="">Organisation Type...</option> </select>

The ng-options statement took me some time to work out, but it worked as expected so I hope to be able to simply add the following in:

| orderBy:’name’

Not happening. The ng-options statement needed some jiggery-pokery to work with the object array and so does the sorting. After looking around, I came across this excellent filter created by Justin Klemm:

http://justinklemm.com/angularjs-filter-ordering-objects-ngrepeat/

Looks bang on the money so I grabbed it and added the following in:

| orderObjectBy:’name’

Still not happening: Once I’d ruled out my usual issues (reference the script, injecting it into the app module etc.), I started scratching my head. The only difference I could see was that this filter used the ng-repeat approach in its example. I knew I could have declared my select using <option ng-repeat… instead of the ng-options route but I had expected them to work the same under the bonnet. Well, if they do, then the ng-options does not work well with the custom filter as I finally managed to get my select ordering nicely by re-coding the select design to the following:

<select ng-model="controller.registration.organisationType" class="form-control"> <option value="" ng-selected="true">Organisation Type...</option> <option ng-repeat="type in controller.tenantTypes | orderObjectBy:'name'" value="{{type.id}}">{{type.name}} </option> </select>

Again, seasoned angular devs may be able to point out some glaring mistakes or assumptions I have made and please do, but just in case anyone else has tried or is trying the same approach, this might at least save them a couple of hours of head scratching Smile

Extending Angular $http service

Recently, I’ve been working with AngularJs, developing an Azure targeted application with an Entity Framework (Code First) backend, Web Api v2.0 middle tier and an AngularJs front-end. I like AngularJS for a number of reasons, one of which is that, as a .Net, WPF developer for most of my time, having so many different options and approaches to consider with client-side web development, having a framework that minimises those options into one core approach is beneficial to me. I completely understand that this same reason could be seen as a negative and I am well aware of the impending re-write of Angular due this year but to move from the safety net of a strongly typed .net background to the javascript playground, AngularJS feels a little like I’ve got some stabilisers on my dev bike Smile

Using the $http service over the weekend, I stumbled across a couple of issues:

1. Calling a web api method with a simple string parameter:

public async Task<IHttpActionResult> PostRole([FromBody]string roleName)

The issue is that by sending the string across as a variable causes some misinterpretation of as it as a json literal so instead of the value finding its way to the web api method, you get a null object instead. So, instead of calling the following:

return $http.post(serviceBase + 'api/admin/roles', roleName);

You need to do this:

return $http.post(serviceBase + 'api/admin/roles', "'" + roleName + "'");

2. Calling a ‘get’ method from within IE:

public async Task<IHttpActionResult> Get()

For some unknown reason, no matter how hard I tried to stop this from caching when testing in IE, I had no luck. In the end, the only approach that worked for me was to randomise the request as follows:

return $http.get(serviceBase + 'api/admin/roles?rnd=' + new Date().getTime());

It’s not very pretty is it? Firstly, I must asking anyone reading that has any suggestions or comments about what I’ve done or the issues I have faced, please do get in touch as it is perfectly feasible I have completely crossed my wires with this and there is another approach I should have taken. But for now, these are the issues I hit, Googled, and then put in some workarounds.

Now, having found a workaround, I realised that these sorts of calls could be a regular occurrence and so I don’t want to have to remember to apply these fixes each time. My first thought was to see whether I could extend the $http service to wrap this functionality in a neat bundle for me. Coming from C# extension methods i looked at the following options:

1. prototyping

2. behaviours

3. providers

I’ve heard about most of these approaches but never seen any in practice. I really wanted to stick to passing in $http and just being able to either handle or extend the functionality to handle these requirements but could not get this to work with my limited javascript knowledge. In the end, I create a new service, extendedHttpService, which is actually a factory class that returns an extended version of the $http service:

(function (ng, app) { "use strict"; app.factory('extendedHttpService', ['$http', function($http) { var forceGet = function(url) { return $http.get(url + "?rnd=" + new Date().getTime()); }; var postString = function(url, str) { return $http.post(url, "\"" + str + "\""); }; $http['forceGet'] = forceGet; $http['postString'] = postString; return $http; } ]); })(angular, app);

Allowing me to then call the extended functions as follows:

(function (ng, app) { "use strict"; app.service('rolesService', ['extendedHttpService', 'appSettings', function (extendedHttpService, appSettings) { var serviceBase = appSettings.apiServiceBaseUri; this.getAllRoles = function () { return extendedHttpService.forceGet(serviceBase + "api/admin/roles/"); }; this.createRole = function (roleName) { return extendedHttpService.postString(serviceBase + 'api/admin/roles', roleName); }; this.updateRole = function (role) { return extendedHttpService.put(serviceBase + 'api/admin/roles', role); }; }]); })(angular, app);

This is much neater, but I’d love to hear on other approaches that I could have used in this scenario.