[Solved] Child page specific Menu


#1

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?


#2

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">


#3

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


#4

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.


#5

This came in handy. Thanks a million.


#6

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.


#7

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.


#8

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


#9

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


#10

@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!


#11

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


#12

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