A sticky Angular ui-router problem

November 1, 2016 by JavaScript   Angular  

While working on one of our Angular 1.5.x projects, someone in our QA department reported a rather interesting bug, which I tracked down to a third party library (ui-router-extras, more specifically sticky states) we're using.

Before I go into more detail about the problem, lets first get down to the crux of it all.

Have a look at the follow snippet, can you predict what text will be alerted by the browser?

var text = '', o = { };

o.y = 'World';
o.x = 'Hello';

delete o.y;
o.y = 'Clarice';

for (var key in o) { text +=o[key] + ' '; } 

alert(text);

Ok, now obviously (in this context) its unnecessary to delete the y property if we're merely interested in assigning a new value to it, but lets assume for a second that this is required.

If you expected something as basic as this to work the same across all browsers, you would be wrong!

In Chrome, Firefox, Opera and Safari, "Hello Clarice" (like expected) will be alerted to the screen, while in Internet Explorer and Edge, "Clarice Hello" will be displayed (seems like Edge inherited some of IE's quirks).

Cousins

So basically when we delete a property from an object, Internet Explorer and its cousin Edge, will retain the order in which the property was added.

Now if the order of the added properties is important to the logic of your code, things won't work as expected.

You can see the offending code in the following extract from ct-ui-router-extras.js line 661 (0.1.2).

stateReactivated: function (state) {
  if (inactiveStates[state.self.name]) {
	delete inactiveStates[state.self.name];
  }
  state.self.status = 'entered';
  if (state.self.onReactivate)
	$injector.invoke(state.self.onReactivate, state.self, state.locals.globals);
}

And sure enough, if we set the "DEBUG" variable to true on line 40 in the script, we see something similar to the following in the console output using Internet explorer and Edge.

Current state: panel1, inactive states:  ["path2", "path1"]

While in every other browser (Chrome, Firefox, Opera and Safari).

Current state: panel1, inactive states:  ["path1", "path2"]

Not ideal.

A quick fix I came up with can be seen in the following snippet.

stateReactivated: function (state) {
  var states = {};
  angular.forEach(inactiveStates, function (item, property) {
	  if (property !== state.self.name) {
		  states[property] = item;
	  }
  });
  inactiveStates = states;
  state.self.status = 'entered';
  if (state.self.onReactivate)
	  $injector.invoke(state.self.onReactivate, state.self, state.locals.globals);
}

The basic idea is, instead of deleting the property from the object, simply exclude it or filter it out into a new object, thereby preserving the property order.

And thats all she wrote.

Just a quick shout out to Chris Thielen for writing these marvellous ui-router extras in the first place - sir you're a gentleman and a scholar Winking smile


Leave a Comment