Plusieurs types de menus

On peut essentiellement concevoir deux types de menus :

  1. les « side menus » : ils font toute la hauteur de la fenêtre et apparaissent en coulissant horizontalement. En Ionic, ils utilisent la balise <ion-menu>.

  2. les menus déroulants, qui se déroulent verticalement. En Ionic, ils utilisent la notion de popover.

Les side menus

Un side menu est censé représenter un menu pour toute l’application, c’est-à-dire que c’est ce même menu qui apparaît dans toutes les pages. C’est pourquoi on ne le définit pas dans chaque page mais plutôt directement dans le fichier app.component.html.

app.component.html
<ion-app>
  <ion-menu side="end" menuId="first" contentId="main">
    <ion-header>
      <ion-toolbar color="primary">
        <ion-title>Start Menu</ion-title>
      </ion-toolbar>
    </ion-header>
    <ion-content>
      <ion-list>
        <ion-item>Item 1</ion-item>
        <ion-item>Item 2</ion-item>
      </ion-list>
    </ion-content>
  </ion-menu>
  <ion-router-outlet id="main"></ion-router-outlet>
</ion-app>

Notez que le ion-menu a une propriété contentId dont la valeur correspond à l’id du router-outlet en bas de fichier. La propriété menuId sera utile par la suite si l’on déclare plusieurs side menus.

Attention

Tous les side menus doivent être déclarés à la racine de l’application, donc dans la balise <ion-app></ion-app> mais ils doivent également se trouver en dehors de balises ion-header, ion-content et ion-footer.

Si l’on veut faire apparaître ce menu dans une page, il suffit d’insérer dans son header une balise ion-side-menu, comme indiqué ci-dessous :

home.page.html
<ion-header [translucent]="true">
  <ion-toolbar>
    <ion-title>Blank</ion-title>
    <ion-buttons slot="end">
      <ion-menu-button></ion-menu-button>
    </ion-buttons>
  </ion-toolbar>
</ion-header>

<ion-content [fullscreen]="true">
  <p><a routerLink="/cours">vers la page cours</a></p>
</ion-content>

Ici, la balise <ion-buttons></ion-buttons> permet de stocker des boutons. Le fait de lui attribuer une propriété slot dont la valeur est à end permet de placer les boutons sur la droite de l’écran. Si on les veut à gauche, il suffit de remplacer end par start.

Le résultat obtenu est le suivant (menu en haut à droite) :

Un bouton de side-menu

Et si l’on clique sur le bouton du side menu :

Le side-menu déroulé

Comment créer plusieurs side menus

L’idée consiste à tous les créer à la racine de l’application, comme vu dessus, maison on leur donne à chacun un menuId différent :

app.component.ts
<ion-app>
  <ion-menu side="end" menuId="first" contentId="main">
    <ion-header>
      <ion-toolbar color="primary">
        <ion-title>Start Menu</ion-title>
      </ion-toolbar>
    </ion-header>
    <ion-content>
      <ion-list>
        <ion-item>Item 1</ion-item>
        <ion-item>Item 2</ion-item>
      </ion-list>
    </ion-content>
  </ion-menu>

  <ion-menu side="end" menuId="second" contentId="main">
    <ion-content>
      <ion-list>
        <ion-item>Item XXX 1</ion-item>
        <ion-item>Item XXX 2</ion-item>
      </ion-list>
    </ion-content>
  </ion-menu>

  <ion-router-outlet id="main"></ion-router-outlet>
</ion-app>

Dans une page, pour sélectionner le bon menu, tout se passe, non plus dans le template HTML, mais dans le TypeScript : on indique via un MenuController quel est le menu qui nous intéresse :

home.page.ts
import { Component, OnInit } from '@angular/core';
import {
  IonHeader, IonToolbar, IonTitle, IonContent,
  IonList, IonItem,
  IonButtons, IonMenuButton, MenuController
} from '@ionic/angular/standalone';
import {MonServiceService, MyInfo} from "../mon-service.service";
import {RouterLink} from "@angular/router";

@Component({
  selector: 'app-home',
  templateUrl: 'home.page.html',
  styleUrls: ['home.page.scss'],
  standalone: true,
  imports: [
    IonHeader, IonToolbar, IonTitle, IonContent,
    IonItem, IonList,
    RouterLink, IonButtons, IonMenuButton],
})
export class HomePage implements OnInit {
  info!: MyInfo;

  constructor(private mon_service: MonServiceService,
              private menu: MenuController) {}

  async ngOnInit() {
    this.mon_service.getInfos().subscribe(res => {this.info = res;});

    await this.menu.enable(false);         // on invalide tous les menus
    await this.menu.enable(true, 'third'); // on sélectionne celui qui nous intéresse
  }
}

Side menus : organisation du code

