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
.
<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 :
<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) :
Et si l’on clique sur le bouton du side menu :
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 :
<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 :
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
}
}
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 :
on place chaque menu dans un composant ;
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 :
<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>
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() {}
}
<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>
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
:
<ion-app>
<app-menu-first></app-menu-first>
<app-menu-second></app-menu-second>
<ion-router-outlet id="main"></ion-router-outlet>
</ion-app>
Ces menus se construisent en 4 étapes :
Il faut créer un composant contenant le code du menu.
Dans le template HTML de la page, on crée un bouton pour ouvrir le menu.
Dans le TypeScript de la page, on ajoute une méthode créant un PopoverController.
Si l’on souhaite utiliser une icône pour symboliser le menu, on la crée dans le TypeScript de la page.
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 PopoverController. 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 popoverController, on retourne la valeur qui nous intéresse.
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 popoverController: PopoverController) { }
ngOnInit() {}
// la méthode dismiss renverra la valeur de event à la page contenant le menu
closePopover(event: any): void {this.popoverController.dismiss(event);}
}
Le code HTML du menu est le suivant :
<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é -->
@for(elt of nb; track elt) {
<ion-item (click)="closePopover(elt)">
Menu item {{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).
Notez le event binding réalisé dans chacun des items du menu. C’est cela qui permet d’appeler le popoverController.dismiss() qui ferme le menu et transmet l’item sélectionné à la page contenant le menu.
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).
<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>
Dans le TypeScript de la page, on a plusieurs actions à effectuer :
Dans le constructeur, on passe par dependency injection une instance, appelée ici popoverController du PopoverController. C’est cette instance qui va gérer l’ouverture et la fermeture du menu.
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()).
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.
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 popoverController: 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.popoverController.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);
});
});
}
}
Créez maintenant un menu déroulant spécifique à la page des cours. Pour cela, créez un nouveau composant Dropdown-menu-cours. Pour le moment, l’objectif est de produire l’affichage du popup. Laissez donc la partie HTML du menu à « Menu works! ». Testez que vous voyez bien le menu/popup apparaître quand vous cliquez sur le bouton censé l’ouvrir.
Faites en sorte que le contenu du menu soit un <ion-radio-group> (cf. l’URL https://ionicframework.com/docs/api/radio-group) dont les éléments permettent de retrier la liste des cours affichés :
par nom de cours,
par date du dernier post,
par nombre de topics.
Là encore, l’objectif de cette étape est uniquement d’avoir les bons affichages. Ce n’est pas encore de sélectionner le tri à réaliser. On veut toutefois que les tris soient passés au DropDownMenuCoursComponent par CoursPage lors de sa création (cf. l’attribut componentProps du this.popoverController.create). Enfin, quand on clique sur un des trois tris, il faut que cela ferme le menu.
Quand l’utilisateur clique sur un item du menu, on souhaite récupérer son nom ou son index dans la page CoursPage et l’afficher dans la console. Réalisez cela en exploitant la méthode onDidDismiss. Regardez bien ce qui est affiché dans la console.
Sauvegardez le tri sélectionné par l’utilisateur dans un champ du CoursPage, puis passez la valeur de ce champ à la création du DropdownMenuCoursComponent via l’attribut componentProps (cf. l’étape précédente). Enfin, modifiez le DropdownMenuCoursComponent pour que le tri passé via componentProps apparaisse comme sélectionné (cf. l’attribut value de <ion-radio-group>). Maintenant, quand vous sélectionnez dans votre menu un nouveau tri et que vous rouvrez le menu, c’est bien celui-ci qui est sélectionné.
Dans votre page CoursPage, vous pouvez maintenant retrier les données selon le tri sélectionné. Pour cela, vous pourrez avantageusement exploiter la méthode sort des tableaux javascript.
Dans le forum de démonstration, si vous sélectionnez un tri donné et si celui-ci était déjà sélectionné, on change l’ordre du tri : croissant/décroissant. Vous pouvez essayer de réaliser cela dans votre application. Pour cela, il suffit de remarquer que la méthode sort prend en argument une fonction fnCompare(a,b) : number qui, étant donné deux éléments du tableau a et b renvoie :
un nombre strictement négatif si a doit être classé avant b ;
0 si a et b sont identiques du point de vue du tri ;
un nombre strictement positif si b doit être classé avant b.
Pour alterner tris croissants et décroissants, il suffit donc de créer une variable, appelons-la sensTri initialement égale à -1. Avant chaque tri, on multiplie cette variable par -1. Donc, lors du 1er tri, elle vaut 1, lors du 2ème, elle vaut -1, lors du 3ème, elle vaut 1, etc. Si on modifie la fonction fnCompare(a,b) de manière à ce que ses valeurs de retour soient multipliées par sensTri, chaque fois qu’on appellera la méthode sort en passant en paramètre fnCompare, on alternera des tris croissants et décroissants.
Vous pouvez maintenant tirer parti de cette astuce pour alterner vos tris.