谷歌地图地点 API V3自动完成-选择第一个选项进入

我已经成功地实现了谷歌地图地点 V3自动完成功能在我的输入框按照 http://web.archive.org/web/20120225114154/http://code.google.com:80/intl/sk-SK/apis/maps/documentation/javascript/places.html。它工作得很好,但是我想知道如何使它选择第一个选项从建议时,用户按下输入。我想我需要一些 JS 的魔法,但我对 JS 很陌生,不知道从哪里开始。

先谢谢你!

136302 次浏览

I had the same issue when implementing autocomplete on a site I worked on recently. This is the solution I came up with:

$("input").focusin(function () {
$(document).keypress(function (e) {
if (e.which == 13) {
var firstResult = $(".pac-container .pac-item:first").text();


var geocoder = new google.maps.Geocoder();
geocoder.geocode({"address":firstResult }, function(results, status) {
if (status == google.maps.GeocoderStatus.OK) {
var lat = results[0].geometry.location.lat(),
lng = results[0].geometry.location.lng(),
placeName = results[0].address_components[0].long_name,
latlng = new google.maps.LatLng(lat, lng);


$(".pac-container .pac-item:first").addClass("pac-selected");
$(".pac-container").css("display","none");
$("#searchTextField").val(firstResult);
$(".pac-container").css("visibility","hidden");


moveMarker(placeName, latlng);


}
});
} else {
$(".pac-container").css("visibility","visible");
}


});
});

http://jsfiddle.net/dodger/pbbhH/

Here is a solution that does not make a geocoding request that may return an incorrect result: http://jsfiddle.net/amirnissim/2D6HW/

It simulates a down-arrow keypress whenever the user hits return inside the autocomplete field. The event is triggered before the return event so it simulates the user selecting the first suggestion using the keyboard.

Here is the code (tested on Chrome and Firefox) :

<script src='https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js'></script>
<script src="https://maps.googleapis.com/maps/api/js?sensor=false&libraries=places"></script>
<script>
var pac_input = document.getElementById('searchTextField');


(function pacSelectFirst(input) {
// store the original event binding function
var _addEventListener = (input.addEventListener) ? input.addEventListener : input.attachEvent;


function addEventListenerWrapper(type, listener) {
// Simulate a 'down arrow' keypress on hitting 'return' when no pac suggestion is selected,
// and then trigger the original listener.
if (type == "keydown") {
var orig_listener = listener;
listener = function(event) {
var suggestion_selected = $(".pac-item-selected").length > 0;
if (event.which == 13 && !suggestion_selected) {
var simulated_downarrow = $.Event("keydown", {
keyCode: 40,
which: 40
});
orig_listener.apply(input, [simulated_downarrow]);
}


orig_listener.apply(input, [event]);
};
}


_addEventListener.apply(input, [type, listener]);
}


input.addEventListener = addEventListenerWrapper;
input.attachEvent = addEventListenerWrapper;


var autocomplete = new google.maps.places.Autocomplete(input);


})(pac_input);
</script>

Here is an example of a real, non-hacky, solution. It doesn't use any browser hacks etc, just methods from the public API provided by Google and documented here: Google Maps API

The only downside is that additional requests to Google are required if the user doesn't select an item from the list. The upside is that the result will always be correct as the query is performed identically to the query inside the AutoComplete. Second upside is that by only using public API methods and not relying on the internal HTML structure of the AutoComplete widget, we can be sure that our product won't break if Google makes changes.

var input = /** @type {HTMLInputElement} */(document.getElementById('searchTextField'));
var autocomplete = new google.maps.places.Autocomplete(input);
// These are my options for the AutoComplete
autocomplete.setTypes(['(cities)']);
autocomplete.setComponentRestrictions({'country': 'es'});


