Transitions and Triggers

Angular: The Full Gamut Edition

Charlie Greenman
April 20, 2021
15 min read

There are a number of transition states.

Wildcard Matching

Using an asterisk in a transition state will represent any sort of situation.

For example:

open => closed 
open => ** => closed
* => *

All of the above four will match when an element's state changes from open to anything else. Similar to routing, the asterisk will be a wild card meant to match any remaining use cases, animation transitions also follow in the same vein.

Situations where a Wildcard can be Used

Using Wildcard with Styles

transition ('* => open', [animate ('1s',style ({ opacity: '*' }),),]),

The transition of animate of 1s will match whatever it is that the current rule is.

Combining Wildcard and Void States

A void state is a way of causing an animation to occur whenever an element is entering or leaving a page.

The proper syntax for letting Angular knowing that an element should be animated when leaving, or entering would be to do the following:

animations: [trigger('flyInOut', [//...transition('void => *', [//...]),transition('* => void', [//...])])]

:enter and :leave aliases

However, Angular allows for the following alias, called :enter and :leave.

animations: [trigger('flyInOut', [state('in', style({ transform: 'translateX(0)' })),transition(':enter', [style({ transform: 'translateX(-100%)' }),animate(100)]),transition(':leave', [animate(100, style({ transform: 'translateX(100%)' }))])])]

In the above code, when an HTML element isn't attached to the view, we apply a transition. When entering the page, the element will fly in. When leaving the page, the element will fly out.

It is important to note that :enter and :leave willonly run, if the element is removed, or added. Therefore, it is required to leave an *ngIf on the div. An example, would be something such asthe following:

<div @myInsertRemoveTrigger *ngIf="isShown" class="insert-remove-container"><p>The box is inserted</p></div>
trigger('myInsertRemoveTrigger', [transition(':enter', [style({ opacity: 0 }),animate('5s', style({ opacity: 1 })),]),transition(':leave', [animate('5s', style({ opacity: 0 }))])]),

When a Value Increases, or Decreases

Angular has a built in transition for when a value increases, or decreases.

trigger('filterAnimation', [transition(':enter, * => 0, * => -1', []),transition(':increment', [query(':enter', [style({ opacity: 0, width: '0px' }),stagger(50, [animate('300ms ease-out', style({ opacity: 1, width: '*' })),]),], { optional: true })]),transition(':decrement', [query(':leave', [stagger(50, [animate('300ms ease-out', style({ opacity: 0, width: '0px' })),]),])]),]),

When the value is incremented it will cause a transition to happen.

Transitions for Boolean Values

In many situations, we may ask - should something be opened, or closed. Should it be shown, or hidden? For situations like this, Angular offers the ability to create transitions based on booleans:

<div [@openClose]="isOpen ? true : false" class="open-close-container"></div>

and

animations: [trigger('openClose', [state('true', style({ height: '*' })),state('false', style({ height: '0px' })),transition('false <=> true', animate(500))])],

Multiple Animation Triggers

In an Angular setting, each time an animation is triggered, the parent always get priority and cuts off the ability for child animation to run.

In order for a child animation to run, a parent element must trigger an animateChild function placed on the child element.

<div [@.disabled]="isDisabled"><div [@childAnimation]="isOpen ? 'open' : 'closed'"class="open-close-container"><p>The box is now {{ isOpen ? 'Open' : 'Closed' }}!</p></div></div>
@Component({
  animations: [trigger('childAnimation', [// ...]),],})export class OpenCloseChildComponent {
  isDisabled = false;
  isOpen = false;}

Animation Callbacks

There are times where you might want to tap into a particular time period of an animation. For a low API request, you might want the download button to have some sort of pulsating animation. When the API request completes, the pulsating can stop. An icon can work as well, however, there is some psychology behind if the actual item clicked on is the one that animates, as opposed to bringing in an outside icon. So let's imagine we are triggering an animation, we can do something such as the following:

@Component({
  selector: 'app-open-close',
  animations: [trigger('openClose', [// ...]),],
  templateUrl: 'open-close.component.html',
  styleUrls: ['open-close.component.css']})export class OpenCloseComponent {onAnimationEvent ( event: AnimationEvent ) {}}
<div [@openClose]="isOpen ? 'open' : 'closed'"(@openClose.start)="onAnimationEvent($event)"(@openClose.done)="onAnimationEvent($event)"class="open-close-container"></div>

Debugging Animations using Callbacks

Let's imagine in the aboveanimationEvent, if we were instead to use cram our animationEvent with the following:

export class OpenCloseComponent {onAnimationEvent ( event: AnimationEvent ) {// openClose is trigger name in this exampleconsole.warn(`Animation Trigger: ${event.triggerName}`);// phaseName is start or doneconsole.warn(`Phase: ${event.phaseName}`);// in our example, totalTime is 1000 or 1 secondconsole.warn(`Total time: ${event.totalTime}`);// in our example, fromState is either open or closedconsole.warn(`From: ${event.fromState}`);// in our example, toState either open or closedconsole.warn(`To: ${event.toState}`);// the HTML element itself, the button in this caseconsole.warn(`Element: ${event.element}`);}}

In the above, we can now view exactly what ishappening within our animation and can use this to determine animation if anything is awry.

Keyframes

Many animations happen to have a simple two step solution. For instance, show and hide component, or maximize height. However, there might be an animation that might be more than two steps. For animations of this scale, we will want to use Angular's keyframe() function, which is very similar to keyframes in CSS [^1].

If using default keyframes, it will automatically split the different times, across the time frame.

transition('* => active', [animate('2s', keyframes([style({ backgroundColor: 'blue' }),style({ backgroundColor: 'red' }),style({ backgroundColor: 'orange' })]))

In the above code, initially the backgroundColor will be blue. From 0s to 1s, the backgroundColor will be red. From 1s to 2s the backgroundColor will be orange.

Offset

Angular also gives the option to define at which point in the keyframe the animation should occur. Let's take the previous default animation and supply it with offsets:

transition('* => active', [animate('2s', keyframes([style({ backgroundColor: 'blue', offset: 0}),style({ backgroundColor: 'red', offset: 0.8}),style({ backgroundColor: 'orange', offset: 1.0})])),]),transition('* => inactive', [animate('2s', keyframes([style({ backgroundColor: 'orange', offset: 0}),style({ backgroundColor: 'red', offset: 0.2}),style({ backgroundColor: 'blue', offset: 1.0})]))]),

Automatic property calculation with wildcards

Many times, we are not 100% aware of what the height will be of the component that we would like to animate. Angular allows us to apply a wild card styling to the element, so that the height is determined at run time. Therefore, if we want, we can apply something like the following, so that we can determine the height at runtime, and then shrink the element when appropriate.

animations: [trigger('shrinkOut', [state('in', style({ height: '*' })),transition('* => void', [style({ height: '*' }),animate(250, style({ height: 0 }))])])]

Subscribe to the Razroo Angular Newsletter!

Razroo takes pride in it's Angular newsletter, and we really pour heart and soul into it. Pass along your e-mail to recieve it in the mail. Our commitment, is to keep you up to date with the latest in Angular, so you don't have to.

More articles similar to this

footer

Razroo is committed towards contributing to open source. Take the pledge towards open source by tweeting, #itaketherazroopledge to @_Razroo on twitter. One of our associates will get back to you and set you up with an open source project to work on.