Patrick Zinner
Patrick Zinner

Reputation: 364

Ionic3/Angular4: Two-way databinding stops working after click event on Google-Maps

I am using Ionic 3 and I am using the Google Maps plugin for it. I have two pages, a list and a map. I display the same objects on both views and when you click a list entry or a marker (info window), the object's detail page will be opened where you can rate the selected object.

Anyway, when I open the detail page via the list, everything works fine but when I open the detail page via the map-marker, the two-way data binding stops working. In the code below you can find the <rating-input> component in the rating dialog. That's the code that's not working.

I've tried using a simple input text to find out if it's really the two way data binding or if it's my component. The data binding did not work either using the simple textbox.

Here is the code:

 //adding the markers to the map and adding the clicklisteners
 putMarkersForMarkets(markets: Christmasmarket[]) {


markets.forEach(market => {
  let isOpen = this.openinghoursService.isOpenAt(market, new Date());
  this.map.addMarker({
    title: market.name,
    icon: isOpen ? 'green' : 'red',
    animation: 'DROP',
    position: {
      lat: market.position.latitude,
      lng: market.position.longitude
    }
  }).then(marker => {
    marker.on(GoogleMapsEvent.INFO_CLICK)
      .subscribe(() => {
        this.onClickMarket(market);
      });
  });;

});


}

onClickMarket(market: Christmasmarket) {
       this.map.setVisible(false);
       this.map.setClickable(false);
     this.app.getRootNav().push(MarketDetailPage, { data: market });
   }

Rating-Popup html:

<ion-header>
  <ion-navbar color="primary">
    <ion-title>{{'RATINGDIALOG.TITLE' | translate}}</ion-title>
    <ion-buttons end>
      <button ion-button icon-only (click)="dismiss()">
      <ion-icon name="close"></ion-icon>
    </button>
    </ion-buttons>
  </ion-navbar>
</ion-header>
<ion-content padding>
  <form #form="ngForm">
    <b>{{'RATINGDIALOG.HEADERMARKET' | translate}}</b><br>
    <rating-input [(rating)]="rating" [texts]="ratingTexts" name="rating"></rating-input>
    <b>{{'RATINGDIALOG.HEADERPRICE' | translate}}</b><br>
    <rating-input [(rating)]="ratingPrice" symbol="logo-euro" [texts]="ratingPriceTexts"></rating-input>

    <input type="text" [(value)]="meinval">

    {{meinval}}
  </form>
</ion-content>
<ion-footer>
  <ion-row>
    <ion-col>
      <button [disabled]="rating == 0 || ratingPrice == 0" ion-button full color="secondary" (click)="saveRating()">
        {{'RATINGDIALOG.SAVE' | translate}}
    </button>
    </ion-col>
    <ion-col>
      <button ion-button full color="secondary" (click)="dismiss()">{{'RATINGDIALOG.CANCEL' | translate}}
      </button>
    </ion-col>
  </ion-row>
</ion-footer>

Rating popup typescript:

import { Component, OnInit } from '@angular/core';
import { ViewController, NavParams } from 'ionic-angular';
import { Christmasmarket } from "../../../model/christmasmarket";
import { ChristmasMarketService } from "../../../services/christmasmarketservice";
import { TranslateService } from "@ngx-translate/core";

@Component({
  selector: 'rating-dialog',
  templateUrl: 'ratingdialog.component.html'
})
export class RatingDialogComponent implements OnInit {


  rating: number;
  ratingPrice: number;

  ratingTexts: Array<string>;
  ratingPriceTexts: Array<string>;

  market: Christmasmarket;

  meinval: String;
  constructor(
    private viewCtrl: ViewController,
    private navParams: NavParams,
    private christmasMarketService: ChristmasMarketService,
    private translate: TranslateService
  ) {

  }

  ngOnInit(): void {

    this.translate.get('RATINGTEXTS').subscribe(res => {
      this.ratingTexts = [];
      this.ratingTexts.push(res['VALUE1']);
      this.ratingTexts.push(res['VALUE2']);
      this.ratingTexts.push(res['VALUE3']);
      this.ratingTexts.push(res['VALUE4']);
      this.ratingTexts.push(res['VALUE5']);
    });


    this.translate.get('RATINGPRICETEXTS').subscribe(res => {
      this.ratingPriceTexts = [];
      this.ratingPriceTexts.push(res['VALUE1']);
      this.ratingPriceTexts.push(res['VALUE2']);
      this.ratingPriceTexts.push(res['VALUE3']);
      this.ratingPriceTexts.push(res['VALUE4']);
      this.ratingPriceTexts.push(res['VALUE5']);
    });

    this.market = this.navParams.get('data');
    this.rating = 0;
    this.ratingPrice = 0;

    this.christmasMarketService.findRatingOfMarket(this.market.id).then(rating => {

      if (rating) {
        this.rating = rating.rating;
        this.ratingPrice = rating.ratingPrice;
      }
    }).catch(e => {

        console.log(e);
    });
  }

  dismiss() {
    this.viewCtrl.dismiss(this.market);
  }

  saveRating() {
    this.christmasMarketService.rateMarket(this.market.id, this.rating, this.ratingPrice).then(market => {
      this.market = market;
      this.dismiss();
      console.log(market);
  }).catch(e => {
      console.log(e);
  });

  }

}

Rating-input component html

<button ion-button icon-only clear="true" (click)="onClickRating(num)" *ngFor="let num of possibleRatings">
<ion-icon [name]="symbol"  [color]="rating >= num ? 'black' : 'grey'"></ion-icon>
</button>
<p *ngIf="texts">
    {{texts[rating-1]}}
</p>
<p *ngIf="!rating || rating == 0">
    -
</p>

rating-input component typescript

import { Component, Input, Output, EventEmitter } from "@angular/core";

@Component({
  selector: 'rating-input',
  templateUrl: 'ratinginput.component.html'
})
export class RatingInputComponent{

  @Input() @Output() rating: number;
  @Input() symbol: string = 'star';
  @Input() texts : Array<string>;

  @Output() ratingChange = new EventEmitter();

  possibleRatings: Array<number> = [1, 2, 3, 4, 5];

  onClickRating(rating: number) {
    this.rating = rating;
    this.ratingChange.emit(this.rating);
  }
}

Upvotes: 0

Views: 656

Answers (2)

Poul Kruijt
Poul Kruijt

Reputation: 71941

The problem lies here:

.then(marker => {
    marker.on(GoogleMapsEvent.INFO_CLICK)
      .subscribe(() => {
        this.onClickMarket(market);
      });
});

A click event coming from the google maps api uses its own event listeners, and you have to listen to them using addListener. The problem is, that this is running outside the ngZone. To re-enter the zone, after an event like that has occured, you need to inject ngZone into your component, and execute ngZone.run():

constructor(private readonly ngZone: NgZone) {}  

// ...
putMarkersForMarkets(): void {

    // ...
    .then(marker => {
        marker.on(GoogleMapsEvent.INFO_CLICK).subscribe(() => {
            this.ngZone.run(() => this.onClickMarket(market));
        });
    });
}

This will make sure that any code running inside the run() method will be running inside the zone, and therefore will trigger the change detection

Upvotes: 2

mehul
mehul

Reputation: 55

You Can Use Ng zone

 import { NgZone  } from '@angular/core';

    export class AppComponent {

    constructor(public zone: NgZone){
     window['AppComponent'] = {component: this, zone: zone};
    }
    onClickRating(){
    window['AppComponent'].zone.run(() => {window['AppComponent'].component.yourFunctionName();});
    }
    }

Upvotes: -1

Related Questions