Chapter 9. Animating Components with Angular 2

Nowadays, animations are one of the cornerstones of modern user experience design. Far from just representing a visual eye candy for beautifying the UI, they have become an important part of the visual narrative. Animations pave the road to convey messages in a non-intrusive way, becoming a cheap but powerful tool for informing the user about the underlying processes and events that happen while we interact with our application. The moment an animation pattern becomes widespread and the audience embraces it as a modern standard, we gain access to a priceless tool for enhancing our application's user experience. Animations are language-agnostic, are not necessarily bound to a single device or environment (web, desktop or mobile) and are pleasant to the eye of the beholder when used wisely. In other words, animations are here to stay and Angular 2 has a strong commitment to this aspect of modern visual development.

With all modern browsers embracing the newer features of CSS3 for animation handling, Angular 2 offers support for implementing imperative animation scripting through an incredibly easy but powerful API. This chapter will cover several approaches to implementing animation effects, moving from leveraging plain vanilla CSS for applying class-based animations, to implementing script routines where Angular 2 takes full responsibility for handling DOM transitions.

In this chapter we will:

  • Create animations with plain vanilla CSS
  • Leverage class-named animation with the ngClass directive to better handle transitions
  • Look at Angular's built-in CSS hooks for defining styles for each transition state
  • Animate components with the CssAnimationBuilder API
  • Design directives that handle animation
  • Introduce ngAnimate 2.0

Note

As a word of caution, bear in mind that the current implementation of animation in Angular 2 at the time of writing is, to a certain extent, temporary. In that sense, all the functionalities described in this chapter could probably be deprecated in the long run as the Angular 2 codebase matures. However, for now, there is no reason to hold ourselves back and leverage the animation modules already available in the framework. This will give us the power and functionality required to enhance the overall user experience of our applications.

Creating animations with plain vanilla CSS

The inception of CSS-based animation set an important milestone in modern web design. Before that, we used to rely on JavaScript to accommodate animations in our web applications by manipulating DOM elements through complex and cumbersome scripts based on intervals, timeouts, and loops of all sorts. Unfortunately, this was neither maintainable nor scalable.

