[Solved] Child page specific Menu

I have a menu that is only available and populated by a child page.

It seems that a menu has to be setup within the app.html file, e.g.

 <ion-menu side="right" persistent="true" [content]="content">
  <ion-header>
<ion-toolbar>
  <ion-title>Menu</ion-title>
</ion-toolbar>
  </ion-header>

  <ion-content>
  	<p>Menu content</p>
  </ion-content>

</ion-menu>

<!-- Disable swipe-to-go-back because it's poor UX to combine STGB with side menus -->
<ion-nav [root]="rootPage" #content swipeBackEnabled="false"></ion-nav>

However, my data is not available at that point - it comes from a data object within the child page: child-page.html.

I see there are a couple (1, 2) of threads never answered on this topic, is this a limitation of Ionic 2 Menus? That they have to be app-wide and cannot be page specific?

Ideally I would be able to define specific markup in my child-page.html file to show in the menu.

@mhartington, youā€™re always super helpful - any ideas?

Sorted. For anyone else trying to do this, you do not need your menu set in app.html, it can be in any page.

Code example, /src/pages/child-page/child-page.html:

<ion-header [style.background]="pageThemeColour">

  <ion-navbar>
    <ion-title>{{pageTitle}}</ion-title>

    <ion-buttons end>
      <button ion-button icon-only menuToggle="TipMenu">
        <ion-icon name="menu"></ion-icon>
      </button>
    </ion-buttons>

  </ion-navbar>

</ion-header>

<ion-content padding #content>
	 
	 <article *ngIf="articles.length === 0">
	 	<p>No articles found...</p>
	 </article>

	 <article *ngIf="articles.length > 0">

	 	<div [innerHTML]="selectedArticle.post_image"></div>

		<h2>{{selectedArticle.post_title}}</h2>

		<h3>{{selectedArticle.post_date}}</h3>

		<div class="article__body" [innerHTML]="selectedArticle.post_content"></div>

	 </article>


</ion-content>

<!-- page specific menu -->
<ion-menu id="TipMenu" persistent="true" side="right" [content]="content">

  <ion-card *ngFor="let article of articles">
    <div class="card-image" [innerHTML]="article.post_image"></div>
    <h2 class="card-title">{{article.post_title}}</h2>
    <h3 class="card-subtitle">{{article.post_date}}</h3>

  </ion-card>
</ion-menu>

Awesome. Awesome. Awesome. :slight_smile:

Oh, and for anyone beating their head against a wall because the button with menutoggle disappears when on the child page, make sure to add persistent=ā€œtrueā€ to the ion-menu, e.g.

<ion-menu id="TipMenu" persistent="true" side="right" [content]="content">

4 Likes

Donā€™t see how this is a page specific menu if there is already a menu defined in app.html?

I was trying to avoid having to place the menu in app.html. There was one there, as I was following an Ionic 2 tutorial, but I wanted it to appear/be used solely in a child page.

Now the Menu is in a child page and populated with data from there, which is great. As I said, I expect itā€™s due to my limited understanding of menus in Ionic 2, but I wouldnā€™t expect to have to put persistent=ā€œtrueā€ attribute in the child page Menu, in order for the menuToggle button to show - seemed like a bugā€¦

The title of this thread is a bit misleading, so Iā€™ve updated it to better reflect whatā€™s been achieved.

2 Likes

This came in handy. Thanks a million.

Your welcome @semoju

Actually, this approach seems to cause animation issues in iOS, and probably is not what Ionic intend, although it does workā€¦ Instead, from what I can see, the menu should be placed in the appā€™s html (e.g. src/app/app.html) and controlled through Events.

This is a shame, as I liked the idea of keeping pages and their menus gathered together and code out of the /src/app/app.ts, but heyā€¦

Hereā€™s how I ended up coding it:

app.html

<!-- article menu -->
<ion-menu id="ArticleMenu" persistent="true" side="right" [content]="content">

