in Development

UI-Grid and Row Animations

UI-Grid and Row Animations

When you add and delete rows in UI-Grid it can be a bit jumpy.

All of a sudden, rows get shifted around and you’re not sure where you were.

It’s like dropping a book on the ground and losing your place:

Where the hell was I?

In this post we’ll explore how to add animations to grid row operations to visually demonstrate what’s happening. The user should be able to tell what rows have just been added, and what rows are being removed.

Animating with CSS

CSS animations have been around a while. You might have used them on your own projects. If you haven’t, check out a couple links to see them in action.

In case you’re unfamiliar with the idea, animating with CSS offloads the hard work to the browser, which can optimize the operations and keep things looking smooth.

Animations also allow visual continuity. As the Google Material Design docs say:

Transitioning between two visual states should be clear, smooth, and effortless. A well-designed transition tells the user where to focus their attention.

Angular provides a module called ngAnimate which will automatically animate elements in directives like ng-repeat, ng-if, and ng-show if you provide the right CSS classes. Read about that in the Angular docs.

The Problem

Unfortunately there’s a problem when we want to use animations in UI-Grid. Because the grid’s content is virtualized, we are not adding and deleting DOM elements in a way that Angular can automatically manage.

Virtualization means that instead of rendering every row in your data set, the grid only renders what would be visible on the screen, plus a little bit extra as a hedge. Everything else is just wide empty space that you don’t see because you’re basically looking through a window at your data.

The Solution

To sum up so far, we fake having a big old data set on your screen, which means we can’t use the standard and easy animation method. How do we do the animations then? Fake them as well.

Angular’s  $animate service provides us with several methods for manually triggering animations. We’ll use two: addClass and removeClass. When you call these on an element Angular will apply some extra classes you can set up with CSS transitions.

We will fake the rows appearing and disappearing by watching for the data to change from within code that’s bound to the row. If the rendered row’s data changes to a new row, start the animation.

The animation itself for adding a row will set the row’s opacity to 0 initially, right when the data is changed, then it will transition to full opacity over one second. This will give the appearance of a new row fading in.

Let’s look at the CSS we’ll be using for adding rows:

The first class is the one we apply to new rows. We can do this with normal $elm.addClass('new-row'); this makes the row invisible initially. When we remove this class with $animate.addClass(element, 'new-row'), however, Angular will also first add the .new-row-remove class to start the animation, and at the end of the animation add the .new-row-remove-active class. Angular does this by parsing the styles the classes apply and pulling out the transition timing information. The result is that the row will transition from no opacity to full opacity over 1 second.

Enough talk, lets see the example:

Example

There’s a bit more here than we’ve talked about, but focus on the end result of clicking the “Add Rows” button. Remember that no elements are being created or destroyed. We’re just altering CSS and the data binding. So the rows appear to shift down, and the new rows appear to fade in. Best of both worlds, right?

Go ahead and play with the other settings. You can make the rows slide in, and alter the timing so it’s slow or fast. You can also change the easing function; see easings.net to see a bunch of different easing types in action.

Code for Adding Rows

We’ve seen the CSS. What about the JS? We said that we’d watch the data bound to the row and use $animate to change classes. Here’s what that looks like:

In our grid options we bind to the grid’s API and register what’s called a Row Builder. Row builders are functions that are executed on rows when they’re created. You can use them to extend row construction as you see fit. Here we just add a flag identifying the row as new.

We are stacking this directive on top of the existing uiGridRow directive. They will both run but ours runs at a lower priority so as not to affect the core directive.

In our link function we simply watch row.entity, which is the element from your dataset array. When it changed, we see if there’s an isNew property. If so, we add the new-row class, then remove it with $animate. Finally, we reset the isNew flag so the animation doesn’t run again during scrolling.

Q: Hey, in your $watch why aren’t you checking to see if n and o are unequal? Aren’t you running the animations too much?

Answer: No, not exactly. Every row initially has the isNew flag but on the initial $watch execution the entity hasn’t changed, so n and o are in fact equal. If we don’t allow the animation to run they when new rows are added the animations on ALL the visible rows will run at once, and that looks weird. Visually you don’t see the animations run as the row elements pop in when the grid starts, however.

Animated Row Deletions

The delete button on each row will perform the chosen animation, but backwards. We’ll use the delete-row class. Here’s the minimum amount of CSS:

So when the delete-row class is added, the row starts at opacity: 1 and over 0.5 seconds, transitions to opacity: 0.

The code for this is a bit different than what we’ve seen so far, however.  We have two problems:

  1. We can’t just remove the row when the button is clicked. We have to wait for the animation to run, THEN remove the row. Otherwise the row will just pop out of existence. The removeClass method returns a promise that is resolved after the animation completes so we can use that perform the removal.
  2. The scope isolation within the delete button’s grid cell prevents us from having access to our custom uiGridRow directive. We will need to emit an event from the cell that the row listens for.

Let’s see the code. Make sure to pay attention to the comments. They explain how the pieces work together.

That’s a bunch of lines but it gets us what we want. If you haven’t yet, check out deleting rows in the example above.

The rows fade or slide out and then the data shifts up. You’d never know the DOM elements were staying right where they are!

Where to Go From Here

What else can you do with animations, either in UI-Grid or your app? How can you help your users by providing meaningful transitions? What common operations do they (or you) find confusing?

Perhaps you could animate popping up modals, or filtering. Maybe transitions between sections of your application, or interactions with buttons, checkboxes and other elements. The sky’s the limit! Check out the Angular Material Demos to see how Google is encouraging the use of animations in web and mobile applications.

If you have any questions, please feel free to pop them in the comments below. I always respond as soon as I can. Also, for general UI-Grid talk join us on Gitter in the UI-Grid chat room. See you there!




Sign Up

Like what you read? Sign up for more! I love sharing tips, tricks, and methods to make web development faster and easier. And believe me, I hate spam just as much as you do.