Then modern browsers embraced the functionalities brought by the recent CSS transform, transition, keyframes, and animation properties. This became a game changer in the context of web interaction design in recent times. While support for these techniques in browsers such as Microsoft Internet Explorer is far from optimal, the rest of the browsers in store (including Microsoft's very own Edge) provide full support for these CSS APIs.

Note

MSIE provides support for these animation techniques only as of Version 10.

We assume that you have a broad understanding of how CSS animation works in regards of building keyframe-driven or transition-based animations, since providing coverage to these techniques is obviously out of the scope of this book. As a recap, we can highlight the fact that CSS-based animation is usually implemented by any of these approaches, or even a combination of both:

  • Transition properties, that will act as Observers of either all or just a subset of the CSS properties applied to the DOM elements impacted by the selector. Whenever any of these CSS properties is changed, the DOM element will not take the new value right away, but will experience a steady transition into its new state.
  • Named keyframe, animations, where we define different steps of the evolution of one or several CSS properties under a unique name, which will populate later on an animation property of a given selector, being one able to set additional parameters as the delay, duration of the animation tweening or the number of iterations that such animation is meant to feature.

As we can see in the two aforementioned scenarios, the use of a CSS selector populated with animation settings is the starting point for all things related to animation, and that is what we will do now: let's build a fancy pulse animation to emulate a heartbeat-style effect in the bitmap that decorates our Pomodoro timer.

We will use a keyframe-based animation this time, so we will begin by building the actual CSS routine in a separate style sheet. The entire animation is based on a simple interpolation where we take an object, scale it up by 10 percent and scale it back down again to its initial state. This keyframe-based tweening is then named and wrapped in a CSS class named pulse, which will execute such animation in an infinite loop where each iteration takes 1 second to complete.

All the CSS rules for implementing this animation will live in an external style sheet part of the timer widget component, within the timer feature folder:

app/timer/timer-widget.component.css

@keyframes pulse {
  0% {
    transform: scale3d(1, 1, 1);
  }

  50% {
    transform: scale3d(1.1, 1.1, 1.1);
  }

  100% {
    transform: scale3d(1, 1, 1);
  }
}

.pulse {
    animation: pulse 1s infinite;
}

As for this point on, any DOM element (in the TimerWidgetComponent template) annotated with this class name will visually beat like a heart. This visual effect is actually a good hint that the element is undertaking some kind of action, so applying it to the main pomodoro icon bitmap in our pomodoro timer widget when the countdown is on will help convey the feeling that an activity is currently taking place in a lively fashion.

Thankfully, we have a good way to apply such effect only when the countdown is active. We use the isPaused binding in the TimerWidgetComponent template. Binding its value to the NgClass directive in order to render the classname only when the component is not paused will do the trick, so just open the timer widget code unit file and add a reference to the style sheet we just created and apply the directive as described previously:

app/timer/timer-widget.component.ts

...
@Component({
  selector: 'pomodoro-timer-widget',
  styleUrls: ['app/timer/timer-widget.component.css'],
  template: `
    <div class="text-center">
      <img src="/app/shared/assets/img/pomodoro.png"
        [ngClass]="{ pulse: !isPaused }">
      <h3><small>{{ taskName }}</small></h3>
      <h1> {{ minutes }}:{{ seconds  | number: '2.0' }} </h1>
      <p>
        <button (click)="togglePause()" class="btn btn-danger">
        {{ buttonLabelKey | i18nSelect: buttonLabelsMap }}
        </button>
      </p>
    </div>`
})
...

And that's it! Run our pomodoro app and click on the Timer link at the top to reach the timer component page and check the visual effect live after starting the countdown. Stop it and resume it again to see the effect applied only when the countdown is active.

Handling animation with CSS class hooks

As we have just seen in the previous section, applying visual effects based on CSS classes is a breeze thanks mostly to the flexibility we have for adding custom class names in Angular 2.

On top of that, Angular provides support for animation class hooks, a functionality that was already available in Angular 1.x, under a different incarnation though. Basically, the mechanics is as follows: DOM elements managed by template-driven directives (NgSwitch, NgFor, or NgIf) can be decorated with the ng-animate classname. From that very moment, such elements will feature additional class names depending on the stage of the animations applied to that DOM element in the context of the wrapping component lifecycle.

This last statement might sound a bit odd and daunting, so let's see all this in action through an actual example. Open our TasksComponent template and update the row tag of the tasks list by adding a class named ng-animate to the markup. Then, do the same with the queued label displayed when the task model is lined up in our tasks queue. The code is as follows:

app/tasks/tasks.component.html

<tr *ngFor="let task of tasks; let i = index" class="ng-animate">
  <th scope="row">{{i}}
    <span *ngIf="task.queued" class="label label-info ng-animate">
      Queued
    </span>
  </th>
  <!-- the rest of the template remains untouched -->

</tr>

Save everything and then run again the application while inspecting the code in the browser dev tools. Well, apparently nothing happens. But we are talking about animations here, and as a matter of fact the underlying process is expecting exactly that, so let's decorate the ng-animate class with some animation-related styling defined in the component's associated style sheet:

app/tasks/tasks.component.css

h3, p {
  text-align: center;
}
.table {
  margin: auto;
  max-width: 860px;
}
.ng-animate {
  transition: all 0.3s ease-in;
}

The CSS rule defined previously will force any styling change applied to the DOM element to take place in 10 seconds following an ease-in algorithm curve when applied. Run the code again and inspect the code: apparently we're now into something: all elements flagged with the ng-animate class now feature some classes that temporarily decorate the element. These classes are ng-enter and ng-enter-active, where the first one is enabled by default when rendering the element and the latter applied straight away. After 10 seconds, which is the scope of the transition we defined in the CSS rule, both class names will disappear, leaving the ng-animate class as the only track of its now extinct existence.

Basically, we have the same behavior we implemented by hand in the previous section, with the sole exception that the class name's binding is operated this time by Angular 2 itself. With all this in mind, let's repurpose the style sheet a little bit to leverage the brief existence of these classnames in our DOM and the transition property to make a nice fade-in effect occur upon loading. Replace the 10 seconds transition period by a shorter 0.3 second value (300 milliseconds) and let's define opacity values for the initial ng-enter class name and the final ng-enter-active class names. The code is as follows:

app/tasks/tasks.component.css

...
.ng-animate {
  transition: all 0.3s ease-in;
}
.ng-enter {
  opacity: 0;
}
.ng-enter-active {
  opacity: 1;
}

Save and rerun the code examples. Now, you will see how the task list smoothly fades in on screen. The same applies to the blue label informing if any given task has been queued up or not.

Class hooks available

The class names we have just seen are known in Angular-land as class hooks, which resonates from the directive or routing lifecycle events we already covered, and each one of them will only exist while the animation takes place. We have four class hooks:

  • ng-enter: This will be applied by Angular's DOM renderer to any element flagged with the ng-animate class name upon being attached to the view. It usually wraps the styling we require our component to feature by default.
  • ng-enter-active: This classname is temporarily attached to the element on runtime right before starting the CSS animation and is removed automatically, along with the ng-enter class name, when the animation is completed. It usually defines the styling we strive our component to assume by the end of the animation which was previously reset by ng-enter.
  • ng-leave: Think of this class hook as the counterpart of ng-enter, but it takes place when the element is about to be detached from the view.
  • ng-leave-active: This is same as ng-enter-active, but it takes place when the element is about to be detached from the view. The classname is applied to the element and will be removed, along with ng-leave, once the transition is completed and before the element is removed from the DOM.

    Tip

    Do not ever forget that this technique is only available for DOM elements that are handled by the DomRenderer type, which is a low-level class in charge of creating, updating, or removing nodes and views among other tasks. In our case, elements decorated with the NgIf, NgFor, or NgSwitch directives are the only feasible candidates for it.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset