Skip to content

Commit

Permalink
feat: added google place types, Location & radius search
Browse files Browse the repository at this point in the history
  • Loading branch information
Tanoy Maity committed Jul 4, 2017
1 parent c99519f commit 8e69e8a
Show file tree
Hide file tree
Showing 5 changed files with 161 additions and 26 deletions.
13 changes: 10 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Angular 4 compatible google autocomplete
# Angular 4 compatible google autocomplete
[![Build Status](https://travis-ci.org/tanoy009/ng4-geoautocomplete.svg?branch=master)](https://travis-ci.org/tanoy009/ng4-geoautocomplete)
[![codecov](https://codecov.io/gh/tanoy009/ng4-geoautocomplete/branch/master/graph/badge.svg)](https://codecov.io/gh/tanoy009/ng4-geoautocomplete)
[![npm version](https://badge.fury.io/js/ng4-geoautocomplete.svg)](http://badge.fury.io/js/ng4-geoautocomplete)
Expand Down Expand Up @@ -72,7 +72,10 @@ List of settings that can be used to configure the module (all config. are optio
geoLatLangServiceUrl?: string; //should be a server url which returns place object upon lat and lon. (GET request)
geoLocDetailServerUrl?: string; //should be a server url which returns place details upon placeID received by 'geoPredictionServerUrl' (GET request)
geoCountryRestriction?: any; //should be an array of country code where search should be restricted like ['in', 'us', 'pr', 'vi', 'gu', 'mp'] *(Default: 'no restriction')*
serverResponseListHierarchy?: any; //should be an array of key from where 'geoPredictionServer' data should be extracted. (see Example.)
geoTypes?: any; //should be an array of Place types defined by [Google api](https://developers.google.com/places/web-service/autocomplete#place_types).
geoLocation?: any; //should be an array in the format [latitude,longitude]. #### This feature will not work if country restriction is implimented.
geoRadius?: number; //should be a number and should only be used with 'geoLocation'.
serverResponseListHierarchy?: any; //should be an array of key from where 'geoPredictionServer' data should be extracted. (see Example.)
serverResponseatLangHierarchy?: any; //should be an array of key from where 'geoLatLangService' data should be extracted. (see Example.)
serverResponseDetailHierarchy?: any; //should be an array of key from where 'geoLocDetailSerice' data should be extracted. (see Example.)
resOnSearchButtonClickOnly?: boolean; //when output should be emmited when search button clicked only.
Expand All @@ -88,9 +91,13 @@ List of settings that can be used to configure the module (all config. are optio
locationIconUrl?: string; //Genetal Location icon can be changed *(Should be an image or svg url)*
}
```

* ####NOTE: 'geoType' can be used for multiple Place Types like ['(regions)', '(cities)'] OR ['(regions)', 'establishment', 'geocode']. This will make individual api call for each Place Types to google to fetch lists and then it will merge the resuts with uniqueness. USE THIS FEATURE CAREFULLY
You may also find it useful to view the [demo source](https://github.com/tanoy009/ng4-geoautocomplete/blob/master/demo/demo.component.ts).

### You can use it with system js as well

`'ng4-geoautocomplete': 'npm:ng4-geoautocomplete/bundles/ng4-geoautocomplete.umd.js'`

### Usage without a module bundler
```
<script src="node_modules/ng4-geoautocomplete/bundles/ng4-geoautocomplete.umd.js"></script>
Expand Down
58 changes: 51 additions & 7 deletions demo/demo.component.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<div class="row">
<div class="row">
<div class="col-xs-12 col-md-6">
<div class="panel panel-primary">
<div class="panel-heading">
Expand Down Expand Up @@ -83,15 +83,16 @@ <h3 class="panel-title">Example 4 (without search button and changed place icons
</div>
</div>
<div class="row">
<div class="col-xs-12">
<div class="panel panel-primary">
<div class="col-xs-12 col-md-6">
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title">Example 5 (using server API instead of google API) (Please enable CORS on your browser to see the result as I am using 3rd party server URL)</h3>
<h3 class="panel-title">Example 5 (Using google pleace types for location filteration. multiple place types can be send)</h3>
</div>
<div class="panel-body">
<div class="well">
<p>Setting used:</p>
<code style="word-wrap: break-word;" [innerHTML]="getCodeHtml(userSettings5_1)"></code>
<code style="word-wrap: break-word;" [innerHTML]="getCodeHtml(userSettings5)"></code>
<p>Above 'geoTypes' attribute can be used for multiple 'TYPES' defined by google place service like <code style="word-wrap: break-word;">['(regions)', '(cities)']</code> (Refer Doc for more info)</p>
</div>
<div>
<ng4geo-autocomplete [userSettings]="userSettings5" (componentCallback)="autoCompleteCallback5($event)"></ng4geo-autocomplete>
Expand All @@ -101,6 +102,49 @@ <h3 class="panel-title">Example 5 (using server API instead of google API) (Plea
<samp style="word-wrap: break-word;">{{componentData5}}</samp>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-xs-12 col-md-6">
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title">Example 6 (Using google Latitude and longitude along with radius to trimdown result)</h3>
</div>
<div class="panel-body">
<div class="well">
<p>Setting used:</p>
<code style="word-wrap: break-word;" [innerHTML]="getCodeHtml(userSettings6)"></code>
<p>Above 'geoRadius' is in KM (5 means 5KM)</p>
</div>
<div>
<ng4geo-autocomplete [userSettings]="userSettings6" (componentCallback)="autoCompleteCallback6($event)"></ng4geo-autocomplete>
</div>
<div class="well" style="float: left;width: 100%;margin-top: 20px;">
<p>Sample Output:</p>
<samp style="word-wrap: break-word;">{{componentData6}}</samp>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-xs-12">
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title">Example 7 (using server API instead of google API) (Please enable CORS on your browser to see the result as I am using 3rd party server URL)</h3>
</div>
<div class="panel-body">
<div class="well">
<p>Setting used:</p>
<code style="word-wrap: break-word;" [innerHTML]="getCodeHtml(userSettings7_1)"></code>
</div>
<div>
<ng4geo-autocomplete [userSettings]="userSettings7" (componentCallback)="autoCompleteCallback7($event)"></ng4geo-autocomplete>
</div>
<div class="well" style="float: left;width: 100%;margin-top: 20px;">
<p>Sample Output:</p>
<samp style="word-wrap: break-word;">{{componentData7}}</samp>
</div>
</div>
</div>
</div>
</div>
22 changes: 20 additions & 2 deletions demo/demo.component.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Component } from '@angular/core';
import { Component } from '@angular/core';

@Component({
selector: 'ng2geo-autocomplete-demoapp',
Expand All @@ -10,6 +10,8 @@ export class DemoComponent {
public componentData3: any = '';
public componentData4: any = '';
public componentData5: any = '';
public componentData6: any = '';
public componentData7: any = '';
public userSettings2: any = {
showRecentSearch: false,
geoCountryRestriction: ['in'],
Expand All @@ -29,6 +31,14 @@ export class DemoComponent {
noOfRecentSearchSave: 8
};
public userSettings5: any = {
geoCountryRestriction: ['in'],
geoTypes: ["establishment"]
};
public userSettings6: any = {
geoLocation: [37.76999,-122.44696],
geoRadius: 5
};
public userSettings7: any = {
useGoogleGeoApi: false,
geoLocDetailServerUrl: 'https://www.simplymovein.com/api/v4/get-location',
geoPredictionServerUrl: 'https://www.simplymovein.com/api/v4/search-location',
Expand All @@ -39,7 +49,7 @@ export class DemoComponent {
recentStorageName: 'componentData5'
};

public userSettings5_1: any = {
public userSettings7_1: any = {
useGoogleGeoApi: false,
geoLocDetailServerUrl: 'https://www.XXX.com/api/v4/get-location',
geoPredictionServerUrl: 'https://www.XXX.com/api/v4/search-location',
Expand Down Expand Up @@ -80,4 +90,12 @@ export class DemoComponent {
autoCompleteCallback5(data: any): any {
this.componentData5 = JSON.stringify(data);
}

autoCompleteCallback6(data: any): any {
this.componentData6 = JSON.stringify(data);
}
autoCompleteCallback7(data: any): any {
this.componentData7 = JSON.stringify(data);
}

}
31 changes: 29 additions & 2 deletions src/auto-complete.component.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Component, PLATFORM_ID, Inject, Input, Output, EventEmitter, OnInit, ElementRef } from '@angular/core';
import { Component, PLATFORM_ID, Inject, Input, Output, EventEmitter, OnInit, ElementRef } from '@angular/core';
import { isPlatformBrowser, isPlatformServer } from '@angular/common';
import { GlobalRef } from './windowRef.service';
import { AutoCompleteSearchService } from './auto-complete.service';
Expand All @@ -8,6 +8,9 @@ export interface Settings {
geoLatLangServiceUrl?: string;
geoLocDetailServerUrl?: string;
geoCountryRestriction?: any;
geoTypes?: any;
geoLocation?: any;
geoRadius?: number;
serverResponseListHierarchy?: any;
serverResponseatLangHierarchy?: any;
serverResponseDetailHierarchy?: any;
Expand Down Expand Up @@ -295,6 +298,9 @@ export class AutoCompleteComponent implements OnInit {
geoLatLangServiceUrl: '',
geoLocDetailServerUrl: '',
geoCountryRestriction: [],
geoTypes: [],
geoLocation: [],
geoRadius: 0,
serverResponseListHierarchy: [],
serverResponseatLangHierarchy: [],
serverResponseDetailHierarchy: [],
Expand All @@ -319,7 +325,19 @@ export class AutoCompleteComponent implements OnInit {

ngOnInit(): any {
this.settings = this.setUserSettings();
//condition to check if Radius is set without location detail.
if(this.settings.geoRadius) {
if(this.settings.geoLocation.length !== 2) {
this.isSettingsError = true;
this.settingsErrorMsg = this.settingsErrorMsg +
'Radius should be used with GeoLocation. Please use "geoLocation" key to set lat and lng. ';
}
}

//condition to check if lat and lng is set and radious is not set then it will set to 20,000KM by default
if((this.settings.geoLocation.length === 2) && !this.settings.geoRadius) {
this.settings.geoRadius = 20000000;
}
if (this.settings.showRecentSearch) {
this.getRecentLocations();
}
Expand Down Expand Up @@ -449,7 +467,16 @@ export class AutoCompleteComponent implements OnInit {
private getListQuery(value: string): any {
this.recentDropdownOpen = false;
if (this.settings.useGoogleGeoApi) {
this._autoCompleteSearchService.getGeoPrediction(value, this.settings.geoCountryRestriction).then((result) => {
let _tempParams: any = {
'query': value,
'countryRestriction': this.settings.geoCountryRestriction,
'geoTypes': this.settings.geoTypes
}
if(this.settings.geoLocation.length === 2) {
_tempParams.geoLocation = this.settings.geoLocation;
_tempParams.radius = this.settings.geoRadius;
}
this._autoCompleteSearchService.getGeoPrediction(_tempParams).then((result) => {
this.updateListItem(result);
});
}else {
Expand Down
63 changes: 51 additions & 12 deletions src/auto-complete.service.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Injectable, PLATFORM_ID, Inject } from '@angular/core';
import { Injectable, PLATFORM_ID, Inject } from '@angular/core';
import { isPlatformBrowser, isPlatformServer } from '@angular/common';
import { GlobalRef } from './windowRef.service';
import { Http } from '@angular/http';
Expand Down Expand Up @@ -98,29 +98,51 @@ export class AutoCompleteSearchService {
});
}

getGeoPrediction(query: string, countryRestriction: any): Promise<any> {
getGeoPrediction(params: any): Promise<any> {
return new Promise(resolve => {
if (isPlatformBrowser(this.platformId)) {
let _window: any = this._global.nativeGlobal;
let placesService: any = new _window.google.maps.places.AutocompleteService();
let queryInput: any = {};
if (countryRestriction.length) {
let promiseArr = [];
if (params.countryRestriction.length) {
queryInput = {
input: query,
componentRestrictions: {country: countryRestriction}
input: params.query,
componentRestrictions: {country: params.countryRestriction},
};
}else {
queryInput = {
input: query
input: params.query
};
}
placesService.getPlacePredictions(queryInput, (result: any, status: any) => {
if (status === _window.google.maps.places.PlacesServiceStatus.OK) {
resolve(result);
}else {
resolve(false);
if(params.geoLocation) {
queryInput.location = new _window.google.maps.LatLng(parseFloat(params.geoLocation[0]),parseFloat(params.geoLocation[1]));
queryInput.radius = params.radius
}
if(params.geoTypes.length) {
for(let i = 0;i<params.geoTypes.length;i++) {
let _tempQuery:any = queryInput;
_tempQuery['types'] = new Array(params.geoTypes[i]);
promiseArr.push(this.geoPredictionCall(placesService, _tempQuery));
}
});
}else {
promiseArr.push(this.geoPredictionCall(placesService, queryInput));
}

Promise.all(promiseArr).then(values => {
if(values.length > 1) {
let _tempArr = [];
for(let j = 0;j<values.length;j++) {
if(values[j] && values[j].length) {
_tempArr = _tempArr.concat(values[j]);
}
}
_tempArr = this.getUniqueResults(_tempArr);
resolve(_tempArr);
}else {
resolve(values[0]);
}
})
}else {
resolve(false);
}
Expand Down Expand Up @@ -200,4 +222,21 @@ export class AutoCompleteSearchService {
});
}

private getUniqueResults(arr: any): any {
return Array.from(arr.reduce((m, t) => m.set(t.place_id, t), new Map()).values())
}

private geoPredictionCall(placesService: any, queryInput: any): Promise<any> {
let _window: any = this._global.nativeGlobal;
return new Promise(resolve => {
placesService.getPlacePredictions(queryInput, (result: any, status: any) => {
if (status === _window.google.maps.places.PlacesServiceStatus.OK) {
resolve(result);
}else {
resolve(false);
}
});
});
}

}

0 comments on commit 8e69e8a

Please sign in to comment.