I inadvertently coded myself into an interesting problem using AngularJS's animations.
the setup
Using ngRepeat, I was displaying a list of things to a <table>
on the page. In each table row, using a directive, I was also displaying an action button that when clicked would show a drop down menu (using Angular Strap) of actions you could take on that row... one of the things being to delete the row.
This being Angular though, and a modern web app, animations were aplenty. The drop down menu showed a little opening and closing animation. The table row animated by sliding in/out.
the problem
Thinking in a very linear way... animations happened immediately when the thing they were animated indicated.
In other words the order of events went:
- click the action button
- animation-enter happens to show drop down menu
- click a menu item
- menu item function runs
- animation-leave happens to close drop down menu
That was all well and good... until the function being run was to remove the table row!
- click the action button
- animation-enter happens on drop down menu
- click delete menu item
- array element (using in ngRepeat) is removed
- animation-leave happens to table row
- animation-leave wants to happen for the drop down menu... but the row, along with the drop down menu, is gone!
the solution
I either needed to not have the animation-leave happen for the drop down menu, or be sure it runs before the row is removed. Since the table row slid out to the left it looked a little, well, odd to see the row slide away with the drop down menu open.
The better option was to have the animation-leave on the table row happen after the animation-leave on the drop down menu... but how to delay?
Proving it actually would work, I put a hardcoded delay of 300ms on the table row animation... but that wouldn't work well if in the future we decided to change the drop down menu animation to run for 0.5s. I obviously needed a more bulletproof and reusable solution.
the code
Surprising it was very simple. Just set the delay to zero milliseconds!
$timeout(function() {
$scope.myArray.splice(index, 1)
}, 0, true);
the takeaway
AngularJS, like all frameworks, makes a lot of things easy for you but it also, like all frameworks, brings new challenges. Solutions can be simple in concept but hard to implement within the intent of the framework when you fight against the framework.
I had initially tried various other ways to agnostically delay the second animation, looking to lodash's _.defer
but in the end the solution was found within Angular itself.