1. 程式人生 > >Patterns for Using GreenSock in Angular

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

in our demo app. The question is, does it need additional states for “activating” and “inactivating,” or can these just be transitions? It largely depends on whether any app behavior must be linked to those conditions. In our case, the extra two states are not necessary, because they have no logical meaning for our app.

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:

src/event-emitter/components/button/button.component.ts:37,50

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:

src/event-emitter/components/light/light.component.ts:36,42

The toggle method uses the state of the component to determine which transition to play:

src/event-emitter/components/light/light.component.ts:61,71

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:

src/event-emitter/components/light/light.component.ts:30

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:

src/event-emitter/components/light/light.component.ts:73,80

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.