google.maps.event.addListener(autocomplete, 'place_changed', function() {
result = autocomplete.getPlace();
if(typeof result.address_components == 'undefined') {
// The user pressed enter in the input
// without selecting a result from the list
// Let's get the list from the Google API so that
// we can retrieve the details about the first result
// and use it (just as if the user had actually selected it)
autocompleteService = new google.maps.places.AutocompleteService();
autocompleteService.getPlacePredictions(
{
'input': result.name,
'offset': result.name.length,
// I repeat the options for my AutoComplete here to get
// the same results from this query as I got in the
// AutoComplete widget
'componentRestrictions': {'country': 'es'},
'types': ['(cities)']
},
function listentoresult(list, status) {
if(list == null || list.length == 0) {
// There are no suggestions available.
// The user saw an empty list and hit enter.
console.log("No results");
} else {
// Here's the first result that the user saw
// in the list. We can use it and it'll be just
// as if the user actually selected it
// themselves. But first we need to get its details
// to receive the result on the same format as we
// do in the AutoComplete.
placesService = new google.maps.places.PlacesService(document.getElementById('placesAttribution'));
placesService.getDetails(
{'reference': list[0].reference},
function detailsresult(detailsResult, placesServiceStatus) {
// Here's the first result in the AutoComplete with the exact
// same data format as you get from the AutoComplete.
console.log("We selected the first item from the list automatically because the user didn't select anything");
console.log(detailsResult);
}
);
}
}
);
} else {
// The user selected a result from the list, we can
// proceed and use it right away
console.log("User selected an item from the list");
console.log(result);
}
});

Building on amimissim's answer, I present a slight alternative, utilising Google's API to handle the events in a cross browser way (amimissim's solution doesn't seem to work in IE8).

I also had to change pac-item.pac-selected to pac-item-refresh.pac-selected as it seems the results div class has changed. This makes pressing ENTER on a suggestion work (rather than selecting the next one down).

var input = document.getElementById('MyFormField');
var autocomplete = new google.maps.places.Autocomplete(input);
google.maps.event.addListener(autocomplete, 'keydown', function(event) {
var suggestion_selected = $(".pac-item-refesh.pac-selected").length > 0;
if (event.which == 13 && !suggestion_selected) {
var simulated_downarrow = $.Event("keydown", {
keyCode: 40,
which: 40
});
this.apply(autocomplete, [simulated_downarrow]);
}
this.apply(autocomplete, [event]);
});

@benregn @amirnissim I think the selection error comes from:

var suggestion_selected = $(".pac-item.pac-selected").length > 0;

The class pac-selected should be pac-item-selected, which explains why !suggestion_selected always evaluate to true, causing the incorrect location to be selected when the enter key is pressed after using 'keyup' or 'keydown' to highlight the desired location.

I just want to write an small enhancement for the answer of amirnissim
The script posted doesn't support IE8, because "event.which" seems to be always empty in IE8.
To solve this problem you just need to additionally check for "event.keyCode":

listener = function (event) {
if (event.which == 13 || event.keyCode == 13) {
var suggestion_selected = $(".pac-item.pac-selected").length > 0;
if(!suggestion_selected){
var simulated_downarrow = $.Event("keydown", {keyCode:40, which:40})
orig_listener.apply(input, [simulated_downarrow]);
}
}
orig_listener.apply(input, [event]);
};

JS-Fiddle: http://jsfiddle.net/QW59W/107/

It seems there is a much better and clean solution: To use google.maps.places.SearchBox instead of google.maps.places.Autocomplete. A code is almost the same, just getting the first from multiple places. On pressing the Enter the the correct list is returned - so it runs out of the box and there is no need for hacks.

See the example HTML page:

http://rawgithub.com/klokan/8408394/raw/5ab795fb36c67ad73c215269f61c7648633ae53e/places-enter-first-item.html

The relevant code snippet is:

var searchBox = new google.maps.places.SearchBox(document.getElementById('searchinput'));


google.maps.event.addListener(searchBox, 'places_changed', function() {
var place = searchBox.getPlaces()[0];


if (!place.geometry) return;


if (place.geometry.viewport) {
map.fitBounds(place.geometry.viewport);
} else {
map.setCenter(place.geometry.location);
map.setZoom(16);
}
});

The complete source code of the example is at: https://gist.github.com/klokan/8408394

How about this?

$("input").keypress(function(event) {
var firstValue = null;
if (event.keyCode == 13 || event.keyCode == 9) {
$(event.target).blur();
if ($(".pac-container .pac-item:first span:eq(3)").text() == "") {
firstValue = $(".pac-container .pac-item:first .pac-item-query").text();
} else {
firstValue = $(".pac-container .pac-item:first .pac-item-query").text() + ", " + $(".pac-container .pac-item:first span:eq(3)").text();
}
event.target.value = firstValue;
} else
return true;
});

