Google maps making sidemenu unusable

Hi,

I’m currently working on my first ionic app (ionic 2) starting with the ionic conference app as a template and I’m trying to use the native google maps plugin. I’m basically using the code from http://www.joshmorony.com/integrating-native-google-maps-into-an-ionic-2-application/ for now, but if I open the side menu, the map intercepts all clicks and I can’t interact with the menu at all. Looking around, apparently it should be possible to solve this by using map.setClickable(false) when the menu is opened and true when it is closed again, but I can’t seem to figure out either how to trigger sth on the menu opening (MenuController.isOpen() only seems to trigger the moment I tap on the menu item for the page containing the map) or how to set the map to clickable (this.setClickable(false) at the bottom of loadMap() (see linked code) does not appear to do anything. I’m guessing I’m probably misunderstanding something about how ionic works, but would be grateful for any suggestions.

You’re on the right track, this plugin in particular is hard to get right because it’s constantly fighting with the html for event focus. I haven’t tried it with a side menu, just tabs. The menu has an ionOpen output event, so for you that’s the best place to trigger a call to setClickable(false), then use ionClose to do the opposite.

You will find that you need to do cross page communication with the map plugin so you can either using Events, an Observable subscriber or an injectable service to talk to the map plugin when you need it to give up/regain focus.

Thanks for the quick response! I came across the ionOpen event but couldn’t figure out how to use output events…
I’m guessing by injectable service you mean a provider, i.e. I should create a persistent map module so that it can be accessed from anywhere within the app?

Yes, exactly, having a provider that holds a reference to your map plugin.

My app that uses the native google maps doesn’t currently use the ionic wrapper for that plugin but it does have a generic mapping provider which then switches between google maps web/google maps native/leaflet as required: https://github.com/openchargemap/ocm-labs/blob/master/App/Ionic2/ocm-app/src/providers/mapping/Mapping.ts

Brilliant, thanks! I’ve definitely gotten a lot farther now. I’ve set up a provider and injected it into app.component.js so that I can access it in the global scope. I’ve also successfully applied setClickable to it, so that the navigation works again. The problem is, that in the initialization I have to bind it to a dom object so it only works if the page with the map is the first one loaded. How can I initialize it in a way that it’s placed inside the appropriate container on the map page but still accessible globally so I can access it from the menu’s output events?

If the map is not initialised yet (is null) then you don’t have to call setClickable on it yet, so it’s probably fine if the full init of the map provider doesn’t happen until the page your maps is on gets loaded (ngOnInit maybe?). Your method in your provider to toggle setClickable could just check if map is null or not? In my app the map is the first page though.

I think I’m always there, but I’m still having scoping problems. In order for the output events in the menu template:
(ionOpen)="menuOpened()" (ionClose)="menuClosed()"
not to throw an error
Property 'menuOpened' does not exist on type
I’m having to put menuOpened() and menuClosed() into app.module.ts. If I put it there, I don’t know how to initialize it only on the map page…

Yep, so you need those methods in your app so that the menu template can see those methods to call them, but you can inject the map provider into both the main app and the map page, so they both see the same instance of the map provider code. Then you want something like:

if (this.map!=null) this.map.setClickable(true); In your map provider, that way it will do nothing if the maps is not yet initialized.

Sorry, maybe I’m just being a bit stupid right now. Even if I inject the map provider into app.component.ts, i.e.
import { MapProvider } from '../providers/map-provider' @Component({ templateUrl: 'app.template.html', providers: [MapProvider] })
and I have the functions menuOpened() and menuClosed() inside the provider, they’re still not seen by the calls inside the main template. If I put them inside the main app without initializing the map there, it complains that
Property 'map' does not exist on type

When you inject a provider into a component the actual injection takes place in the component constructor when angular creates your component:

export class MyApp {
  
