 Today we'll talk about animating your Angular app and using the Angular Animation Library. I'm Alisa Duncan. I'm a senior developer advocate at Okta, Google Developer Expert in Angular, co-organizer of Angular KC Meetup, and on the 14 Avengy Girls. I'm a nerd who enjoys cooking, watching k-dramas, and drinking wine. And I am not a designer, and I'm not a CSS expert. So all the animations that we'll apply today will be straightforward and simple so that we can focus on the capabilities of the Angular Animation Library. You can find me on Twitter, GitHub and Dev2 at Alisa Duncan. First, let's talk about animations. Animations are styles that are applied across time to HTML elements that mimic motion. And the motion could look like changing colors, growing, shrinking, fading, or moving across the screen. We want to use animations because it helps draw us attention to elements and makes applications feel more intuitive. It also makes applications feel more expressive so that users can feel like they're really interacting with the application. Currently, you would define styles for animations in CSS and then add any programmatic control for it in the JavaScript file. If you wanted to have any complex sequences or any coordination across HTML elements, you might need to watch for the animation done event or handle that logic yourself. But we want to take the time to do that because animations add polish and delight to your applications and it makes users want to use it. In Angular, you have another option. You can use the Angular Animation Library. Angular Animations are built on top of CSS animation. So the syntax will already feel very familiar. And Angular Animations are first class citizen of components. So you don't have to write code that binds animation styles across changing HTML elements. And it is also first class citizen on the router, which is awesome, so that you can hook into router events and apply consistent animation across router changes and not have to do so manually. Angular does the heavy lifting for you and it manages the coordination of animations across the component and supports complex animation sequences. So let's animate an Angular app together from the ground up and learn about the capabilities of the Angular Animation Library. We have a riff on the Angular Tour of Heroes. This is the heroes contact list. What we have is a dashboard with the frequent hero contacts and we have a menu here where we can navigate to the list of all your hero contacts. We also can see groups of heroes. You might wanna organize your heroes by your favorite heroes or ones that are friends or maybe even ones that are all doctors. And lastly we have an about page where we have some description about this application and this learn more expansion panel that gives you a little bit of trivia. To add animations to your Angular app, the first thing you wanna do is bring in the necessary import, the browser animations module. The browser animations module brings an extra animation specific dependency injection providers. And this is what unlocks animation capabilities in your Angular app. We're adding it to the app module so that we have browser animations available throughout our application. And note that the import for browser animations module is the Angular platform browser animations. So let's start our animation journey by looking at this app. And the first thing we wanna do is look at this unstyled expansion panel for this learn more. And those apply some animations to that. The basis of all animations in Angular is state transitions and triggers. So first we'll go to the about component and notice that I have all of my templates inlined so that we can easily take a look at what's going on in our animations code without having to navigate across multiple flights. I'm also using Angular material so that I have some nicely styled UI components. So you'll see that within the template as well. A little overview of the about component. We have a title, we have that description text and then we have the little learn more expansion panel in which we are setting the icon, the map icon for that expansion panel to be one of two icons depending on whether we clicked that learn more button or not. So whether that panel's expanded or collapsed. Additionally, we have an NGF, they're using that learn more property to determine whether or not to show the extra text. All right, so with that, let's start animating. We're gonna add an animation to an HTML element. The syntax for that is at sign and then the name of the animation. We're gonna call this learn more. You can name it whatever you want. Now to add animations into components we combined it directly into the component metadata. There is a property for animations that takes an array of animation triggers. So we'll be using the trigger helper method and then passing in the animation that we want to run which is learn more. So notice that trigger is imported from the Angular Animations library. All of the helper methods that we'll be using all come from this library. The trigger takes an array of animation metadata and we'll be using that a lot today. So the first thing we wanna do is talk about state. State is the beginning point and the end point of an animation. And in our case, we want to animate a expansion panel. So we have two states that we can use. We can identify whether the expansion panel is collapsed or expanded. So let's use those as our states and add some styles to it. So the first thing we're gonna do is use the state helper method. And state takes in a parameter of which state to style. And in our case, we're gonna use expanded. And then we can pass in the styles we wanna set for this expanded state. Style is basically CSS styles and the syntax is exactly the same. So we can save border and then pass in one pixel dashed light gray and then let's set a border radius. So border radius has a kebab case that has a hyphen in it. So that obviously doesn't work for a object property. We can leave it as kebab case property name and just add quotes around the property. Or we can also use camel casing, which is what we'll be using. So let's set this to be four pixels. Now we need to bind the state to that HTML element. And we don't have that set up yet. To do so, we can use this learn more property and we can bind this learn more animation like we would for property binding. So we've used the square brackets and then pass an expression, learn more. If one more is true, then the state is expanded. Otherwise, the state is collapsed. So now we're passing in what state this element is in when we set the style. Now let's add the styles for collapsed. I have a couple of snippets that we'll be using today so that you don't have to watch me type everything. So I already have some states set. I'm setting a pink border and a light pink background for the collapsed state. So let's take a look at what this looks like in the app. Here we go. The app reloaded and we see this pink background and pink border and when we expand the expansion panel, the border changes to a light gray dash and there is no background color. So we've successfully applied styles to our expansion panel based off a state but we don't have any animations yet. So let's add that in. For animation, we first need to identify the beginning state and the end state to animate. The transition. The syntax for transition is at the beginning state. So we're going to say expanded and then we use a fat arrow for the end state which is collapsed. So now we've defined what the transition is. We can pass in an array of animation metadata into that. So these are the styles that you would apply during this transition process. And one method we can pass in is animate which actually does the animation. The animate method has a couple of types of parameters you can pass in. The first is a string where you could say duration, delay and easing in space delimited. The duration and delay are units of time where duration is how long the animation should run and delay is how long the way before the animation runs. And you would need to pass in the unit of time to match such as one second or 500 milliseconds. Then you can also pass in an easing function. You could use a predefined one or you can define your own. Alternatively, you can pass in a value for animate which is the number of milliseconds for the duration of this animation. So we are going to do that and then just see what this looks like. So now we see the pink background. The background goes away and then we see the background slowly fade back in into view over that duration over the three seconds. Let's see that one more time. So we added our first animation. Now for an expansion panel, we don't really need the pink background. We don't need the pink border. We're actually fine with using the same dashed light gray border for both states. Luckily, the state method allows us to pass in a comma separated list of states to apply the styles for. So here we can say collapsed and then we can get rid of this state. We're also okay with applying the same animation to both expanded to collapse as well as collapse to expanded. Now we don't have to define a new transition for that if the animation steps inside or the same. We could use a divided arrow which is saying that regardless of whether you're transitioning from expanded to collapse or collapse to expanded apply the same animation. Now this animation will not show anything because we just have the same styles to run over three seconds. Let's add a different animation where we make it look like the expansion boxes actually expanding and collapsing. So for that, we're going to pass in the duration of let's say 500 milliseconds. And then we're going to define our own cubic basic easing function where I'm using a little snippet to paste that in so you don't want to see me type that out and we can apply this. Now let's take a look at the app. So now we've reloaded and we now see that the box looks like it is sliding open when it's expanded and sliding closed when it's collapsed. So this is perfect. Now let's add some more animations to this component. So far we've added animations across by adding it to an HTML element but we can also add animations to the entire component. We could do that by using host binding. So the syntax for that is adding a host binding then we pass in the name of the animation. So the same syntax of at sign and then we'll just call it page animations. Now we're going to set this to be a public property page animations and then set the value to be true. So because we always want this animation to run however, if you want programmatic control of setting animations this is how you do it. You do it via host binding. That way you can enable and disable and do all sorts of things in your component code. So now we need to add a new trigger and I'll have it a separate for that. But what do we use for the transition? We haven't defined a beginning state and end state. Well luckily Angular has some defined for us that we could use. One of them is void that is when no state has been manually applied to an element. All components and elements start in the void state and all components and elements end in the void state. And then there's also another option we could use which is the wild card state and asterisk or a star. This state applies to any existing state including anything that we might have applied manually. So it would also apply to expanded and collapsed as well. So we can say that the transition of void to anything is the same as component creation or when elements first come into view. The opposite of that is asterisk to void. And this is when components are cleaned up or the end of an element's view. These two transitions are used so frequently in Angular there's a helper for it and it is the colon enter and colon leave. So we're gonna use enter as our transition and then define the animation steps. So the first thing we're gonna do here is let's just make sure everything works and do the old console log of CSS which is adding a style that is pretty visible. And so for some length of time, three seconds we'll just say change the color to red so that way we can see if this animation is applying to the component. So now when we take a look at this in this loads we see that the text changes from black to red and then changes back to black after the duration of the animation. Let's look at that one more time in case you might have missed it. All right, so you can see now our animations are working across the entire page. But this isn't exactly the style we want. What we really want to do is we want to target this description text. Wouldn't it be really cool if this text faded in to view when the component loaded? So let's write our code for that by using query. So in animations we can query for what we want to animate. And if we look at our template we have this description paragraph text and it also has a CSS class of description. So we can use that in our query. So let's get rid of this animate with the red text and use the query helper method, query for description, and then pass in our animation metadata. And we're gonna set the style originally to opacity zero and then animate it for 2000 milliseconds, which is two seconds and set the style to opacity of one. So we're doing a fade in over two seconds. So now if we take a look at this and it loads, there we go, we see the description text fading in. So that's working. All right, so enough about the hero contacts about page for right now. Let's move on to someplace else. Let's move on to this contacts list. And notice how the contacts list is just appearing as the contacts are loaded. Wouldn't it be really cool if we show to the contacts appearing and sliding in one at a time? We could do that using stagger. So for that, let's open our contacts component. And once again, the template is inlined as well. So now we'll add our animation trigger and we'll say animate in, and then we'll set up the animation metadata. And I have a snippet for that, where I'm saying for the transition, anything to anything, we have a query for the animation styles we wanna run. So previously we used a query of a CSS class but query is really powerful. We actually can query for transitions as well. So we could use enter or leave. You can also query for animating, which is for all of the elements that are currently undergoing animation. You could also query by animation name using the at sign and then the name of the animation. Or you can also wildcard search for all animations and you can also query for self. So include its own HTML element in the animation. We're gonna use enter. And then here we're gonna pass in our animation code to stagger in the heroes. So I've set a style where the opacity starts off at zero and we're gonna apply a transform of the y-axis. And then we're using that helper method stagger. The stagger takes in a timings, which is the amount of time to wait between staggering in each element. Then we apply the animation for it. So let's take a look at the application now. So we see an error pop up. Now luckily the Angular animation library is provides really helpful error information. We see here that the animate in animation failed because query returned zero elements. It's also letting us know how we can resolve this, which is by passing in an optional true parameter, which is what we're going to be doing. Now you might want to not set optional true if you're in the middle of development processes, you wanna make sure that your queries are hitting elements properly. But in our case, we're fine with disabling it. Now you might also be wondering why did we have this error? And that's because enter and leave transitions are nuanced in Angular. Only elements that are created by its own logic, that is as a dynamic components or through structural directives, that's NGF or NG4 will always hit both the enter and leave transitions. Elements will pass enter, but not all elements will pass leave if you are using routing, for example. So the thing to note is that the enter and leave transitions have some caveats. So in our case, we can get around this by using that optional true, we're fine with that. So let's add that in and then let's take a look. And we see that the heroes did not stagger in, they just still appear. Now this is because the stagger method needs to know how many items to stagger in and we didn't provide that. So to do so, we need to bind the number of heroes to this animation. So we could do that using property binding and passing in the expression of the length of this array. Now, when we load this page, we should see the heroes staggering in. So that works out great. Now let's take a look at this contact name and contact button. We'd like to have that also animate in. And wouldn't it be really cool if they fade it into view and also the contact button slid over from the same space as the contact name all at the same time. Let's put that in. So for this, we're gonna use page animations. So we can add our host binding. We'll just call this page animations again and then add a new trigger. And here for the enter transition, I added a query of map form field and the button. So those are those two controls and we're setting the opacity from zero to one. All right, so now if we take a look at this, we see the heroes filtering in and we saw the contact name and the contact button appear slowly. So that's perfect. Well, let's go and add a code where we're sliding the button over. So to do that, we can remove this button from the query because we'll need to have a different style for it. And then I have a snippet for the styles which is in addition to setting the opacity also add a transform where we're moving along the X axis. And then during the animation process out of the style. All right, so now if we go back with this load. Okay, so we saw the contact name and the contact button enter interview and fade in. But the contact button slid over after the contact name. So that one more time. You can see it's happening in sequence and we want it to happen in parallel. So this is expected behavior for Angular. All animations will run in sequence unless you explicitly say or to run in parallel. So what we need to do is use a helper method group. So group method takes an array of animations to run, animation steps. So you can just move those two queries and plot them directly into that array. Let's see how this looks. Yay, so we, during the load, we see the contact name or the buttons fade into view and the contact button move over at the same time. Let's look at that one more time. Excellent, that works. So there is an opposite function for group and that is stagger so that you can force animations to run in sequence. Now you might be asking, why do you need to have this method if running a sequence is already the default behavior for Angular? Well, that's because if you have any nested animations and you want to have some animations that run in parallel, some that run in sequence and have complex animation steps, then you would need to have both control for setting whether animations run in groups or in sequence. All right, so now that we've had some animations to this contact list and we've done so by setting styles during the animate call, let's check out adding keyframes, which is also possible to do. We'll do that in this dashboard. We want these hero cards to scale up, get big and then scale back down to full size upon load. And we can do that by having different frames and offsets using keyframes. So let's now go to the dashboard component and then let's add the animation on the map card. So on each card. Now we're just going to call this animate. We're becoming less and less unique in our names. And then for the animation trigger, I've already defined all of the transition and the keyframes. So the animate method, we've passed in style. It also could take a keyframes method where you define the array of frames. So here we're setting the scale, the size of the cards, making it larger and then scaling it back. So let's take a look at what this looks like when it loads. All right. So we see the hero cards getting larger and then zooming back. So that worked. Now, heroes are pretty darn busy. We may not be always be able to get them. So I think we probably should display a warning banner. Just to alert people, let them know that heroes might not be always and immediately available for us to contact. But we only want to show this banner whenever we are done animating this dashboard. We can do that by watching for animation events. So if we go back to the dashboard component, I already have a paragraph text for this warning and I'm hiding it using this display warning property. So we want to change this display warning property to true when the animation completes. And we can do that by watching the animation event, which the syntax for that is the parentheses, looks like you do for click handling and the at sign animate, the name of the animate. And then we want to call done on it. Now here we can call a method that we defined if we have a lot of logic going on. And the event will include the animation event, which would have the beginning state, the end state, the HTML element itself. But in our case, our logic is pretty straightforward. So we can just inline it and just set the display warning value to true. All right. So now let's take a look and we see that the warning banner displays after the animation completes for that one more time. So this is obviously a fairly contrived example of watching for an animation event. The Angular material library is a great place to look at code that utilizes a lot of different Angular capabilities. And one of the things that it has in there is use of the animations library and animation events. For example, the Angular material dialogue uses animation events to know when to clean up resources. So I highly recommend checking that out for real world use cases. All right, so there was one more thing that kind of bothers me, maybe it bothers you. We go back to this about page. This expansion panel, when we open it up, the text just appears. We'd like for it to slide in and then slide back up when the expansion panel collapse. So it feels a bit more fluid to it being in accordion control. Let's add the code in for that. So for this, we'll go back to the about component and we're gonna add a new animation trigger. And we're just gonna call this slide in. And then let's add the trigger here. And I have a snippet for that where I have a transform along the y-axis, depending on whether I'm entering or leaving. Okay, so now let's go back and let this reload. And now when we expand and when we collapsed I'm not seeing a difference. It still looks like the text is just appearing and not sliding into view or sliding out of view. Well, that's expected because what we have is nested animations. And in Angular, the parent animation takes precedence. So if we take a look at our code, we have this learn more animation in a parent element of this slide in animation. We need to have the parent explicitly have the child animation run. So now if we go back to the parent animation learn more, we can query for that child animation. And then we could use a helper method animate child, which explicitly tells Angular to animate any children animations. All right, so now let's let this reload. And we see the description text where we see the text slide into view and slide out of view. But we see that the animations are happening in sequence not in parallel from the expansion panel collapse and expanding. So let's add that group function in. So we want these two animation steps to run in a group. We could just paste this in here and bring in the import. And then when this reloads, here we go. So now this looks a lot more natural with a collapsing and expanding accordion control. All right, so now we've added a lot of animations to components. Let's take a look at animations to routes. So let's focus our attention to this nav menu up here. And what we'd like to do is add animation that as we're navigating across this menu for the view to change along with it. So it looks like it's following the order of the navigation items. So if we go from dashboard to contacts, it's sliding over to the right. If we go from contacts to dashboards, it's sliding back into the left. To do that, we need to add some states to our routes. So we need to add some route data. Let's go to the app routing module. And in our route definition, we can add some data. And I have a snippet that we could use where I'm setting the data and defining route as my data. And then the value of route is a string that's the same as the path. So we now have two states. So now we need to add the animation to the router outlet. And in our app, it's in the app component. So we'll go to the app component and we have the router outlet here. The first thing I wanna do is we wanna grab the outlet. So that way we have access to the route data. And then we want to use that outlet to actually get the data for the animation. So we're gonna bind it to, we'll call this route animations. And we're doing this on the parent element of the router outlet. Then we're gonna call a method that we will define here in a set called get route and then pass it at the outlet. Okay, so now for the method, we will define it like this and we're just traversing down into the route to get that data of either dashboard or context. Now for the animation trigger, well, that was a lot. So let's go through what we just snippeted out. So back up to the top, we have our animation definition. We have our trigger and the same name as route animations, which is what we have up here on the router outlet. And then we're defining a transition dashboard to contacts. And then we have a series of different queries and styles and animation steps, where we are moving the view over. And then we have another transition, contacts to dashboard, where we have a very similar set of styles and animation steps, but it's moving the view the other direction. Just once again, so we have that fluidity between moving between those two menu options. Let's take a look at the application. All right, so we're on dashboard, let's navigate to context and then let's navigate back to dashboard. And we can see that movement of the view matches up with the how we're moving around in the navigation menu. So we added the transitions for dashboard to contacts and contacts to dashboard. Though we have two other routes here. We don't want to have to define transitions for dashboard to groups, dashboard to about, contacts to about, basically for every permutation of for all these four routes that we can go. That's a lot to fit in. What we can do is use a helper, increment and decrement, which uses a numeric value to identify whether or not you are navigating in one direction or the other. And we could use this for our transition. So how increment works is if you had a value that was greater than your last, then you're incrementing. If you have a value that was less than your last, you're decrementing. So if we change these route data to be numeric, then we can utilize this. So let's go back to that routing module and I had a snippet with all these data applied to the routes. And I just ordered them from zero to five in increasing sequence order across the navigation menu. Now in the app component, we need to change our transition. So when we were transitioning from dashboard to contacts previously, it's the same as using increment. And when we're transitioning from contacts to dashboard, it's the same as using decrement. All right, so let's take a look. All right, so now we can navigate from dashboard to groups to about, back to contacts in any order. And our animations are applying correctly. So there's one of the places we like to add some animation and that is we wanna apply the same animation like we had on contacts to groups. So our groups list just appears, it doesn't filter in so we'd like for it to do that. We like to stagger in just like this. We don't wanna have to define the animation for staggering in the groups in exactly the same way that we did for contacts. We could reuse it. So so far we've defined all of our animations within the component, but we can also define it external to the component and use it within components so we can keep our code dry. So I have a animations file that I've already created externally from a component and the syntax for this is export cost the name of the animation. We're gonna call this animate list in and then we'll use the helper method animation. Now in the animation method we pass in the animation steps. So I have a snippet for this that is exactly the same animation steps that we used for staggering in the heroes contact list. So now let's apply this animation to groups. So we go to groups component and then we have to add the same animation triggers like we did previously. So we'll call this animate in and then we need to pass in the lengths of the groups and then add our trigger. I have a snippet for that. Now when we use the animation we'll be using a helper method use animation and then we pass in the name of the animation wanna use which is animate list in. All right, so now our groups are loading in one by one. So that's perfect. We'll split that one more time. All right, so now that we've applied that animation to groups we can also clean up contacts component but we're using animations actually more powerful than this we can also use parameters. So I don't know if you noticed in the router animation the code, the animation steps for increment and decrement are very similar to each other with the exception of the some of the values that we have in the transform. So let's go ahead and have a reasonable animation for that with parameters. So if we go to animations we can define a new animations here and I have a snippet for that. And this is exact same code as one of the transitions. And then we need to pass in a parameter for this transform and we're gonna call this enter and notice that the syntax is a double curly brace. So it's the same as template binding. And then we need to pass in another parameter and it's this transform and we're gonna call this one lead. All right, so now to use this animation we'll go back to app component and then just to make it easier for us to follow I'm just gonna delete this entire trigger and use a snippet to paste it back in. So we have the same trigger, route animations. We have that method use animation we're passing in the animation that we created slide over around animation. And then we are passing in optional parameters. We're using the properties enter and leave which is what we define and then setting different values for it. So now if we take a look at our application with this reload our animations are still working across the routing. So that's great. So now that we've spent all this time adding animations to our application let's talk about how to remove them. Let's talk about how to disable them and you're gonna wanna do that. One place where you're gonna make sure that animations don't run is in unit tests because having animations around will cause flakiness in your tests. So instead of importing in browser animation module you'll just import nobop animation modules and now disable animations for this component. You can also disable animations at build time by passing in a with config and then setting disabled and disable animations property here. And this is good if you run E2E test and create a separate build for your E2E test runs and you can disable it as part of the build process. Lastly, you can also disable animations at runtime where you do this is at the top most component of your application. In our case, the app component. And then we're gonna have host binding where we bind to animation so that at sign and then disabled and then we accept this property be true. So this will disable all the animations across our application. We could do this if you don't wanna have a separate build for E2E tests or if you wanna have a user preference for disabling animations. All right, so let's go back and regroup over what we covered because we've covered a lot. We've added animations to components using template binding and we're using a component and binding to the entire component using host binding. We've talked about states, transitions and triggers all different ways to call different transitions. And we've also talked about querying and the different ways you can query by a CSS selector or colon enter by transition or an animation name. We've set styles. We've staggered. We've used child animations here. And we've also set keyframes and watch for animation events. We've also added animation across our routes. Use the increment and decrement helper so that we wouldn't have to define a whole bunch of different transitions. And we've reused animations and reused animations with parameters. And then lastly, we talked about three different ways that we can disable animations. I hope that was exciting for you. And if you'd like to learn more, you can check out my slides on speaker deck and at aliza.gen and the name of the slides are Angular Animations. You can also check out this sample app on GitHub, the GitHub repo names Angular Hero Animations. And I also have all the animations we added in this effort branch called Animations. I highly recommend checking out the great documentation on Angular.io. There is lots of good information in there and lots of details about how to add all these animations. If you're up to it, I highly recommend checking out the code for Angular Material Components and you can access it through their website material.angler.io. And lastly, let's keep in touch. I'd love to hear about your Angular Animation journey.