<ion-scroll scrollY="true">

  <ion-card class="background-image-card" *ngFor="let article of articleMenuArray; let i = index" (click)="articleMenuChange(i)">
    <div class="card-image background-image-card__image-wrapper" [innerHTML]="article.post_image"></div>

      <div class="background-image-card__titles">

        <h2 class="card-title background-image-card__title">{{article.post_title}}</h2>

        <h3 class="card-subtitle  background-image-card__subtitle" [innerHTML]="utils.dateFromString(article.post_date)"></h3>
        
      </div>

  </ion-card>

  </ion-scroll>

</ion-menu>

<ion-nav [root]="rootPage" swipeBackEnabled="false" #content></ion-nav>

app.components.ts

import { Component } from '@angular/core';
import { HomePage } from '../pages/home/home';
import { StatusBar, Splashscreen } from 'ionic-native';

import { Platform, Events, Nav, AlertController } from ā€˜ionic-angularā€™;

@Component({
  templateUrl: 'app.html'
})

export class MyApp {

  private rootPage = HomePage;
  private articleMenuArray = [];


  constructor(public platform: Platform, public events: Events) {
    platform.ready().then(() => {
      console.log('platform ready. set status bar colour and hide splash now.', utils);
      
      StatusBar.styleDefault();
      Splashscreen.hide();
      // setup menu subscription
      this.setupArticleMenuSubscribe();
    });
  }
  
  // article menu
  setupArticleMenuSubscribe(){
    console.log('setupArticleMenuSubscribe');
  // sub scribe to the populate event of the menu
    this.events.subscribe('articleMenu:populate', articleArray => {
      this.articleMenuArray = articleArray;
    });
  }

  articleMenuChange(index){
  // notify any observers that an option has been clicked in the menu
    this.events.publish('articleMenu:change', index);
  }

}

In the page that does need a menu, you can add:

articles-page.ts

import { Component } from '@angular/core';
import { MenuController, Events } from 'ionic-angular';

@Component({
  selector: 'articles-page',
  templateUrl: 'articles.html'
})
export class ArticlesPage {
	    private articles;
	constructor(menuCtrl:MenuController, public events: Events) {
	    this.articles = ['article 1', 'article 2', 'article 3', 'article 4', 'article 5']; // article data to populate menu in the app.html
	}
	// load page
	ionViewDidLoad() {
	    	// notify any observers that the data is ready to populate and pass it
	    this.events.publish('articleMenu:populate', this.articles);

	    	// subscribe to the change event of the menu (an option being selected)
	    this.events.subscribe('articleMenu:change', (index) => {this.showArticle(index);});
	    	// enable the menu on this page
	    this.menuCtrl.enable(true, 'ArticleMenu');
	}
	// unload page
	ionViewWillUnload(){
	    	// disable the menu when you leave this page
	   this.menuCtrl.enable(false, 'ArticleMenu');
	}
	    showArticle(index){
	    // do something with the article here, e.g. display it
	   console.log('Show article:', this.article[index]);
	    }
}

I hope this helps someone out.

4 Likes

I can confirm this: Even though the menu would be cleaner within the child component and it works in browsers, it causes animation problems in iOS. So @nebrekabā€™s approach should be used to support all devices.

Thanks alot :grinning: you saved my time !!!

Finally, solved it. Thank you. Though, Its such an un-intuitive paradigm shift compared to regular software architecture design. Thanks again.

@nebrekab this was pretty helpfulā€¦ now that youā€™ve solved this, do you have any thoughts on the right way to maintain different menus that are child page dependent?

for instance

page a, b and c would show menu 1 (on left)
page d, e and f would show menu 2 (on left)

cheers!

Use a provider I would say
Which gives the content for each menu
Which then is loaded in the menu using ngFor
Trighered by Events

Which also seems to me as a nicer solution for popularing the menu with articles in the earlier example

And possibly an neater alternative to Events is a provider which emits new menu values using an Observable to which app.ts can subscribe to, triggered by child method calls to the provider