  constructor(public mapping:MapProvider) { 
//this is where the provider would get injected, using 'private' or 'public' automatically makes it also a member variable of the class, in this case MyApp.mapping
  
   doMenuClosedStuff(){
     this.mapping.menuClosed()
   }
  }

then you can refer to that instance either in the component code (in a method of MyApp as above) or in the corresponding component template: (ionClose)=“mapping.menuClosed()”

… and that’s also true for any component in your app, not just the main app component.

Thank you so much for your help! I couldn’t get it to work for ages because somehow the map object was always undefined in the MenuClosed() function. I went into more and more roundabout ways of trying to get it to work, incl. using event emitters which gave me trouble because I couldn’t figure out how to unsubscribe and the subscribes just kept stacking up (for some reason ionViewDidLoad() kept being called every time the page was called rather than only once) until I finally realized that the problem was that I declared the provider in the components decorator and so it wasn’t using the global object. Once I fixed that it worked! I’ll post some sample code below for the next person who might be looking for this.

Could you by any chance point me in the right direction on another thing?
I’ve set up a geolocation provider that uses Geolocation.watchPosition[…]subscribe[…] => { this.zone.run() in order to globally keep up to date with the current location. Depending on the page I’m currently on, I would do different recalculations if the coordinates change. The problem is I don’t know how to trigger these. For event subscribers I’d again have the problem that they somehow keep stacking up, so I’m not sure how a location change can trigger a different function on each page.

So here are some code snippets how I got the maps to work in the end:

In the map provider:

initMap(mapCanvasID: string) {
  this.map = new GoogleMap(mapCanvasID, { [settings] }};

menuOpened() {
  if(this.map) {
    this.map.setClickable(false);
  }
}

menuClosed() {
  if(this.map) {
    this.map.setClickable(true);
  }
}

In app.component.ts:

import { MapProvider } from '../providers/map-provider'
@Component({
  templateUrl: 'app.template.html',
  providers: [MapProvider]
})
export class MyApp {
[...]
  constructor(
    [...]
    public mapping: MapProvider
  ) { };

in the map page (important, the Mapprovider should not be listed as a provider in @Component):

import { MapProvider } from '../../providers/map-provider'
export class MapPage {
  constructor(public mapping: MapProvider) { }

  ionViewDidEnter() {
    this.platform.ready().then(() => {
      this.map.initMap('map');
    });
  }
}

Note that you can declare your providers in app.module.ts in the Provider section, then you don’t have to include them in any pages or components because they’ve already been instantiated., you can just inject them in the constructor.

Not sure about the Geoposition watcher, if you subscribe to something in a singleton provider (such as your map provider, if you provide it only in app.module.ts) you should only get notified in the one place. Your certainly getting there, agree it can be pretty confusing. Looking back at my Open Charge Map code I can see things I wouldn’t do now (I’d try to avoid anything fancy with NgZones, that’s mostly likely a bug present at the time I wrote it), I started writing that a year ago using one of the earliest alphas of Ionic 2 - still not finished!

I see, thanks. I think for now I might actually leave things with manual refresh of the location and perhaps look into figuring out the automatic one once everything else is working. I am very happy that I got the map-sidemenu combo working in the end with your help :slight_smile:
Right now I’m struggling with an issue though where somehow the addMarker() callback isn’t executing. I’ve put debugging code into the module javascript to verify that the code arrives at the callback call, but somehow nothing happens. I’ve reported it in the issue tracker of the module. You haven’t come across something like that before by any chance?

Well I’ve struggled on many occasions with different parts of the mapping, my markers aren’t working currently either but probably a different problem. If still not working for you then post a code snippet and we’ll see if it’s a syntax thing.

When you say your markers aren’t working, do you by any chance mean that the callbacks aren’t fired? I took that issue to the plugin developer and apparently the ionic wrapper somehow kills the callback, so if you don’t use the wrapper but access the plugin directly (i.e. as window[“plugin”].google.maps) it works.

Thanks, no I’m using the plugin directly - the app I have pre-dates the ionic native wrapper a little.

Hi guys, I just want to share my solution to a working sidemenu. I am using Events from ‘ionic-angular’.

The code was simplified.

In ‘app.html’

<ion-menu [content]="content" 
         (ionOpen)="menuOpened()" 
         (ionClose)="menuClosed()">

  <ion-header>...</ion-header>
  <ion-content>...</ion-content>
</ion-menu>

in ‘app.ts’

import { Events } from 'ionic-angular';
...
@Component({
  templateUrl: 'app.html'
})
export class ... {
  constructor(private events: Events) { ... }

  menuClosed() {
    this.events.publish('menu:closed', '');
  }
  menuOpened() {
    this.events.publish('menu:opened', '');
  }
}

Then, in the page containing the Map.

import { Events } from 'ionic-angular';
import { GoogleMap, ... } from 'ionic-native';
...
export class ... {
  mymap: GoogleMap; // of course the map should be initialized
  constructor(private events: Events) {
    this.events.subscribe('menu:opened', () => {
        this.mymap.setClickable(false);
    });
    this.events.subscribe('menu:closed', () => {
        this.mymap.setClickable(true);
    });
  }
}

Hope it helps. The solution was taken from several places over the Internet, so I cannot give a specific credit to someone.


Here’s my system:

Cordova CLI: 6.3.1
Ionic Framework Version: 2.0.0-rc.3
Ionic CLI Version: 2.1.12
Ionic App Lib Version: 2.1.7
Ionic App Scripts Version: 0.0.45
OS: Windows 8.1
Node Version: v6.9.1
2 Likes

@rroque6428 Thanks man, :slight_smile:

1 Like