99. A-tag-not-highly-recommended

Ionic(Angular)でアコーディオンを実装する方法

アプリ開発をイメージさせる写真
Ionic(Angular)でアコーディオンを実装する方法です。

ポイント

  • Componentを自作して、それを読み込むようにします
  • これによりいろいろな場所で使い回しができるようになります

結果

こんな感じでクリックするとカードが広がるようになります。

環境

Ionic 4, 5, 6で動作検証しています。

流れ

  1. コンポーネントを作成し、HTMLとSCSSを記載します
  2. 読み込みたい場所で作成したコンポーネントをmoduleに登録します
  3. 使いたい場所のHTMLでapp-expandableタグで呼び出します

ソースコード

GItHubに上げました。 https://github.com/NP-Systems/Ionic_tips/tree/id1131-createAccordion/frontend

参考文献

https://www.joshmorony.com/creating-an-accordion-list-in-ionic/

初期状態

IonicのTabsのデフォルトテンプレートから始めます。
appフォルダ以下の構成は下記のようになっています。
.
├── app-routing.module.ts
├── app.component.html
├── app.component.scss
├── app.component.spec.ts
├── app.component.ts
├── app.module.ts
├── explore-container
│   ├── explore-container.component.html
│   ├── explore-container.component.scss
│   ├── explore-container.component.spec.ts
│   ├── explore-container.component.ts
│   └── explore-container.module.ts
├── tab1
│   ├── tab1-routing.module.ts
│   ├── tab1.module.ts
│   ├── tab1.page.html
│   ├── tab1.page.scss
│   ├── tab1.page.spec.ts
│   └── tab1.page.ts
├── tab2
│   ├── tab2-routing.module.ts
│   ├── tab2.module.ts
│   ├── tab2.page.html
│   ├── tab2.page.scss
│   ├── tab2.page.spec.ts
│   └── tab2.page.ts
├── tab3
│   ├── tab3-routing.module.ts
│   ├── tab3.module.ts
│   ├── tab3.page.html
│   ├── tab3.page.scss
│   ├── tab3.page.spec.ts
│   └── tab3.page.ts
└── tabs
    ├── tabs-routing.module.ts
    ├── tabs.module.ts
    ├── tabs.page.html
    ├── tabs.page.scss
    ├── tabs.page.spec.ts
    └── tabs.page.ts

自作コンポーネントの作成

下記のコマンドで自作コンポーネントを作成します。
% ionic g component components/Expandable
app.module.tsと同じ階層にcomponentsフォルダができて新しいコンポーネントが生成されます。
.
├── app-routing.module.ts
├── app.component.html
├── app.component.scss
├── app.component.spec.ts
├── app.component.ts
├── app.module.ts
├── components
│   └── expandable
│       ├── expandable.component.html
│       ├── expandable.component.scss
│       ├── expandable.component.spec.ts
│       └── expandable.component.ts
├── explore-container
│   ├── explore-container.component.html
│   ├── explore-container.component.scss
│   ├── explore-container.component.spec.ts
│   ├── explore-container.component.ts
│   └── explore-container.module.ts
├── tab1
│   ├── tab1-routing.module.ts
│   ├── tab1.module.ts
│   ├── tab1.page.html
│   ├── tab1.page.scss
│   ├── tab1.page.spec.ts
│   └── tab1.page.ts
├── tab2
│   ├── tab2-routing.module.ts
│   ├── tab2.module.ts
│   ├── tab2.page.html
│   ├── tab2.page.scss
│   ├── tab2.page.spec.ts
│   └── tab2.page.ts
├── tab3
│   ├── tab3-routing.module.ts
│   ├── tab3.module.ts
│   ├── tab3.page.html
│   ├── tab3.page.scss
│   ├── tab3.page.spec.ts
│   └── tab3.page.ts
└── tabs
    ├── tabs-routing.module.ts
    ├── tabs.module.ts
    ├── tabs.page.html
    ├── tabs.page.scss
    ├── tabs.page.spec.ts
    └── tabs.page.ts

tab1.module.tsでの読み込み

