Patterns for Using GreenSock in Angular
State Management
Angular is a very state-based world. For optimal performance, properties, bindings, events, and other Angular mechanisms should be updating and reacting to the states of the various directives. Animations are great for transitioning between states. But if the animation is actually the one managing the state changes, you can end up introducing states that are actually dependent on the associated animations, not associated with any logic in Angular, and not easily reachable in a non-linear way. In other words, your animations, rather than your component logic, are driving your state.
This is backwards, and it creates a lot of problems. Animations can respond to states, trigger states, and transition between states, but they shouldn’t unilaterally assemble new states that don’t otherwise exist. In the future, you might find yourself needing to access one of those states without running a complex animation sequence. Or you might need that state to “mean something” to some other part of the system. To do this, you need your state to drive your animations, not the other way around.
First, it’s helpful to understand the difference between a state and a transition. A state has some sort of logical value to the system, while a transition is just the process of moving from one state to another. Every developer needs to figure out this distinction for their own app. Perhaps a component has an “active” state and an “inactive” state, like the LightComponent
Animations work differently for states versus transitions. If an animation is truly associated with a state, it’s going to be some sort of repeating (or, at the very least, infinite) animation that plays as long as the state is active. The ButtonComponent
has an example of this:
The startFlashing
method initiates an infinitely repeating animation, and saves it to a property on the component so we can access it later. The stopFlashing
method uses that property to kill the flashing animation, and then kicks off a transition to make sure the button returns fully to its original appearance.
With a transition, the component is simply moving from State A to State B, which would typically mean a unidirectional animation that plays for a finite amount of time and then completes. This is how the animations work on the LightComponent
since it has no state animations:
The toggle
method uses the state of the component to determine which transition to play:
This method also contains a bit of “magic” code that’s very important when working with state and animations. By default, functions that run in GreenSock timelines execute outside of the “Angular Zone.” I’m not going to get into the details of Zone.js in this post, but suffice it to say that if a function executes outside the Angular Zone, nothing it does will trigger Angular’s change detection. Obviously, this is a problem when updating state. Fortunately, Angular does give us a means to force our code to execute in the Angular Zone: the NgZone
class. We can import it from @angular/core
and then inject it like a service into our constructor:
Then, we wrap any state-changing code we want to run in a call to our NgZone’s run
method, as in the snippet above. Now, that line updating our component’s property will trigger Angular’s change detection, ensuring that we can base other, non-animation logic on that property if we want to.
Also notice that we have the ability to change the state without playing the full animation, which we use for resetting both on demand and on initialization:
This is a fairly simple example; things would be more complicated if state animations were involved, as we’d also have to make sure we could clean those up whenever the state changes, like we do in the ButtonComponent
. Storing such animations in the component after their creation, and using GreenSock’s kill methods to prevent collisions, are both useful for this purpose.