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...

  • dimitri

    How would you for example begin to create a Gender selection Component i.e male/female. Or a stepper component

    plus/minus ? Or an easy to input birthDate component?

  • dimitri

    Hi, i am trying to use this components in another page (lazy loaded ) instead of home and i am getting

    1. If ‘expandable is an Angular component, then verify that it is part of this module.
    2. If expandable’ is a Web Component then add ‘CUSTOM_ELEMENTS_SCHEMA’ to the ‘@NgModule.schemas’ of this component to suppress this message.

    @NgModule({
    declarations: [
    MyApp,
    HomePage,
    ExpandableComponent
    ],
    Can you please help

    • Josh Clark

      Same problem/question here…

      • Karan Sharma

        Did you find any solution to this problem, I am facing the same issue..

    • Mustaqim Abdullah

      You can declare on your-page.module.ts
      below is my declaration on my temp-accordion-list.module.ts
      import { NgModule } from ‘@angular/core’;
      import { IonicPageModule } from ‘ionic-angular’;
      import { TempAccordionListPage } from ‘./temp-accordion-list’;
      import { ExpandableComponent } from ‘../../components/expandable/expandable’

      @NgModule({
      declarations: [
      TempAccordionListPage,
      ExpandableComponent
      ],
      imports: [
      IonicPageModule.forChild(TempAccordionListPage),
      ],
      })
      export class TempAccordionListPageModule {}

      • Pie

        Hi I am facing the same problem. And if we add it to our page.module.ts it throws error “Error: Type ExpandableComponent is part of the declarations of 2 modules: AppModule and CustomModule! Please consider moving ExpandableComponent to a higher module that imports AppModule andCustomModule. You can also create a new NgModule that exports and includes ExpandableComponent then import that NgModule in AppModule and CustomModule.”

      • Mustaqim Abdullah

        You need to create a shared.module.ts..therefore you can shared the components across your build. I cannot share my code because im away from my laptop. But you can get the answer here https://stackoverflow.com/questions/43675253/ionic-3-one-header-component-for-several-pages

      • Mayank Rungta

        Why are we required to create a shared module. I could just use app.module.ts if it is going to be used by multiple modules, making it a global declaration. Why do we need so many levels of inclusion – it is either local or global. Can be lazy load if it is a local module specific to a particular page, else let it load the usual way?

    • Jay Patel

      No need to add this declaration i.e. ExpandableComponent in app.module.ts
      Simply add this in import in the page.module.ts in which you are going to use ExpandableComponent

      Check @ https://stackoverflow.com/questions/43507800/custom-component-in-ionic-v3

      • Mayank Rungta

        It gets added automatically when you create the Component using the CLI. It should work if globally added. If I want it to be lazy I can move it to the specific page. Why is the global declaration not working? Seems like a bug.

  • Jacques Nieuwoudt

    Super easy to make the items have dynamic height instead of fixed height. I needed them to be dynamic since the content of each item is variable.
    Just change
    this.renderer.setElementStyle(this.expandWrapper.nativeElement, ‘height’, this.expandHeight + ‘px’);
    to
    this.renderer.setElementStyle(this.expandWrapper.nativeElement, ‘height’, ‘auto’);

    • Jacques Nieuwoudt

      And to clean up just remove all expandHeight stuff.

      • Yasir

        hi, I also need dynamic height, but when change it to ‘auto’ the transition animation disappears…. how to fix this problem ?
        much appreciated

      • Raghavendra Kumar D

        Facing same issue on animation

      • Syndafloden

        You can’t use transitions on `height: auto`, you need to use a fixed for it to work.

  • Alonso

    Excellent, but in another page, not home?

  • John Douglas

    What is the best method to get the expandable content to be full width rather than starting after the thumbnail (while still keeping the thumbnail at the top)?

  • FanFan Fantastic

    I make an API in the component page and all components got updated… I don’t want the content to be the same… plz help

  • vajohnify

    How do I get the contents to differ, been trying all sorts of tricks but failing

  • Shalvi Sethiya

    Uncaught (in promise): TypeError: Cannot read property ‘call’ of undefined TypeError: Cannot read property ‘call’ of undefined at __webpack_require_ how to resolve it.

  • Shalvi Sethiya

    when i add multi pal div in expandable tag

    Education: Software Eng.
    Father Name: Jsutin

    Requset

    like this. so only button will show after expand. another div’s show before expand . please help me

    • Shalvi Sethiya

      please help me

  • Romain BAGHI

    Thanks for this useful tutorial. I tried to add a button inside the expandable content but It’s not working since the entire item is expanded or reduced on click.
    How can we make this work ?

    any ideas ?