None of these answers seemed to work for me. They'd get the general location but wouldn't actually pan to the actual place I searched for. Within the .pac-item you can actually get just the address (name of place excluded) by selecting $('.pac-item:first').children()[2].textContent

So here is my solution:

$("#search_field").on("keyup", function(e) {
if(e.keyCode == 13) {
searchPlaces();
}
});


function searchPlaces() {
var $firstResult = $('.pac-item:first').children();
var placeName = $firstResult[1].textContent;
var placeAddress = $firstResult[2].textContent;


$("#search_field").val(placeName + ", " + placeAddress);


var geocoder = new google.maps.Geocoder();
geocoder.geocode({"address":placeAddress }, function(results, status) {
if (status == google.maps.GeocoderStatus.OK) {
var lat = results[0].geometry.location.lat(),
lng = results[0].geometry.location.lng(),
placeName = results[0].address_components[0].long_name,
latlng = new google.maps.LatLng(lat, lng);


map.panTo(latlng);
}
});
}

I know this question was already answered but figured I'd throw in my 2 cents just in case anyone else was having the same problem as me.

For Google Places Autocomplete V3, the best solution for this is two API requests.

Here is the fiddle

The reason why none of the other answers sufficed is because they either used jquery to mimic events (hacky) or used either Geocoder or Google Places Search box which does not always match autocomplete results. Instead, what we will do is is uses Google's Autocomplete Service as detailed here with only javascript (no jquery)

Below is detailed the most cross browser compatible solution using native Google APIs to generate the autocomplete box and then rerun the query to select the first option.

<script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?libraries=places&language=en"></script>

Javascript

// For convenience, although if you are supporting IE8 and below
// bind() is not supported
var $ = document.querySelector.bind(document);


function autoCallback(predictions, status) {
// *Callback from async google places call
if (status != google.maps.places.PlacesServiceStatus.OK) {
// show that this address is an error
pacInput.className = 'error';
return;
}


// Show a successful return
pacInput.className = 'success';
pacInput.value = predictions[0].description;
}




function queryAutocomplete(input) {
// *Uses Google's autocomplete service to select an address
var service = new google.maps.places.AutocompleteService();
service.getPlacePredictions({
input: input,
componentRestrictions: {
country: 'us'
}
}, autoCallback);
}


function handleTabbingOnInput(evt) {
// *Handles Tab event on delivery-location input
if (evt.target.id == "pac-input") {
// Remove active class
evt.target.className = '';


// Check if a tab was pressed
if (evt.which == 9 || evt.keyCode == 9) {
queryAutocomplete(evt.target.value);
}
}
}


// ***** Initializations ***** //
// initialize pac search field //
var pacInput = $('#pac-input');
pacInput.focus();


// Initialize Autocomplete
var options = {
componentRestrictions: {
country: 'us'
}
};
var autocomplete = new google.maps.places.Autocomplete(pacInput, options);
// ***** End Initializations ***** //


// ***** Event Listeners ***** //
google.maps.event.addListener(autocomplete, 'place_changed', function () {
var result = autocomplete.getPlace();
if (typeof result.address_components == 'undefined') {
queryAutocomplete(result.name);
} else {
// returns native functionality and place object
console.log(result.address_components);
}
});


// Tabbing Event Listener
if (document.addEventListener) {
document.addEventListener('keydown', handleTabbingOnInput, false);
} else if (document.attachEvent) { // IE8 and below
document.attachEvent("onsubmit", handleTabbingOnInput);
}


// search form listener
var standardForm = $('#search-shop-form');
if (standardForm.addEventListener) {
standardForm.addEventListener("submit", preventStandardForm, false);
} else if (standardForm.attachEvent) { // IE8 and below
standardForm.attachEvent("onsubmit", preventStandardForm);
}
// ***** End Event Listeners ***** //

HTML

<form id="search-shop-form" class="search-form" name="searchShopForm" action="/impl_custom/index/search/" method="post">
<label for="pac-input">Delivery Location</label>
<input id="pac-input" type="text" placeholder="Los Angeles, Manhattan, Houston" autocomplete="off" />
<button class="search-btn btn-success" type="submit">Search</button>
</form>

The only gripe is that the native implementation returns a different data structure although the information is the same. Adjust accordingly.

