Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular2 two-way binding stop work after google maps callback

I work on an angular 2 application. I have a google maps in it, with autocomplete. I have an input (with google autocomplete), and a search button. When i hit the search button, I send the input data to google geocode, and put a marker on the map. It's sounds simple, but after this, the angular2 data bindig just stop working. The input don't get the formatted address, the addressIsValid dont turn on true.

HTML:

<div class="input-group">
<input autocorrect="off" autocapitalize="off" spellcheck="off" type="text"
       class="form-control" [(ngModel)]="addressInput" #search id="address" (keydown)="addressChanged()">
<div class="input-group-btn">
    <button id="addressSubmitButton" type="submit" class="btn btn-default" [class.btn-success]="addressIsValid"
            (click)="submited(search.value)">
        {{ (addressIsValid ? 'addressInput.success' : 'addressInput.search') | translate }}
    </button>
</div>

Code:

export class AddressInputComponent implements OnInit {
public map: google.maps.Map;
public autocomplete: google.maps.places.Autocomplete;
private geocoder: google.maps.Geocoder = new google.maps.Geocoder();
public marker: google.maps.Marker;
public defaultZoom: number;
public defaultLat: number;
public defaultLng: number;
public errorCode: number;
private addressIsValid: boolean;
@Input('address') private addressInput: string;
@Output() addressUpdated: EventEmitter<string> = new EventEmitter();

@ViewChild("search")
public searchElementRef: ElementRef;

constructor() {
    this.addressIsValid = false;
}

ngOnInit() {
    this.defaultZoom = 7;
    this.defaultLat = 47.338941;
    this.defaultLng = 19.396167;
    this.errorCode = null;

    this.autocomplete = new google.maps.places.Autocomplete(this.searchElementRef.nativeElement, {
        types: ["address"]
    });

    this._setMapToDefault();

    this.autocomplete.bindTo('bounds', this.map);

    this.autocomplete.addListener('place_changed', () => {
        this.addressInput = this.autocomplete.getPlace().formatted_address;
    })
}

private _setMapToDefault() {
    this.map = new google.maps.Map(document.getElementById('map'), {
        center: {lat: this.defaultLat, lng: this.defaultLng},
        zoom: this.defaultZoom,
        scrollwheel: false,
    });

    this.marker = new google.maps.Marker();
    this.marker.setMap(this.map);
}

submited() {
    this.geocoder.geocode( { 'address': this.addressInput}, (results, status) => {
        if (status == google.maps.GeocoderStatus.OK) {
            setInterval(this._changeInput(results[0]), 200);
        } else {
            this.addressIsValid = false;
            this.errorCode = 1;
            this._setMapToDefault();
        }
    });
}

private _changeInput(result) {
    this.errorCode = null;

    if (result.address_components[0].types[0] != "street_number") {
        this.addressIsValid = false;
        this.errorCode = 2;
        this._setMapToDefault();
        return;
    }

    // jQuery('#address').val(result.formatted_address);
    this.addressInput = result.formatted_address;
    this.addressUpdated.emit(this.addressInput);
    this.addressIsValid = true;

    this.map.setCenter(result.geometry.location);
    this.map.setZoom(17);
    this.marker.setPosition(result.geometry.location);
    this.marker.setVisible(true);
}

private addressChanged() {
    if (this.addressIsValid == true) {
        this.addressIsValid = false;
        this._setMapToDefault();
    }
}

}

like image 968
László Tóth Avatar asked Feb 03 '17 10:02

László Tóth


1 Answers

Change detection is not triggered because google.maps uses its own sets of events/addListener 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 injecting the ngZone service in your component, on which you can reenter the angular zone using the run method:

constructor(private ngZone: NgZone) {}

ngOnInit() {
    //...
    this.autocomplete.addListener('place_changed', () => {
        this.ngZone.run(() => {
            this.addressInput = this.autocomplete.getPlace().formatted_address;
        });  
    })
}
like image 96
Poul Kruijt Avatar answered Oct 21 '22 14:10

Poul Kruijt