Accordion in Ionic

Creating an Accordion List in Ionic



·

Earlier this week I recorded a video where I walked through building an expandable component on screen. This was a generic component that could be given a specific height, and its state could be toggled between being expanded and collapsed.

Due to a dwindling battery, I didn’t get to take the component quite as far as I wanted in the video, so I’m writing this blog post to finish things off a bit, and also use the component to create an accordion style list in Ionic. Here’s what we will have by the end of this tutorial:

Accordion List in Ionic

I will walk through building it from start to finish, so no need to watch the video if you don’t want to.

NOTE: We will be building an accordion list using a generic expandable component, I don’t think it would be correct to refer to this as an accordion component. It would be a small step to modify this to be a dedicated accordion component, though.

Before We Get Started

Last updated for Ionic 3.3.0

Before you go through this tutorial, you should have at least a basic understanding of Ionic concepts. You must also already have Ionic set up on your machine.

If you’re not familiar with Ionic already, I’d recommend reading my Ionic Beginners Guide or watching my beginners series first to get up and running and understand the basic concepts. If you want a much more detailed guide for learning Ionic, then take a look at Building Mobile Apps with Ionic.

1. Generate a New Ionic Application

We’re going to start by generating a new blank Ionic application, to do that just run the following command:

ionic start ionic-expandable-list blank

Once that has finished generating, you should make it your working directory:

cd ionic-expandable-list

We will be creating a custom component called Expandable, so we will also generate that now:

ionic g component Expandable

This command will also automatically set up the component in the app.module.ts file, however, it also auto generates an expandable.module.ts file which we don’t need since we are not using lazy loading. You should delete the expandable.module.ts file.

2. Building the Expandable Component

What we are aiming to build is a component that will look like this:

        <expandable [expandHeight]="itemExpandHeight" [expanded]="item.expanded">
            some content here
        </expandable>

We could insert this wherever we wanted to display a collapsible/expandable component. We can pass in our desired height with the expandHeight option, and we can toggle the expanded state with the expanded input. With that in mind, let’s build out the component itself.

Modify src/components/expandable/expandable.html to reflect the following:

<div #expandWrapper class='expand-wrapper' [class.collapsed]="!expanded">
    <ng-content></ng-content>
</div>

The template for the component is reasonably simple. We are using content projection with <ng-content> to project whatever we add inside of the <expandable> tags into the component’s template. This means that we can display some content here inside of our component.

We also have a wrapper <div> that has a template variable of expandWrapper so that we can access it later, and the collapsed class is being applied conditionally based on the value of expanded. This is how we will toggle the collapsed styling on and off.

Modify src/components/expandable/expandable.scss to reflect the following:

expandable {

    .expand-wrapper {
        transition: 0.2s linear;
    }   

    .collapsed {
        height: 0 !important;
    }

}

In the template, we were toggling the collapsed class on and off, and this is where we define that class. When the collapsed class is applied it will force the height of the wrapper to be 0 so it should disappear, and we also add a transition property so that the component will shrink or grow smoothly rather than just snapping in and out of existence.

Modify src/components/expandable/expandable.ts to reflect the following:

import { Component, Input, ViewChild, ElementRef, Renderer } from '@angular/core';

@Component({
  selector: 'expandable',
  templateUrl: 'expandable.html'
})
export class ExpandableComponent {

    @ViewChild('expandWrapper', {read: ElementRef}) expandWrapper;
    @Input('expanded') expanded;
    @Input('expandHeight') expandHeight;

    constructor(public renderer: Renderer) {

    }

    ngAfterViewInit(){
        this.renderer.setElementStyle(this.expandWrapper.nativeElement, 'height', this.expandHeight + 'px');    
    }

}

Now we have the class for the component. This is also pretty simple – we set up the two inputs that we wanted, and we also grab a reference to the expandWrapper using the template variable we added earlier. We then use that reference to set the height of the component in the ngAfterViewInit function.

3. Use the Component to Create an Accordion List

The component is complete now, so all we need to do is use it. As I mentioned, we are going to use it to create an accordion list by adding it into a list. It’s mostly just a case of dropping it in there, however, we also need to add a bit of logic to close all other expanded components in the list when a new component is expanded (so that only one is open at a time).

Modify src/pages/home/home.html to reflect the following:

<ion-header>
  <ion-navbar>
    <ion-title>
      Ionic Blank
    </ion-title>
  </ion-navbar>
</ion-header>

<ion-content>

    <ion-list>
      <button detail-none (click)="expandItem(item)" ion-item *ngFor="let item of items">
        <ion-thumbnail item-start>
          <img src="http://placehold.it/100">
        </ion-thumbnail>
        <h2>My Neighbor Totoro</h2>
        <p>Hayao Miyazaki • 1988</p>
        <expandable [expandHeight]="itemExpandHeight" [expanded]="item.expanded">
            Hello people
        </expandable>
        <button ion-button clear item-end>View</button>
      </button>
    </ion-list>

</ion-content>

We’ve just added a stock standard list to the template here, except that we’ve added an <expandable> component inside of it. We bind the expanded input to the value of each particular items expanded property (which will allow us to toggle them individually), and we also have a click handler that triggers expandItem which will allow us to toggle the items expanded state.

Modify src/pages/home/home.ts to reflect the following:

import { Component } from '@angular/core';
import { NavController } from 'ionic-angular';

@Component({
  selector: 'page-home',
  templateUrl: 'home.html'
})
export class HomePage {

    items: any = [];
    itemExpandHeight: number = 100;

    constructor(public navCtrl: NavController) {

        this.items = [
            {expanded: false},
            {expanded: false},
            {expanded: false},
            {expanded: false},
            {expanded: false},
            {expanded: false},
            {expanded: false},
            {expanded: false},
            {expanded: false}
        ];

    }

    expandItem(item){

        this.items.map((listItem) => {

            if(item == listItem){
                listItem.expanded = !listItem.expanded;
            } else {
                listItem.expanded = false;
            }

            return listItem;

        });

    }

}

We’ve set up an array of item objects here that only have a single expanded property. Of course, you would usually have other properties defined here as well like the title, body, and so on.

Our expandItem function maps each element in the array – when it gets to the item that has been clicked, it will toggle its state, and it will set any other expanded items back to the collapsed state.

Modify src/pages/home/home.scss to reflect the following:

.ios, .md {

    page-home {

        button {
            align-items: baseline;
        }

    }

}

This just makes the list look less weird when the component is expanded by aligning the image and the view button to the top of the item. If you load the application up in your browser now and click around a bit, you should have something that looks like this:

Accordion List in Ionic

Summary

The way in which this component is designed is quite generic and modular, so even though we are using it to create an accordion style list in this case, it could also be used in a variety of different circumstances as well. This is why it’s often better to create custom components for this type of functionality, rather than trying to build functionality directly into your pages.

What to watch next...