I did some work around this and now I can force select 1st option from google placces using angular js and angular Autocomplete module.
Thanks to kuhnza
my code

<form method="get" ng-app="StarterApp"  ng-controller="AppCtrl" action="searchresults.html" id="target" autocomplete="off">
<br/>
<div class="row">
<div class="col-md-4"><input class="form-control" tabindex="1" autofocus g-places-autocomplete force-selection="true"  ng-model="user.fromPlace" placeholder="From Place" autocomplete="off"   required>
</div>
<div class="col-md-4"><input class="form-control" tabindex="2"  g-places-autocomplete force-selection="true"  placeholder="To Place" autocomplete="off" ng-model="user.toPlace" required>
</div>
<div class="col-md-4"> <input class="btn btn-primary"  type="submit" value="submit"></div></div><br /><br/>
<input class="form-control"  style="width:40%" type="text" name="sourceAddressLat" placeholder="From Place Lat" id="fromLat">
<input class="form-control"  style="width:40%"type="text" name="sourceAddressLang" placeholder="From Place Long" id="fromLong">
<input class="form-control"  style="width:40%"type="text" name="sourceAddress" placeholder="From Place City" id="fromCity">
<input class="form-control"  style="width:40%"type="text" name="destinationAddressLat" placeholder="To Place Lat" id="toLat">
<input class="form-control"  style="width:40%"type="text" name="destinationAddressLang" placeholder="To Place Long"id="toLong">
<input class="form-control"  style="width:40%"type="text" name="destinationAddress"placeholder="To Place City" id="toCity">
</form>

Here is a Plunker
Thank you.

Just a pure javascript version (without jquery) of the great amirnissim's solution:

listener = function(event) {
var suggestion_selected = document.getElementsByClassName('.pac-item-selected').length > 0;
if (event.which === 13 && !suggestion_selected) {
var e = JSON.parse(JSON.stringify(event));
e.which = 40;
e.keyCode = 40;
orig_listener.apply(input, [e]);
}
orig_listener.apply(input, [event]);
};

I investigated this a bit since I have the same Issue. What I did not like about the previous solutions was, that the autocomplete already fired the AutocompleteService to show the predictions. Therefore, the predictions should be somewhere and should not be loaded again.

I found out that the predictions of place inkl. place_id is stored in

Autocomplete.gm_accessors_.place.Kc.l

and you will be able to get a lot of data from the records [0].data. Imho, it's faster and better to get the location by using the place_id instead of address data. This very strange object selection appears not very good to me, tho.

Do you know, if there is a better way to retrieve the first prediction from the autocomplete?

A working answer for 2020.

I've combined the best answers on this page and written it in straightforward ES6. No jQuery, 2nd API request, or IIFE needed.

Basically, we simulate a ↓ (down-arrow) keypress whenever the user hits return inside the autocomplete field.

First, assuming in your HTML you have something like <input id="address-field">, set up the identification of your address field like this:

const field = document.getElementById('address-field')


const autoComplete = new google.maps.places.Autocomplete(field)


autoComplete.setTypes(['address'])

Then add this on the next line:

enableEnterKey(field)

And then elsewhere in your script, to keep this functionality separate in your code if you'd like to, add the function:

  function enableEnterKey(input) {


/* Store original event listener */
const _addEventListener = input.addEventListener


const addEventListenerWrapper = (type, listener) => {
if (type === 'keydown') {
/* Store existing listener function */
const _listener = listener
listener = (event) => {
/* Simulate a 'down arrow' keypress if no address has been selected */
const suggestionSelected = document.getElementsByClassName('pac-item-selected').length
if (event.key === 'Enter' && !suggestionSelected) {
const e = new KeyboardEvent('keydown', {
key: 'ArrowDown',
code: 'ArrowDown',
keyCode: 40,
})
_listener.apply(input, [e])
}
_listener.apply(input, [event])
}
}
_addEventListener.apply(input, [type, listener])
}


input.addEventListener = addEventListenerWrapper
}

You should be good to go. Essentially, the function captures each keypress in the input field and if it's an enter, simulates instead a down-arrow keypress. It also stores and rebinds listeners and events to maintain all functionality of your Google Maps Autocomplete().

With thanks to earlier answers for much of this code, particular amirnissim and Alexander Schwarzman.

