大田区から発信するゆるゆる日記

主にITエンジニアに関する備忘録日記。たまに趣味も。何か不備があればコメント頂けると幸いです。Twitterアカウント https://twitter.com/ryuzan03

【Ionic/Angular】ion-navでネストがあるナビゲーション機能を実装

※下記の内容に不備がありましたら、コメント頂けると幸いです。また、下記の内容をご使用頂ける場合は自己責任でお願いします。

【目次】

概要

ion-navを使ってrouterを汚さずにページ遷移ができるようにしました。

ion-navのコンポーネントドキュメントだとJavascriptを使うので、私はコンポーネントを使う方法で実装してみました。何故Javascriptを使うのか理由が知りたい...
ion-nav - Ionic Framework 日本語ドキュメンテーション

また、Ionic5から実装されたion-nav-linkも使ってみました(代わりにion-nav-push・back・set-rootがなくなりましたね)
How to Upgrade Your App to Ionic 5 - Ionic AcademyIonic Academy

ちなみに、Angular Materialのmat-treeとion-navを使ってもできそうでしたが、ion-navが良い感じに使いこなせなくて私は途中で挫折しました。あと、ion-tree-listとかあったんですが、公式ではなかったので使うのやめました。ion-treeとかありそうなのにないんですね。

ion-navでネストがあるナビゲーション機能を実装

必要なコンポーネントとデータを作成

ターミナル

$ ionic g component parent
$ ionic g component children
$ ionic g service datas

data.ts

export interface Data {
  name: string;
  children?: Data[];
}


ここのデータ構造は重要です。

datas.ts

import { Data } from './data';

export const DATAS: Data[] = [
  {
    name: 'カテゴリー1',
    children: [
      {name: '子カテゴリー1'},
      {name: '子カテゴリー2'},
    ]
  },
  {
    name: 'カテゴリー2',
    children: [
      {name: '子カテゴリー1'},
      {name: '子カテゴリー2'},
    ]
  },
...
]

datas.service.ts

import { Injectable } from '@angular/core';
import { Data } from './data';
import { DATAS } from './datas';

@Injectable({
  providedIn: 'root'
})
export class DatasService {
  getDatas(): Data[] {
    return DATAS;
  }
}



parentコンポーネントとchildrenコンポーネントを修正

ここが今回最も重要な章です。

プログラミングしていくイメージは、ネストがあるデータやコンポーネントを親(page1・ParentComponent)から子(ChildrenCompnent)に順に渡すイメージです。

その時に使うのがion-navであったりion-nav-linkだったりします。親からデータを受け取る時にはNavParamsオブジェクトを使います。

children.component.ts

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

@Component({
  selector: 'ls-children',
  templateUrl: './children.component.html',
  styleUrls: ['./children.component.scss'],
})
export class ChildrenComponent implements OnInit {
  children: [{[key: string]: string}];

  constructor(private navParams: NavParams) { }

  ngOnInit() {
    this.children = this.navParams.data.children;
  }

}

children.component.html

<ion-header>
  <ion-toolbar>
    <ion-buttons slot="start">
      <ion-back-button></ion-back-button>
    </ion-buttons>
    <ion-title>
      子要素
    </ion-title>
  </ion-toolbar>
</ion-header>

<ion-content  fullscreen>
  <ion-list>
    <ng-container *ngFor="let child of children"> 
      <ion-item button>
        <ion-icon name="arrow-forward" slot="end"></ion-icon>
        <ion-label>
          {{child.name}}
        </ion-label>
      </ion-item>
    </ng-container>
  </ion-list>
</ion-content>


parent.component.ts

import { Component, OnInit } from '@angular/core';
import { NavParams } from '@ionic/angular';
import { ChildrenComponent } from '../children/children.component';

@Component({
  selector: 'ls-parent',
  templateUrl: './parent.component.html',
  styleUrls: ['./parent.component.scss'],
})
export class ParentComponent implements OnInit {
  datas: {[key: string]: any} = {};
  nextPage = ChildrenComponent;

  constructor(private navParams: NavParams) { }

  ngOnInit() {
    this.datas = this.navParams.data;
  }

}

children.component.html

<ion-header>
  <ion-toolbar>
    <ion-title>
      トップ
    </ion-title>
  </ion-toolbar>
</ion-header>

<ion-content  fullscreen>
  <ion-list>
    <ng-container *ngFor="let data of datas"> 
      <ion-nav-link [component]="nextPage" [componentProps]="{'children': data.children}">
        <ion-item button>
          <ion-icon name="arrow-forward" slot="end"></ion-icon>
          <ion-label>
            {{data.name}}
          </ion-label>
        </ion-item>
      </ion-nav-link>
    </ng-container>
  </ion-list>
</ion-content>



pageにion-navを追加

page1.page.ts

import { Component, OnInit } from '@angular/core';
import { Dictionaries } from 'src/app/shared/models/dictionaries';
import { DatasService } from '../datas.service';
import { ParentComponent } from '../parent/parent.component';

@Component({
  selector: 'app-page1',
  templateUrl: 'page1.page.html',
  styleUrls: ['page1.page.scss']
})
export class Page1Page implements OnInit {
  datas: Datas[];
  navTop: any = ParentComponent;

  constructor(
    private datasService: DatasService,
  ) {}

  ngOnInit() {
    this.getDatas();
  }

  getDatas(): void {
    this.datas = this.datasService.getDatas();
  }

}

page1.page.html

<ion-content [fullscreen]="true">
  <ion-nav [root]="navTop" [rootParams]="datas"></ion-nav>
</ion-content>



応用ヒント

さらにネストが深い場合は、childrenにion-nav-linkを追加して、componentプロパティに自身(children)を反映させる変数を定義しましょう。

example

nextPage = ChildrenComponent


途中でネストを抜け出す場合はngIfを利用します。

example

<ng-container *ngIf="data.children">
...
</ng-container>


もしくは、ネストの深さが一定なら、それだけのコンポーネントを作るのも良さそうです。私はネストが1つだけだったので、parentコンポーネント・childrenコンポーネント・detailコンポーネントの3つで完結させました。


今後に向けて

Ionicは日本語の記事が少ないのでなかなか苦戦してます。。。

少しずつ慣れてはきてるので引き続き英語に負けずに頑張っていきたいと思います。


参考

素晴らしい記事に感謝致します。
ion-nav - Ionic Framework 日本語ドキュメンテーション
ionic-docs/index.html at master · ionic-team/ionic-docs · GitHub
ion-nav-link - Ionic Framework 日本語ドキュメンテーション
How to Upgrade Your App to Ionic 5 - Ionic AcademyIonic Academy