How To Easily Expand or Collapse All Accordion Items in IonAccordionGroup at Once Without Adding Complexity
Tags: angular, ionic
Published: 2024-06-19 11:49:24

The [Ionic Accordion Group](https://ionicframework.com/docs/api/accordion) and the `ion-accordion` are awesome. Accordions provide collapsible sections in your content to reduce vertical space while organizing and grouping information.

However, I feel there's an important feature that's missing: the ability to _easily_ expand or collapse all accordion items at once. 

While it's _technically_ possible to expand or collapse all items at once, the current method is quite cumbersome. You have to use `<ion-accordion-group  [value]="valuesOfTheAccordionsThatShouldBeExpanded">` and pass an array of all the values of the accordions (or an empty array if you want to collapse all). This approach is problematic for several reasons:

1. Typically, you don't want to assign a value to each accordion manually. Ionic already handles this automatically, but as a developer, you can't easily see these values. 

2. Your component suddenly has an extra responsibility: managing the state of each accordion group. This adds unnecessary complexity to your app.

Generally, I appreciate that the Ionic team keeps things lean and leaves extra functionalities to the developer. However, in this case, I would have liked to see an `accordionGroup.toggleAll / expandAll / collapseAll` method.

Fortunately, with Angular directives, it's relatively simple to add this functionality cleanly.

Here's an example:

```typescript
import { ContentChildren, Directive, QueryList } from "@angular/core";
import { IonAccordion, IonAccordionGroup } from "@ionic/angular/standalone";

@Directive({
  selector: "[appAccordionGroupToggle]",
  exportAs: "appAccordionGroupToggle",
  standalone: true,
})
export class AccordionGroupToggleDirective {
  @ContentChildren(IonAccordion) accordions!: QueryList<IonAccordion>;

  get isCompletelyExpanded() {
    const groupValue = this.host.value;
    return (
      Array.isArray(groupValue) && groupValue?.length === this.accordions.length
    );
  }

  constructor(private host: IonAccordionGroup) {}

  toggle() {
    if (this.isCompletelyExpanded) {
      this.collapse();
    } else {
      this.expand();
    }
  }

  expand() {
    this.host.value = this.accordions.map((accordion) => accordion.value);
  }

  collapse() {
    this.host.value = [];
  }
}
```

The directive works by using `@ContentChildren` to get a reference to all `ion-accordion` items within the accordion group. This allows the directive to access the state (and thus the values) of all the accordions collectively. The constructor injects the host `IonAccordionGroup`, providing access to the group’s properties and methods.

In the `expand` method, the directive sets the `value` of the host accordion group to an array containing the values of all the individual accordions, effectively expanding all of them. In the `collapse` method, it sets the `value` to an empty array, collapsing all the accordions.

The `toggle` method first checks if all accordions are already open by checking the value of `isCompletelyExpanded`. If it equals `true`, it collapses them; if not, it expands them. 

You can use the directive as follows (don't forget to import the directive in your component):

```angular
<ion-content>
  <ion-button (click)="toggler.toggle()">Toggle all</ion-button>
  <ion-button (click)="toggler.expand()">Expand all</ion-button>
  <ion-button (click)="toggler.collapse()">Collapse all</ion-button>
  <ion-accordion-group
    appAccordionGroupToggle
    #toggler="appAccordionGroupToggle"
  >
    <ion-accordion>
      <ion-item slot="header">
        <ion-label>First Accordion</ion-label>
      </ion-item>
      <div slot="content">😎</div>
    </ion-accordion>
    <ion-accordion>
      <ion-item slot="header">
        <ion-label>Second Accordion</ion-label>
      </ion-item>
      <div slot="content">🤩</div>
    </ion-accordion>
    <ion-accordion>
      <ion-item slot="header">
        <ion-label>Third Accordion</ion-label>
      </ion-item>
      <div slot="content">💪</div>
    </ion-accordion>
  </ion-accordion-group>
</ion-content>
```

In the above example, we use `#toggler` to create a reference to the instance of the Group toggle. This is possible because we set exportAs: 'appAccordionGroupToggle' in our directive. Now, we can call any of the public methods (toggle, expand, and collapse) directly from our template.

That's it! Now you can use this directive to easily toggle all accordions at once anywhere you like. While I hope the Ionic team will add expand, collapse, and toggle methods to `ion-accordion-group` in the near future, it's great that Angular components make it so easy to add the desired functionality yourself.