今回はtab1ページに埋め込むことにするので、tab1.module.tsに下記のように登録します。
import { IonicModule } from '@ionic/angular';
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { Tab1Page } from './tab1.page';
import { ExploreContainerComponentModule } from '../explore-container/explore-container.module';
import { ExpandableComponent } from "../components/expandable/expandable.component";

import { Tab1PageRoutingModule } from './tab1-routing.module';

@NgModule({
  imports: [
    IonicModule,
    CommonModule,
    FormsModule,
    ExploreContainerComponentModule,
    Tab1PageRoutingModule
  ],
  declarations: [Tab1Page,ExpandableComponent]
})
export class Tab1PageModule {}

expandable.component.htmlの変更

 src/components/expandable/expandable.component.htmlのファイルを下記のように変更します。

<p>
  expandable works!
</p>

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

expandable.component.scssの変更

.expand-wrapper {
  transition: max-height 0.4s ease-in-out;
  overflow: hidden;
  height: auto;
}

.collapsed {
  max-height: 0 !important;
}

expandable.component.tsの変更

Before
import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-expandable',
  templateUrl: './expandable.component.html',
  styleUrls: ['./expandable.component.scss'],
})
export class ExpandableComponent implements OnInit {

  constructor() { }

  ngOnInit() {}

}
After
import { Component, AfterViewInit, Input, ViewChild,  ElementRef, Renderer2 } from "@angular/core";

@Component({
  selector: "app-expandable",
  templateUrl: "./expandable.component.html",
  styleUrls: ["./expandable.component.scss"]
})
export class ExpandableComponent implements AfterViewInit {
  @ViewChild("expandWrapper", { read: ElementRef }) expandWrapper: ElementRef;
  @Input("expanded") expanded: boolean = false;
  @Input("expandHeight") expandHeight: string = "150px";

  constructor(public renderer: Renderer2) {}

  ngAfterViewInit() {
    this.renderer.setStyle(this.expandWrapper.nativeElement, "max-height", this.expandHeight);
  }
}

tab1.page.htmlの変更

Before
<ion-header [translucent]="true">
  <ion-toolbar>
    <ion-title>
      Tab 1
    </ion-title>
  </ion-toolbar>
</ion-header>

<ion-content [fullscreen]="true">
  <ion-header collapse="condense">
    <ion-toolbar>
      <ion-title size="large">Tab 1</ion-title>
    </ion-toolbar>
  </ion-header>

  <app-explore-container name="Tab 1 page"></app-explore-container>
</ion-content>
After
<ion-header [translucent]="true">
  <ion-toolbar>
    <ion-title>
      Tab 1
    </ion-title>
  </ion-toolbar>
</ion-header>

<ion-content>
  <ion-card (click)="expandItem(item)" *ngFor="let item of items">
    <ion-card-header>
      <ion-card-title>隣のトトロ</ion-card-title>
    </ion-card-header>

    <ion-card-content>
      <app-expandable expandHeight="100px" [expanded]="item.expanded">
        <p>
          とっとろ、と、とーろ
        </p>
        <p>
          とっとろ、と、とーろ
        </p>
      </app-expandable>
    </ion-card-content>
  </ion-card>
</ion-content>

tab1.page.tsの変更

Before
import { Component } from '@angular/core';

@Component({
  selector: 'app-tab1',
  templateUrl: 'tab1.page.html',
  styleUrls: ['tab1.page.scss']
})
export class Tab1Page {

  constructor() {}

}
After
import { Component } from '@angular/core';

@Component({
  selector: 'app-tab1',
  templateUrl: 'tab1.page.html',
  styleUrls: ['tab1.page.scss']
})
export class Tab1Page {
  public items: any = [];

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

  expandItem(item): void {
    if (item.expanded) {
      item.expanded = false;
    } else {
      this.items.map(listItem => {
        if (item == listItem) {
          listItem.expanded = !listItem.expanded;
        } else {
          listItem.expanded = false;
        }
        return listItem;
      });
    }
  }
}
Meditation Tools開発者
絹田 雅
複数の瞑想を学ぶことができるMeditation Toolsの開発者。 売上は人権段階を通じた寄附により社会をより良くすることに使われます。 利用はこちら
twitter-timeline