Gethin
Gethin

Reputation: 326

Angular (2+) - getting a delay in data binding interpolation

I'm building a location search input using google's location suggestions API. It all works fine, and the results are returned near-instantly, but I'm having several issues with displaying the data.

I've written a basic copy of what I am trying to do on StackBlitz, https://google-location-suggestions.stackblitz.io, and still having the same issues here. I've updated it to have a loading state which better shows the issue.

StackBlitz link with editor: https://stackblitz.com/edit/google-location-suggestions

I've also included a video here so you can see what I mean: https://vimeo.com/271476965

I'd appreciate anyones help if they know why this is happening.

Upvotes: 4

Views: 1710

Answers (2)

ibenjelloun
ibenjelloun

Reputation: 7733

The "delay" is related to change detection, if you click the document after entring a location search, angular will get the event and refresh the DOM.

The maps api is downloaded after the angular loading, which makes the maps code running outside the ngZone. I understood this while reading this post.

The main fix (as you mentionned previously on your answer) is to use ngZone.run to run the code in the Angular Zone :

private suggestions$ = new BehaviorSubject([]);

public search(location: string): void {
    this.service.getPlacePredictions(
      {
        input: location,
        componentRestrictions: { country: 'uk' }
      },
      (predictions, status) => this._ngZone.run(() => this.suggestions$.next(predictions.map(p => p.description)))
    );
  }

I also added some other improvements :

Loading the maps api only once in the constructor to avoid the already loaded error (also stackblitz aot causes loading the service multiple times, so I change the reload mechanism to page on the left bottom menu) :

constructor(private mapsAPILoader: MapsAPILoader) {
    this.mapsAPILoader.load().then(() => {
      this.service = new google.maps.places.AutocompleteService();
      console.log('Maps API loaded');
      this.mapsApiLoading$.next(false);
    });
  }

You could create the desired observable using rxjs pipes :

this.locationSuggestions$ = this.locationForm.get('location').valueChanges.pipe(
        filter(location => location && location !== ''),
        switchMap(location =>
            this.appService.getLocationSuggestions(location)));

This way you can directly subscribe to your observable in the template using async :

<ng-container *ngIf="locationSuggestions$ | async as suggestions; else loading">
{{ suggestions | json }}
</ng-container>
<ng-template #loading>
  loading...
</ng-template>

Sometimes, if I was fast enough I could search before loading the maps api, so I added a loading observable, to let the component know when he could start searching.

Here is the new stackblitz with the changes.

Upvotes: 4

Gethin
Gethin

Reputation: 326

Problem as suggested by @ibenjelloun (thank you) was change detection.

I implemented Angular's NgZone to run change detection so that the binding updates by wrapping the code in the ngZone.run method

this.ngZone.run(() => {
    this.locationSuggestionsLoading = false;
    this.locationSuggestions = locations;
});

Upvotes: 1

Related Questions