Ionic(Angular)でアコーディオンを実装する方法です。
ポイント
- Componentを自作して、それを読み込むようにします
- これによりいろいろな場所で使い回しができるようになります
結果
こんな感じでクリックするとカードが広がるようになります。
環境
Ionic 4, 5, 6で動作検証しています。
流れ
- コンポーネントを作成し、HTMLとSCSSを記載します
- 読み込みたい場所で作成したコンポーネントをmoduleに登録します
- 使いたい場所の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;
});
}
}
}