title: In Ionic v3, why doesn't ngClass and ngIf reflect changes immediately from google maps marker click listener?
body: <p>I am creating an app with a map on the home screen. Whenever the user clicks a marker, it's supposed to open an absolute-positioned div from the bottom of the screen, with info on the place clicked. The issue is: the div's visibility and position are ruled by an ngIf (to make it visible) and an ngClass (to make it slide upwards). When the pin is clicked, google maps' event listener fires and calls a function I defined for the marker click event. This function changes state of the variables used for ngClass and ngIf. When the marker is clicked, that function runs immediately, but I can only see the bottom div appear only after I click on another element of the home screen. This behavior was verified in the chrome inspector tool, the ngIf state and the ngClass are changed just at that moment where I click in any other element on the screen. The map used is google maps' JS implementation. I used successfully the agm implementation, but I needed support for custom drawn markers and couldn't make it work with agm.</p>
  
  <p>Here is the code for the home screen:</p>
  
  <pre><code>&lt;ion-content&gt;
  &lt;div class="places"&gt;
      &lt;ion-list has-header&gt;
          &lt;ion-item&gt;
              &lt;ion-label style="margin: 0px !important;"&gt;Lugares&lt;/ion-label&gt;
              &lt;ion-select interface="popover"&gt;
                  &lt;ion-option *ngFor="let place of placesCombo" [value]="palce" [selected]="place=='Perto de mim'"&gt;{{place}}&lt;/ion-option&gt;
                  &lt;!-- (ionSelect)="onTypeChange(place)" --&gt;
              &lt;/ion-select&gt;
          &lt;/ion-item&gt;
      &lt;/ion-list&gt;
      &lt;ion-searchbar class="home-search" [(ngModel)]="searchQuery" [showCancelButton]="true" (keyup.enter)="closeKeyboard()" (ionInput)="onSearchbarInput($event)"
          (ionCancel)="onCancel($event)" placeholder="Buscar um lugar"&gt;&lt;/ion-searchbar&gt;
      &lt;ion-list class="places-list" *ngIf="showList"&gt;
          &lt;button ion-item *ngFor="let place of searchPlaces" (click)="centerPlace(place)"&gt;
              {{ place.name }}
          &lt;/button&gt;
      &lt;/ion-list&gt;
  &lt;/div&gt;
  &lt;div class="map-wrap"&gt;
      &lt;div #map id="map"&gt;&lt;/div&gt;
      &lt;div *ngIf="placeInfo != null" class="establishment-card" [ngClass]="{'active': isCardShown }"&gt;
          &lt;div class="bg" [ngStyle]="getStyle()"&gt;
          &lt;/div&gt;
          &lt;button ion-button round class="close" (click)="dismissCard(this)"&gt;
              &lt;ion-icon class="icon-close"&gt;&lt;/ion-icon&gt;
          &lt;/button&gt;
          &lt;div card-content no-lines no-padding&gt;
              &lt;h1 text-center&gt;{{getName()}}&lt;/h1&gt;
              &lt;ion-icon text-center white *ngFor="let item of starData; let i = index"&gt;
                  &lt;i icon-medium *ngIf="starData[i].state == 'empty'" class="icon icon-star-outline"&gt;&lt;/i&gt;
                  &lt;i icon-medium *ngIf="starData[i].state == 'half'" class="icon icon-star-half"&gt;&lt;/i&gt;
                  &lt;i icon-medium *ngIf="starData[i].state == 'full'" class="icon icon-star"&gt;&lt;/i&gt;
              &lt;/ion-icon&gt;
              &lt;div class="reviews" span-small text-center margin-bottom&gt;{{getRate()}} (78 avaliações)&lt;/div&gt;
              &lt;button ion-button block icon-start (click)="checkOffers()"&gt;
                  &lt;ion-icon name="pricetags"&gt;&lt;/ion-icon&gt;OFERTAS&lt;/button&gt;
              &lt;button ion-button block icon-start (click)="checkDetails()"&gt;
                  &lt;ion-icon name="search"&gt;&lt;/ion-icon&gt;VER MAIS&lt;/button&gt;
              &lt;button ion-button block icon-start (click)="navigate()"&gt;
                  &lt;ion-icon name="navigate"&gt;&lt;/ion-icon&gt;IR AGORA&lt;/button&gt;
          &lt;/div&gt;
      &lt;/div&gt;
  &lt;/div&gt;
  </code></pre>
  
  <p></p>
  
  <p>This screen is one of the pages in a 3-tab layout. I don't think this can an aggravating, but the more we know, the better.</p>
  
  <p>This is the TS class for the home screen:</p>
  
  <pre><code>import { Component } from '@angular/core';
  import { IonicPage, NavController, NavParams, AlertController, ActionSheetController, Keyboard } from 'ionic-angular';
  import { Geolocation } from '@ionic-native/geolocation';
  import { Http, URLSearchParams } from '@angular/http';
  import { GlobalVars } from '../../../providers/global-variables';
  import { Establishment } from '../../../models/establishment';
  import { AlertUtils } from '../../../utils/alert-utils';
  import { NavUtils } from '../../../utils/nav-utils';
  import { EstablishmentDetailsPage } from '../../establishment-details/establishment-details';
  import { LaunchNavigator } from '../../../../node_modules/@ionic-native/launch-navigator';
  import { EstablishmentOffersPage } from '../../establishment-offers/establishment-offers';
  import { Diagnostic } from '@ionic-native/diagnostic';
  import { Storage } from '../../../../node_modules/@ionic/storage';
  import { MapUtils } from '../../../utils/map-utils';
  
  declare var google;
  declare var CustomMarker;
  
  @IonicPage()
  @Component({
      selector: 'page-home',
      templateUrl: 'home.html',
  })
  export class HomePage {
  
      static title = 'Home';
  
      homeInstance: any;
  
      gMap: any;
      myPos: any = {
      }
      markers: any[] = [];
      placesCombo: any[] = ['Perto de mim', 'Porto Alegre'];
      searchQuery: string = '';
      searchPlaces = [];
      showList = false;
  
      places: Array&lt;Establishment&gt; = [];
      placeInfo: Establishment = null;
      isCardShown: boolean = false;
  
      starData = [
          {
              "state": 'empty',
          },
          {
              "state": 'empty',
          },
          {
              "state": 'empty',
          },
          {
              "state": 'empty',
          },
          {
              "state": 'empty',
          }
      ];
  
      constructor(public navCtrl: NavController
          , public navParams: NavParams
          , public geolocation: Geolocation
          , public http: Http
          , public alertCtrl: AlertController
          , public nav: LaunchNavigator
          , public diag: Diagnostic
          , public actionSheetCtrl: ActionSheetController
          , public storage: Storage
          , public keyboard: Keyboard) {
          this.homeInstance = navParams.data;
          this.homeInstance.tabHome = this;
      }
  
      ionViewWillLoad() {
          this.geolocation.getCurrentPosition().then((resp) =&gt; {
              this.myPos.lat = resp.coords.latitude;
              this.myPos.lng = resp.coords.longitude;
              const position = new google.maps.LatLng(this.myPos.lat, this.myPos.lng);
              this.gMap.setCenter(position);
              var contentString = '&lt;div id="content"&gt;' +
                  'Você está aqui' +
                  '&lt;/div&gt;';
  
              var infowindow = new google.maps.InfoWindow({
                  content: contentString
              });
              var overlay = new CustomMarker(
                  position,
                  this.gMap,
                  {
                      //------------Here I create the marker for the user's position. Oddly, if I click at this marker first, the other ones seem to work ok after that.
                      listener: function () {
                          infowindow.open(this.gMap, overlay);
                      },
                      image: "http://www.stickpng.com/assets/images/585e4bf3cb11b227491c339a.png"
                  }
              );
          }).catch((error) =&gt; {
              console.log('Error getting location', error);
          });
          this.populateLists();
      }
  
      ionViewDidLoad() {
          const position = new google.maps.LatLng(0, 0);
          const mapOptions = {
              zoom: 15,
              center: position,
              disableDefaultUI: true,
              zoomControl: true,
              scaleControl: true
          }
          this.gMap = new google.maps.Map(document.getElementById('map'), mapOptions);
          this.gMap.setOptions({
              styles: [
                  {
                      "featureType": "poi",
                      "elementType": "labels.icon",
                      "stylers": [
                          {
                              "visibility": "off"
                          }
                      ]
                  }
              ]
          });
      }
  
      populateLists() {
          this.http.get(GlobalVars.serverRoot + 'establishments/summary'
          ).map(res =&gt; res.json()).toPromise().then(
              data =&gt; {
                  this.places = new Array&lt;Establishment&gt;();
                  for (let i = 0; i &lt; data.length; i++) {
                      let establishment = new Establishment(data[i]);
                      this.places.push(establishment);
                      let position = new google.maps.LatLng(establishment.location.cordinates.split(', ')[0], establishment.location.cordinates.split(', ')[1]);
                      const home = this;
                      var overlay = new CustomMarker(
                          position,
                          this.gMap,
                          {
                              listener: function () {
                                  //----Here I send the code block I want to run after the marker is clicked.
                                  home.onMarkerClick(establishment);
                              },
                              image: establishment.images[0].path_miniature
                          }
                      );
                      this.markers.push(overlay);
                  };
                  this.diag.isLocationEnabled().then((state) =&gt; {
                      if (!state) {
                          let alert = this.alertCtrl.create({
                              title: "Atenção",
                              message: "Para obter uma experiência mais personalizada, ative os serviços de localização. Deseja fazer isso agora?",
                              enableBackdropDismiss: false,
                              buttons: [{
                                  text: 'Não', handler: (data) =&gt; {
                                      alert.dismiss();
                                  }
                              }, {
                                  text: 'Sim', handler: (data) =&gt; {
                                      this.diag.switchToLocationSettings();
                                  }
                              }]
                          });
                          alert.present();
                      }
                  });
              },
              error =&gt; {
                  AlertUtils.showErrorWithHandler(error, this.alertCtrl, (data) =&gt; {
                      this.diag.isLocationEnabled().then((state) =&gt; {
                          if (!state) {
                              let alert = this.alertCtrl.create({
                                  title: "Atenção",
                                  message: "Para obter uma experiência mais personalizada, ative os serviços de localização. Deseja fazer isso agora?",
                                  enableBackdropDismiss: false,
                                  buttons: [{
                                      text: 'Não', handler: (data) =&gt; {
                                          alert.dismiss();
                                      }
                                  }, {
                                      text: 'Sim', handler: (data) =&gt; {
                                          this.diag.switchToLocationSettings();
                                      }
                                  }]
                              });
                              alert.present();
                          }
                      });
                  });
              });
      }
  
      getLat(place: any) {
          return Number(place.location.cordinates.split(', ')[0]);
      }
  
      getLng(place: any) {
          return Number(place.location.cordinates.split(', ')[1]);
      }
      //-----------------THIS is the function called when a marker is clicked, and changes the variables that govern my bottom card behavior. It is called immediately after the click on the marker, but its effects' visibility are delayed
      onMarkerClick(place) {
          this.placeInfo = place;
          this.onStarClass(this.placeInfo.rank);
          let home = this;
          setTimeout(function () {
              home.isCardShown = true;
          }, 100);
      }
  
      dismissCard(home: HomePage) {
          this.isCardShown = false;
          setTimeout(function () {
              home.placeInfo = null;
          }, 400);
      }
  
      onCancel($event) { }
  
      closeKeyboard() {
          this.keyboard.close();
      }
  
      onSearchbarInput(ev: any) {
  
          // set val to the value of the searchbar
          let val = ev.target.value;
          this.searchQuery = val;
          this.getItems(ev);
      }
  
      getItems(ev: any) {
          // Reset items back to all of the items
          this.searchPlaces = [];
  
          // set val to the value of the searchbar
          let val = ev.target.value;
  
          // if the value is an empty string don't filter the items
          if (val &amp;&amp; val.trim() != '') {
  
  
              let qp = new URLSearchParams();
              if (this.searchQuery != null &amp;&amp; this.searchQuery.trim() != '') {
                  qp.append('name', this.searchQuery);
              }
              this.http.get(GlobalVars.serverRoot + 'establishments/summary', { params: qp }).toPromise().then(
                  response =&gt; {
                      let places = response.json();
                      for (let i = 0; i &lt; places.length; i++) {
                          this.searchPlaces.push(new Establishment(places[i]));
                      }
                  });
  
              // Show the results
              this.showList = true;
          } else {
  
              // hide the results when the query is empty
              this.showList = false;
          }
      }
  
      centerPlace(place: Establishment) {
          this.searchQuery = place.name;
          this.showList = false;
          this.searchPlaces = [];
      }
  
      checkOffers() {
          NavUtils.goToPage(EstablishmentOffersPage, { establishment: this.placeInfo });
      }
  
      checkDetails() {
          NavUtils.goToPage(EstablishmentDetailsPage, { establishment: this.placeInfo });
      }
      navigate() {
          MapUtils.navigate(this.placeInfo, this.actionSheetCtrl, this.alertCtrl);
      }
  
      getStyle() {
          if (this.placeInfo != null) {
              return { 'background-image': 'url(' + this.placeInfo.images[0].path + ')' };
          } else return '';
      }
  
      getName() {
          if (this.placeInfo != null) {
              return this.placeInfo.name;
          } else return '';
      }
  
      getRate() {
          if (this.placeInfo != null) {
              return this.placeInfo.rank;
          } else return '';
      }
  
      onStarClass(rate: number) {
          let decimal = parseInt(rate.toPrecision(2).slice(2));
          let litStars = parseInt((rate).toPrecision(2).charAt(0)) + (decimal &gt;= 7 ? 1 : 0);
          let hasHalfLitStar = decimal &gt;= 2 &amp;&amp; decimal &lt; 7;
          for (let i = 1; i &lt;= this.starData.length; i++) {
              if (i &lt;= litStars) {
                  this.starData[i - 1].state = 'full';
              } else if (i == litStars + 1 &amp;&amp; hasHalfLitStar) {
                  if (hasHalfLitStar) {
                      this.starData[i - 1].state = 'half'
                  }
              }
  
          }
      };
  
  }
  </code></pre>
  
  <p>This is the custom marker js file, which I found online in a great tutorial. I did edit it to make it render the markers as I wanted, and to be recognized by the TS classes.</p>
  
  <pre><code>var CustomMarker = function (latlng, map, args) {
      this.latlng = latlng;
      this.args = args;
      this.setMap(map);
  }
  
  CustomMarker.prototype = new google.maps.OverlayView();
  
  CustomMarker.prototype.draw = function () {
  
      var self = this;
  
      var div = this.div;
  
      if (!div) {
  
          div = this.div = document.createElement('div');
  
          div.className = 'marker';
  
          div.style.position = 'absolute';
          div.style.cursor = 'pointer';
          div.style.width = '36px';
          div.style.height = '46px';
          div.style.backgroundImage = "url('https://s3-sa-east-1.amazonaws.com/clubi-app/pin.png')";
          div.style.backgroundPosition = "center";
          div.style.backgroundSize = "36px 46px";
  
          imgCropper = document.createElement('div');
          imgCropper.style.position = 'absolute';
          imgCropper.style.zIndex = '-1';
          imgCropper.style.cursor = 'pointer';
          imgCropper.style.width = '34px';
          imgCropper.style.height = '34px';
          imgCropper.style.margin = '1px';
          imgCropper.style.overflow = 'hidden';
          imgCropper.style.borderRadius = '50%';
          imgDiv = document.createElement('div');
          imgDiv.style.position = 'absolute';
          imgDiv.style.cursor = 'pointer';
          imgDiv.style.width = '34px';
          imgDiv.style.height = '34px';
          if (typeof (self.args.image) !== 'undefined') {
              imgDiv.style.backgroundImage = "url('" + this.args.image + "')";
              imgDiv.style.backgroundPosition = "center";
              imgDiv.style.backgroundSize = "34px 34px";
          }
  
          imgCropper.appendChild(imgDiv);
          div.appendChild(imgCropper);
  
  
          if (typeof (self.args.marker_id) !== 'undefined') {
              div.dataset.marker_id = self.args.marker_id;
          }
  
          if (typeof (self.args.listener) !== 'undefined') {
              google.maps.event.addDomListener(div, "click", self.args.listener);
          }
  
          var panes = this.getPanes();
          panes.overlayImage.appendChild(div);
      }
  
      var point = this.getProjection().fromLatLngToDivPixel(this.latlng);
  
      if (point) {
          div.style.left = (point.x - 18) + 'px';
          div.style.top = (point.y - 46) + 'px';
      }
  };
  
  CustomMarker.prototype.remove = function () {
      if (this.div) {
          this.div.parentNode.removeChild(this.div);
          this.div = null;
      }
  };
  
  CustomMarker.prototype.getPosition = function () {
      return this.latlng;
  };
  </code></pre>
  
  <p>If there's anything else I can provide to help identify the cause of this issue, please let me know.</p>