Comme vu précédemment, on doit insérer le code HTML des menus à l’intérieur des balises <ion-app></ion-app>. Mais si l’application est grosse et qu’il y a beaucoup de side menus différents, cela diminue la maintenabilité du code. On peut pallier cela en organisant de manière plus fine le code. Voici une telle possibilité d’organisation :

  1. on place chaque menu dans un composant ;

  2. on insère entre les balises <ion-app></ion-app> celles des composants des menus.

Ainsi, pour nos deux menus ci-dessus, on crée deux composants menu-first et menu-second :

menu-first-component.html
<ion-menu side="end" menuId="first" contentId="main">
  <ion-header>
    <ion-toolbar color="primary">
      <ion-title>Start Menu</ion-title>
    </ion-toolbar>
  </ion-header>
  <ion-content>
    <ion-list>
      <ion-item>Item 1</ion-item>
      <ion-item>Item 2</ion-item>
    </ion-list>
  </ion-content>
</ion-menu>
menu-first-component.ts
import { Component, OnInit } from '@angular/core';
import {
  IonContent, IonHeader,
  IonItem, IonList,
  IonMenu, IonTitle, IonToolbar
} from '@ionic/angular/standalone';

@Component({
  selector: 'app-menu-first',
  templateUrl: './menu-first.component.html',
  styleUrls: ['./menu-first.component.scss'],
  imports: [
    IonMenu,
    IonHeader,
    IonToolbar,
    IonContent,
    IonTitle,
    IonList,
    IonItem
  ],
  standalone: true
})
export class MenuFirstComponent  implements OnInit {
  constructor() { }
  ngOnInit() {}
}
menu-second-component.html
<ion-menu side="end" menuId="second" contentId="main">
  <ion-content>
    <ion-list>
      <ion-item>Item XXX 1</ion-item>
      <ion-item>Item XXX 2</ion-item>
    </ion-list>
  </ion-content>
</ion-menu>
menu-second-component.ts
import { Component, OnInit } from '@angular/core';
import {
  IonMenu, IonHeader, IonContent,
  IonToolbar, IonTitle,
  IonItem, IonList} from '@ionic/angular/standalone';

@Component({
  selector: 'app-menu-second',
  templateUrl: './menu-second.component.html',
  styleUrls: ['./menu-second.component.scss'],
  imports: [
    IonMenu, IonHeader, IonContent,
    IonToolbar, IonTitle,
    IonItem, IonList
  ],
  standalone: true
})
export class MenuSecondComponent  implements OnInit {
  constructor() { }
  ngOnInit() {}
}

Il suffit alors d’inclure les balises de ces composants dans le fichier app.component.html :

app.component.html
<ion-app>
  <app-menu-first></app-menu-first>
  <app-menu-second></app-menu-second>

  <ion-router-outlet id="main"></ion-router-outlet>
</ion-app>

Les menus déroulants

Ces menus se construisent en 4 étapes :

  1. Il faut créer un composant contenant le code du menu.

  2. Dans le html de la page, on crée un bouton pour ouvrir le menu.

  3. Dans le TypeScript de la page, on ajoute une méthode créant un PopoverController.

  4. Si l’on souhaite utiliser une icône pour symboliser le menu, on la crée dans le TypeScript de la page.

Étape 1 : créer le composant du menu :

Commençons par le TypeScript du composant menu. Pour se dérouler, le menu va faire appel à un popover. Pour rendre cela possible, il suffit de passer par dependency injection au constructeur une instance de PopoverControler. Le menu servant, en principe, à sélectionner quelque chose, il faut pouvoir retourner ce quelque chose à la page qui contient le menu. Pour cela, on crée une callback, appelée ici closePopover. Grâce à la méthode dismiss du popover, on retourne la valeur qui nous intéresse.

dropdown-menu-component.ts
import { Component, OnInit } from '@angular/core';
import {IonContent, IonItem, IonLabel, IonList, PopoverController} from "@ionic/angular/standalone";
import {CommonModule} from "@angular/common";

@Component({
  selector: 'app-dropdown-menu',
  templateUrl: './dropdown-menu.component.html',
  styleUrls: ['./dropdown-menu.component.scss'],
  imports: [IonContent, IonLabel, IonList, IonItem, CommonModule],
  standalone: true
})
export class DropdownMenuComponent  implements OnInit {
  // le champ ci-dessous va permettre à la page qui contient le menu
  // de lui transmettre des informations quand celui-ci s'ouvrira
  myprop! : string;
  nb = [1, 2, 3]; // les items du menu déroulant

  constructor(private popover: PopoverController) { }
  ngOnInit() {}

  // la méthode dismiss renverra la valeur de event à la page contenant le menu
  closePopover(event: any): void {this.popover.dismiss(event);}
}

Le code HTML du menu est le suivant :

dropdown-menu-component.html
<ion-content>
  <ion-label>Myprop: {{myprop}}</ion-label>
  <ion-list>
    <!-- quand on clique, on appelle la fonction qui renvoie l'item
         sur lequel on a cliqué -->
    <ion-item *ngFor="let elt of nb"
              (click)="closePopover(elt)">
      Menu itel {{elt}}
    </ion-item>
  </ion-list>