Working Solution that listens to if the user has started to navigate down the list with the keyboard rather than triggering the false navigation each time

https://codepen.io/callam/pen/RgzxZB

Here are the important bits

// search input
const searchInput = document.getElementById('js-search-input');


// Google Maps autocomplete
const autocomplete = new google.maps.places.Autocomplete(searchInput);


// Has user pressed the down key to navigate autocomplete options?
let hasDownBeenPressed = false;


// Listener outside to stop nested loop returning odd results
searchInput.addEventListener('keydown', (e) => {
if (e.keyCode === 40) {
hasDownBeenPressed = true;
}
});


// GoogleMaps API custom eventlistener method
google.maps.event.addDomListener(searchInput, 'keydown', (e) => {


// Maps API e.stopPropagation();
e.cancelBubble = true;


// If enter key, or tab key
if (e.keyCode === 13 || e.keyCode === 9) {
// If user isn't navigating using arrows and this hasn't ran yet
if (!hasDownBeenPressed && !e.hasRanOnce) {
google.maps.event.trigger(e.target, 'keydown', {
keyCode: 40,
hasRanOnce: true,
});
}
}
});


// Clear the input on focus, reset hasDownBeenPressed
searchInput.addEventListener('focus', () => {
hasDownBeenPressed = false;
searchInput.value = '';
});


// place_changed GoogleMaps listener when we do submit
google.maps.event.addListener(autocomplete, 'place_changed', function() {


// Get the place info from the autocomplete Api
const place = autocomplete.getPlace();


//If we can find the place lets go to it
if (typeof place.address_components !== 'undefined') {
// reset hasDownBeenPressed in case they don't unfocus
hasDownBeenPressed = false;
}


});

Regarding to all your answers, I have created a solution that works perfectly for me.

/**
* Function that add the google places functionality to the search inputs
* @private
*/
function _addGooglePlacesInputsAndListeners() {
var self = this;
var input = document.getElementById('searchBox');
var options = {
componentRestrictions: {country: "es"}
};


self.addInputEventListenersToAvoidAutocompleteProblem(input);
var searchBox = new google.maps.places.Autocomplete(input, options);
self.addPlacesChangedListener(searchBox, self.SimulatorMapStorage.map);
}


/**
* A problem exists with google.maps.places.Autocomplete when the user write an address and doesn't selectany options that autocomplete gives him so we have to add some events to the two inputs that we have to simulate the behavior that it should have. First, we get the keydown 13 (Enter) and if it's not a suggested option, we simulate a keydown 40 (keydownArrow) to select the first option that Autocomplete gives. Then, we dispatch the event to complete the request.
* @param input
* @private
*/
function _addInputEventListenersToAvoidAutocompleteProblem(input) {
input.addEventListener('keydown', function(event) {
if (event.keyCode === 13 && event.which === 13) {
var suggestion_selected = $(".pac-item-selected").length > 0;
if (!suggestion_selected) {
var keyDownArrowEvent = new Event('keydown');
keyDownArrowEvent.keyCode = 40;
keyDownArrowEvent.which = keyDownArrowEvent.keyCode;


input.dispatchEvent(keyDownArrowEvent);
}
}
});
}
<input id="searchBox" class="search-input initial-input" type="text" autofocus>

Hope that it can help to someone. Please, feel free to discuss the best way to do.

@Alexander 's solution is the one which I was looking for. But it was causing an error - TypeError: a.stopPropagation is not a function.

So I made the event with KeyboardEvent. Here's the working code and Javascript version is very convenient for React.js projects. I also used this for my React.js project.

(function selectFirst(input) {
let _addEventListener = input.addEventListener
? input.addEventListener
: input.attachEvent;


function addEventListenerWrapper(type, listener) {
if (type === 'keydown') {
console.log('keydown');


let orig_listener = listener;
listener = event => {
let suggestion_selected =
document.getElementsByClassName('pac-item-selected').length > 0;


if (event.keyCode === 13 && !suggestion_selected) {
let simulated_downarrow = new KeyboardEvent('keydown', {
bubbles: true,
cancelable: true,
keyCode: 40
});


orig_listener.apply(input, [simulated_downarrow]);
}


orig_listener.apply(input, [event]);
};
}


_addEventListener.apply(input, [type, listener]);
}


if (input.addEventListener) input.addEventListener = addEventListenerWrapper;
else if (input.attachEvent) input.attachEvent = addEventListenerWrapper;
})(addressInput);


