4

I'm using Geocoder API, and when the results are returned, the two-way data binding doesn't work. The data just doesn't refresh inside the view. If I change any other property manually, the data gets refreshed... So, I googled (a lot) and found a solution that uses ngZone. Here's what I've done:

  getLocation(address: string): void {
    var mygc = new google.maps.Geocoder();
    this._ngZone.runOutsideAngular(() => {

      mygc.geocode({
        'address': address
      }, (results, status) => {

        var data: any = results[0];

        this._ngZone.run(() => {
          this.myObject.myData = {          
            lat: data.geometry.location.lat(),
            lng: data.geometry.location.lng()
          };
        });

      });

    });
  }

So I have several questions:

  1. When is ngZone used anyway? The documentation is quite loose...
  2. since this works without runOutsideAngular() as well, what's the point of using it? The example includes this function call as well, so I implemented it too. But it works without it as well...
  3. Is there any other way to refresh myObject in the view?

Thanks!

1

2 Answers 2

8

Someone shoot me if I'm wrong, but as far as I know, you need to use this if you download an external script after zone.js has been loaded. This means that any changes inside that script will not be detected by the change detection. This is what happens when you load the google maps later. Maybe....

Anyways, if this is the case then you have to use the ngZone.run method.

If you want to run something outside the change detection manually, so if you want to force something not to trigger it, you should use the runOutsideAngular. This is not your use-case, so you can safely remove that.

The most common use of this service is to optimize performance when starting a work consisting of one or more asynchronous tasks that don't require UI updates or error handling to be handled by Angular. Such tasks can be kicked off via runOutsideAngular and if needed, these tasks can reenter the Angular zone via run.

But on the other hand, you are mentioning that -two- way data binding doesn't work for you (ngModel). I think the real problem is you updating a property on an existing object. This on itself does not trigger a two way change detection, and is the actual reason why ngZone.run works. If this is the case, then changeRef.detectChanges won't work, and you better use the ApplicationRef and do a tick(). Or don't use two-way data binding and use the data goes down, events go up pattern.

constructor(private appRef: ApplicationRef){}

getLocation(address: string): void {
  let mygc = new google.maps.Geocoder();

  mygc.geocode({
    'address': address
  }, (results, status) => {

      let data: any = results[0];

      this.myObject.myData = {          
         lat: data.geometry.location.lat(),
         lng: data.geometry.location.lng()
      };

      this.appRef.tick();
  });
}

This obviously works, as it is no different from ngZone.run. But the main reason why the change detection is not triggered is because google.maps uses its own sets of events/addEventListener calls. These events are -not- so called monkey patched by zone.js and therefor do not run in the Angular zone, which logically will not trigger a change detection cycle.

So you can solve this by either using your ngZone.run option, or the ApplicationRef.tick. Where I suppose the ngZone.run makes the most sense, because it allows you to (re)enter the angular zone, which is exactly what you want.

For a 'good' read about NgZone you can check out the api

Sign up to request clarification or add additional context in comments.

6 Comments

I include google maps script in the footer, after everything is loaded... so you may be onto something here.
Thanks for your answer! This explains, partially, why ngZone works... do you think it is used wrongly in my case? Is using ApplicationRef a better, more correct approach?
ApplicationRef makes more sense indeed. Another approach could be to emit an event updateLocation.emit({data.geometry.location.lat(), data.geometry.location.lon()}, and subscribe to this event where you display this object :)
You know what I've noticed? If I pressed the button that invokes this call twice, it displayed the data. Just not if I pressed it once... and I've put the script tag way above everything else and it still doesn't work OK. The tick() works, but why is it necessary?
I've explained that. I think it's because you set a property of an object (this.myobject.myData) and not update the entire object itself. This on its own does not trigger a change detection. Double clicking the button does, the first time it sets the value, the second time it does another change detection.
|
2

I use simply:

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

  constructor(
    private zone: NgZone
  ) { super(); }


 getLocation(address: string): void {
  var mygc = new google.maps.Geocoder();
  var self = this;

  mygc.geocode({
    'address': address
  }, (results, status) => {

    var data: any = results[0];

    self.zone.run(() => {
      self.myObject.myData = {          
        lat: data.geometry.location.lat(),
        lng: data.geometry.location.lng()
      };
    });
});

1 Comment

Can u help me with this question? stackoverflow.com/questions/53295654/…

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.