</ion-content>

Ici, le champ myprop illustre comment la page peut transmettre des informations au menu (vous verrez cela dans l’étape 3). Pour afficher les différents items, on a choisit d’utiliser la directive *ngFor, c’est pourquoi, dans le TypeScript du menu, on a dû importer le CommonModule d’Angular.

Notez le event binding réalisé dans chacun des items du menu. C’est cela qui permet d’appeler le popover.dismiss() qui ferme le menu et transmet l’item sélectionné à la page contenant le menu.

Étape 2 : ouvrir le menu dans le HTML de la page :

On symbolise le menu via un bouton. Afin d’alléger les affichages, on se contente en général de mettre une icône. C’est ce qui est fait en dessous. La balise <ion-icon></ion-icon> permet d’afficher des icônes. Par event binding, on associe ici au bouton/icône du menu une callback qui sera appelée si on clique sur le bouton. La propriété name de l’icône permet de spécifier quelle image on souhaite afficher (ici, un + blanc dans un cercle noir).

home.page.html
<ion-header [translucent]="true">
  <ion-toolbar>
    <ion-title>Blank</ion-title>
    <ion-buttons slot="end">
      <ion-button (click)="openMenu($event)">
        <ion-icon slot="icon-only"
                  name="add-circle"></ion-icon>
      </ion-button>
    </ion-buttons>
  </ion-toolbar>
</ion-header>

<ion-content [fullscreen]="true">
  <p><a routerLink="/cours">vers la page cours</a></p>
</ion-content>

Étapes 3 et 4 : créer le PopoverController et l'icône dans la page :

Dans le TypeScript de la page, on a plusieurs actions à effectuer :

  1. Dans le constructeur, on passe par dependency injection une instance, appelée ici popover du PopoverController. C’est cette instance qui va gérer l’ouverture et la fermeture du menu.

  2. On crée la callback qui sera appelée quand on cliquera sur l’icône (+) du menu. Ici, celle-ci s’appelle openMenu. Cette méthode commence par créer la fenêtre du menu. Elle prend en paramètre, notamment, le composant à afficher dans cette fenêtre (ici, notre composant dropdown-menu), un css à appliquer, mais, surtout, une propriété componentProps qui sera transmise au composant dropdown-menu. Ici, on passe une valeur pour myprop. Notez que ce champ est un des attributs de DropdownMenuComponent. C’est donc ainsi que la page transmet des informations à son menu.

    Notez également que la méthode create renvoie une Promise et c’est pour cela que vous voyez un then() : quand la fenêtre aura été créée, on va demander à l’afficher (popoverElement.present()). On indique également ce qu’il faudra faire quand on fermera le menu (popoverElement.onDidDismiss()).

  3. Si l’on souhaite afficher des icônes, il faut les charger (en mode standalone, elles ne le sont pas par défaut). C’est le rôle de la fonction addIcons exécutée dans le ng OnInit.

home.page.ts
import { Component, OnInit } from '@angular/core';
import {
  IonHeader, IonToolbar, IonTitle, IonContent,
  IonList, IonItem,
  IonButtons, IonMenuButton, IonButton, IonIcon, PopoverController
} from '@ionic/angular/standalone';
import {MonServiceService, MyInfo} from "../mon-service.service";
import {RouterLink} from "@angular/router";
import {DropdownMenuComponent} from "../dropdown-menu/dropdown-menu.component";
import {addIcons} from "ionicons";
import {addCircle} from "ionicons/icons";

@Component({
  selector: 'app-home',
  templateUrl: 'home.page.html',
  styleUrls: ['home.page.scss'],
  standalone: true,
  imports: [
    IonHeader, IonToolbar, IonTitle, IonContent,
    IonItem, IonList,
    RouterLink, IonButtons, IonMenuButton, IonButton, IonIcon
  ],
})
export class HomePage implements OnInit {
  info!: MyInfo;

  constructor(private mon_service: MonServiceService,
              private popover: PopoverController) {}

  async ngOnInit() {
    this.mon_service.getInfos().subscribe(res => {this.info = res;});

    addIcons({addCircle});
  }

  openMenu(myevent: MouseEvent) : void {
    // on crée le popup contenant le code du menu
    this.popover.create({
      component: DropdownMenuComponent, // on précise quoi inclure
      showBackdrop: true,
      cssClass: 'my-menu-class', // on peut préciser un css
      event: myevent,            // l'événement clic souris
      componentProps: { // ici, on indique les propriétés que l'on souhaite
        myprop: 'xxxx'  // initialiser dans le composant DropdownMenuComponent
      }
    }).then((popoverElement) => {
      popoverElement.present(); // on affiche le menu
      popoverElement.onDidDismiss().then((res) => {
        console.log(res);
      });
    });
  }
}

Le rendu du menu :

L'icône du menu en haut à droite Le menu déroulé
 
© C.G. 2007 - 2024