this.autocomplete = new window.google.maps.places.Autocomplete(addressInput, options);

Hope this can help someone, :)

    /// <reference types="@types/googlemaps" />
import {ChangeDetectorRef, Component, ElementRef, EventEmitter, Inject, Input, NgZone, OnInit, Output, ViewChild} from '@angular/core';
import {MapsAPILoader, MouseEvent} from '@agm/core';
import { Address } from 'src/@core/interfaces/address.model';
import { NotificationService } from 'src/@core/services/notification.service';
// import {} from 'googlemaps';
declare var google: any;


// @ts-ignore


@Component({
selector: 'app-search-address',
templateUrl: './search-address.component.html',
styleUrls: ['./search-address.component.scss']
})
export class SearchAddressComponent implements OnInit {


@Input('label') label: string;
@Input('addressObj') addressObj: Address = {};
zoom: number;
isSnazzyInfoWindowOpened = false;


private geoCoder;


// @ts-ignore
@Output() onAddressSelected = new EventEmitter<any>();
@Input('defaultAddress') defaultAddress = '';
@ViewChild('search', {static: true})
public searchElementRef: ElementRef = null;




constructor(
private mapsAPILoader: MapsAPILoader,
private ngZone: NgZone,
private notify: NotificationService,
@Inject(ChangeDetectorRef) private changeDetectorRef: ChangeDetectorRef
) { }




ngOnInit() {
// console.log('addressObj# ', this.addressObj);
if (this.defaultAddress !== '') {
this.searchElementRef.nativeElement.value = this.defaultAddress;
}
// load Places Autocomplete
this.mapsAPILoader.load().then(() => {
if (this.addressObj.address) {
this.setZoom();
} else {
this.setCurrentLocation();
}
this.geoCoder = new google.maps.Geocoder;
const autocomplete = new google.maps.places.Autocomplete(this.searchElementRef.nativeElement, {
types: ['address']
});
autocomplete.setTypes(['(cities)']);
autocomplete.setComponentRestrictions({'country': 'in'});
autocomplete.addListener('place_changed', () => {
this.ngZone.run(() => {
// get the place result
const place: google.maps.places.PlaceResult = autocomplete.getPlace();


// verify result
if (place.geometry === undefined || place.geometry === null) {
return;
}


// set latitude, longitude and zoom
this.addressObj.latitude = place.geometry.location.lat();
this.addressObj.longitude = place.geometry.location.lng();
this.getAddress(this.addressObj.latitude, this.addressObj.longitude);
this.zoom = 12;
});
});
});
}


setZoom() {
this.zoom = 8;
}


// Get Current Location Coordinates
private setCurrentLocation() {
if ('geolocation' in navigator) {
navigator.geolocation.getCurrentPosition((position) => {
this.addressObj.latitude = position.coords.latitude;
this.addressObj.longitude = position.coords.longitude;
this.zoom = 8;
this.getAddress(this.addressObj.latitude, this.addressObj.longitude);
});
}
}


markerDragEnd($event: MouseEvent) {
this.addressObj.latitude = $event.coords.lat;
this.addressObj.longitude = $event.coords.lng;
this.getAddress(this.addressObj.latitude, this.addressObj.longitude);
}


getAddress(latitude, longitude) {
this.addressObj.latitude = latitude;
this.addressObj.longitude = longitude;
this.geoCoder.geocode({ location: { lat: latitude, lng: longitude } }, (results, status) => {
if (status === 'OK') {
if (results[0]) {
console.log('results ', results);
this.zoom = 12;
this.addressObj.address = results[0].formatted_address;
this.showSnazzyInfoWindow();
this.addressObj.placeId = results[0].place_id;


for(let i = 0; i < results[0].address_components.length; i++) {
if (results[0].address_components[i].types[0] == 'locality') {
this.addressObj.city = results[0].address_components[i].long_name;
}
if (results[0].address_components[i].types[0] == 'administrative_area_level_1') {
this.addressObj.region = results[0].address_components[i].long_name;
}
if (results[0].address_components[i].types[0] == 'country') {
this.addressObj.country = results[0].address_components[i].long_name;
}
if (results[0].address_components[i].types[0] == 'postal_code') {
this.addressObj.zip = results[0].address_components[i].long_name;
}
}


this.transmitData();
} else {
this.notify.showMessage('No results found', 3000, 'OK');
}
} else {
this.notify.showMessage('Google maps location failed due to: ' + status, 3000, 'OK');
}


});
}




transmitData() {
// console.log(this.addressObj);
this.onAddressSelected.emit(this.addressObj);
}


toggleSnazzyInfoWindow() {
this.isSnazzyInfoWindowOpened = !this.isSnazzyInfoWindowOpened;
}


showSnazzyInfoWindow() {
this.isSnazzyInfoWindowOpened = true;
}


}








