Animated slide panel with Angular

Anton Moiseev
6 min readJun 2, 2017

--

One of my favourite Angular features is animation. Meaningful animation can greatly improve usability of your application. Of course animating parts of an HTML page was possible and before Angular, but integration with the framework and ease of use unlocked it for me. Now when I create a page I consider whether it can be refined with an animation.

In this post I’ll show you one of those examples— animated slide panel. It’s a UI component you should be familiar with from mobile platforms. Here is how it looks like in one of our applications:

Address editing form placed into an animated slide panel (high resolution video here — https://youtu.be/vuyLgpU7wpE)

In the picture above you can see two UI elements — a list of addresses and an address editing form. When user clicks on the edit button next to an address item in the list, the list starts sliding to the left. At the same time the editing form slides from the right. When user clicks BACK or SAVE button the animation process is reverse. Here animation not only looks nice but also helps users to understand what happened, gives them a sense of a context and UI structure.

The sliding panel that hosts the address list and the editing form is implemented as a separate reusable component. We use it in a couple more places in our application. Below I’ll show you how it can be implemented.

General Idea

Before we proceed to the code, let’s discuss the general idea how we are going to implement the panel. It will be represented by an Angular component. The component’s template will consist of three main elements — a parent div element and two child div elements. Parent’s width is twice of the component’s width. Child elements aligned in a row within the parent side-by-side. Each child takes half of the parent’s width. Everything that doesn’t fit the component’s width is hidden. When we toggle children we animate the transition with Angular’s animation API. Here is a visual representation of the idea:

Implementation

Let’s start with declaring basic Angular component boilerplate:

import { Component } from '@angular/core';@Component({
selector: 'my-slide-panel',
styleUrls: [ './slide-panel.component.scss' ],
templateUrl: './slide-panel.component.html'
})
export class SlidePanelComponent {}

Now we need to add a property that will keep information about currently displayed child. Since consumers of our component should be able to change this state it will be an input property:

import {
ChangeDetectionStrategy,
Component,
Input
} from '@angular/core';
@Component({
//...
changeDetection: ChangeDetectionStrategy.OnPush
})
export class SlidePanelComponent {
@Input() activePane: PaneType = 'left';
}
type PaneType = 'left' | 'right';

We can safely enable OnPush change detection strategy for our panel since there is no any other way to update component’s UI rather than changing the value of the input property.

Another interesting thing we use here is PaneType. We leverage TypeScript’s string literal types combined with union types to limit possible values that can be assigned to activePane property. This increases type safety in our application.

Now let’s define the template:

<div class="parent">
<div><ng-content select="[leftPane]"></ng-content></div>
<div><ng-content select="[rightPane]"></ng-content></div>
</div>

As me discussed above we have one parent and two child elements. Each child has an ng-content element defined. This is called content projection in Angular and allows consumers of our component to pass custom HTML markup to be rendered inside our component in place of ng-content elements. A component can provide multiple such areas by distinguishing ng-content elements with unique select attribute values. In our case it’s [leftPane] and [rightPane].

To properly align parent and child elements we need to define CSS inside slide-panel.component.scss file:

:host {
display: block;
overflow: hidden; /* Hide everything that doesn't fit compoennt */
}
.parent {
height: 100%;
width: 200%; /* Make the parent element to take up twice
of the component's width */
display: flex; /* Align all children in a row */
div { flex: 1; } /* Evenly divide width between children */
}

:host selector allows us to apply styles to the HTML element that will represent our component when it’s pasted on the page. It wraps all elements defined inside component’s template. The content of our component will take twice of the component’s width, but we want to display only half of the content at a time. To hide everything that doesn’t fit we declare overflow: hidden.

Next we define styles for the parent element. To make it take up twice of the component’s width we add width: 200%. But this space should be evenly divided between parent’s children, we achieve this with flex layout.

Adding Animation

Note: Angular animation is a big topic. I won’t be explaining animation API details in this post. I’ll just give you a working solution and highlight the key moments. If you are curios to learn it further the good place to start is the official Angular guide.

Angular animation API is shipped as a separate package. It should be installed with npm install --save @angular/animations command (or with yarn add @angular/animations). Next we need to import BrowserAnimationsModule in the root module of Angular application:

import { NgModule } from '@angular/core';
import {
BrowserAnimationsModule
} from '@angular/platform-browser/animations';
@NgModule({
imports: [
//...
BrowserAnimationsModule
],
//...
})
export class AppModule {}

Now we can apply animation to the slide panel:

import {
animate, state, style, transition, trigger
} from '@angular/animations';
@Component({
//...
animations: [
trigger('slide', [
state('left', style({ transform: 'translateX(0)' })),
state('right', style({ transform: 'translateX(-50%)' })),
transition('* => *', animate(300))
])

})
export class SlidePanelComponent {
@Input() activePane: PaneType = 'left';
}

The trigger() function creates a named handle ‘slide’ that we can use in the template to start off the animation. The state() functions define the states that our component can take — either left or right. When component takes one of the states the styles defined for that state applied to the animated element (see the template code below). The transition() function defines how component transitions from one state to another.

To attach animation to an element in the template we use special syntax:

<div class="panes" [@slide]="activePane">
<div><ng-content select="[leftPane]"></ng-content></div>
<div><ng-content select="[rightPane]"></ng-content></div>
</div>

It can be read as: when activePane property changes, apply slide animation to the current div element. The values of the activePane property should match one of the defined animation states.

In our case the left child is visible by default. When the value of the activePane is updated to the ‘right’, animation kicks in and applies styles defined for the right state to the parent div element. transformX(-50%) moves the parent div along the horizontal axis on the left direction. So the left part of the parent element becomes hidden and the right part visible. Normally it would happen immediately, but because of the transition we defined it lasts for 300 milliseconds, so the user sees a smooth animation.

Usage example

Finally let’s add a sample code that uses our component. You can find the working application here. Below I just show the template part of the AppComponent:

<my-slide-panel [activePane]="isLeftVisible ? 'left' : 'right'">
<div leftPane>LEFT</div>
<div rightPane>RIGHT</div>
</my-slide-panel>
<button (click)="isLeftVisible = !isLeftVisible">
Toggle panes
</button>

We declare a my-slide-panel component that has two child div elements. The child elements have leftPane and rightPane attributes attached. This comes from the selectors we defined for the ng-content elements inside slide panel’s template. Next we bind activePane property to the expression that either evaluates to ‘left’ or ‘right’ dependning on isLeftVisible flag’s value. The flag is toggled by the button below every time user clicks on it.

Now everything should work. In the demo application I provide additonal CSS styles for AppComponent to make the demo a bit more illustrative. If you launch it you should see the following UI:

Summary

As you can see it’s not hard to add animation in an Angular application. The harder question is - where it makes sense. But if you manage to identify these places even basic animation techniques can improve your application and help users to better understand it.

--

--

Anton Moiseev

Software developer at Farata Systems. Co-author of Angular 2 Development with TypeScript http://j.mp/ng2book