<mat-form-field class="full-width pt-2 flex-auto w-full">
<input matInput [(ngModel)]="addressObj.address" type="text" (keydown.enter)="$event.preventDefault()" placeholder="\{\{label ? label : 'Location'}}" autocorrect="off" autocapitalize="off" spellcheck="off" type="text" #search>
</mat-form-field>


<agm-map
[latitude]="addressObj.latitude"
[longitude]="addressObj.longitude"
[zoom]="zoom">
<agm-marker
[latitude]="addressObj.latitude"
[longitude]="addressObj.longitude"
[markerDraggable]="true"
(dragEnd)="markerDragEnd($event)">
</agm-marker>


</agm-map>

Yet another solution based on Tony Brasunas' answer.

Although the original question yields great answers to me it still lacks some functionality, which people have also mentioned in the comments, when the user clicks away or when the user submits the form.

This introduces two additional events blur and submit. To introduce these events into my favourite solution I refactored a few things.

/**
* Google Autocomplete auto first select
* What we really need is a [lat,lng] pair to enhance validation of our form
* @event keydown : stores the eventListener in memory
* @event blur : pass the eventListener to a wrapper
* @event submit : pass the eventListener to a wrapper
* @param {Object} input HTMLElement
*/
enableFirstSelection: function (input) {
const originalEventListener = input.addEventListener;
const addEventListenerWrapper = (type, listener) => {
if (type === 'keydown') {
this._listener = listener;


listener = (event) => this.createKeyboardEventWrapperByKey(event, 'Enter', this._listener);
}


if (type === 'blur' || type === 'submit') {
const _listener = listener;


listener = (event) => this.createKeyboardEventWrapperByType(event, 'Enter', this._listener, _listener);
}
            

originalEventListener.apply(input, [type, listener]);
};


input.addEventListener = addEventListenerWrapper;
},


/**
* Simulate a 'down arrow' keypress if no address has been selected
* Re-apply the original event
* @param {Object} ev wrapped keyboardEvent
* @param {String} key value of the key represented by the event
* @param {Object} kbListener stored keyboardEvent listener
*/
createKeyboardEventWrapperByKey: function (ev, key, kbListener) {
const suggestionSelected = document.getElementsByClassName('pac-item-selected').length;


if (ev.key === key && !suggestionSelected) {
const e = new KeyboardEvent('keydown', {
key: 'ArrowDown',
code: 'ArrowDown',
keyCode: 40,
});


kbListener.apply(ev.currentTarget, [e]);
}


if (ev.type === 'keydown') {
kbListener.apply(ev.currentTarget, [ev]);
}
},


/**
* Simulate a 'down arrow' keypress when the user switches focus
* Re-apply the original event
* @param {Object} ev wrapped event
* @param {String} key value of the key represented by the event
* @param {Object} kbListener stored keyboardEvent listener
* @param {Object} typeListener stored typeEvent listener
*/
createKeyboardEventWrapperByType: function (ev, key, kbListener, typeListener) {
const fakeTypeEvent = { key: key, currentTarget: ev.currentTarget, type: ev.type };
this.createKeyboardEventWrapperByKey(fakeTypeEvent, key, kbListener);


typeListener.apply(ev.currentTarget, [ev]);
},

So by calling this.enableFirstSelection(this.autocompleteField); I can now make a selection when the user presses Enter, tabs away, clicks out, triggers the form submit and so on...

The most important explanation to this solution is the requirement of the keydown listener as it is used by the other events to trigger the keyboard listener.
=> this._listener = listener; -vs- const _listener = listener;

Hopefully this is useful to anyone. Thanks to all my predecessors for helping me out on this one.