Does Google Maps Javascript API works in Ionic 2? - javascript

I hope all of you are Ok :)
I am developing a simple application in Ionic Framework with Google Maps (Map + Places SearchBox). The problem is that all works good until I try to search an address (i. e. Morena Blvd #1, San Diego, CA), the input is showing me the suggested addresses but when I choose one of them It never completes the text in my <ion-input> element...
It works very good in my browser when I run my app with ionic serve but not in my iPhone or Android device. Have you experienced any issue like that?
Here is my code (frontend):
<ion-content no-padding>
<ion-searchbar #searchAddress id="searchAddress" [showCancelButton]="false" [(ngModel)]="address">
</ion-searchbar>
And this is my code to set the SearchBox instance to my ion-searchbar inner input control...
#ViewChild('searchAddress', {read: ElementRef}) searchElement: ElementRef;
...
...
...
...
let input = this.searchElement.nativeElement.querySelector('.searchbar-input');
let searchBox = new google.maps.places.SearchBox(input);
searchBox.addListener('places_changed', () => {
let places = searchBox.getPlaces();
if(!places && places.length === 0) return;
this.address = places[0].formatted_address;
this.latitude = places[0].geometry.location.lat();
this.longitude = places[0].geometry.location.lng();
if(places[0].address_components.length === 0) {
this.street = places[0].address_components[1].long_name;
this.selectedEstate = places[0].address_components[4].long_name;
}
});
And it's running in my ionViewDidEnter() event

Try wrapping everything inside your listener in an NgZone:
import { NgZone } from '#angular/core';
export class myPage {
zone: NgZone;
constructor() {
this.zone = new NgZone({enableLongStackTrace: false});
}
...
searchBox.addListener('places_changed', () => {
this.zone.run(() => {
let places = searchBox.getPlaces();
if(!places && places.length === 0) return;
this.address = places[0].formatted_address;
this.latitude = places[0].geometry.location.lat();
this.longitude = places[0].geometry.location.lng();
if(places[0].address_components.length === 0) {
this.street = places[0].address_components[1].long_name;
this.selectedEstate = places[0].address_components[4].long_name;
}
});
});
}
If that works then it means that your listener-callback is executed outside the angular context, which means it does not trigger change detection and doesn't update the value. I had a similar problem once (only mobile phones affected) so it might be related to performance or efficiency optimizations to save battery life.

Related

How could I display distanceMsg on each elements in AR.js?

I'm beginner of using AR.js; Could you please suggest me how can I display distanceMsg on each elements like above or below tag (or could you please suggest me if there are other way to display it) in AR.js. Like this example image
I've found this on AR.js website, but I have no clue how can I display on each A-Frame tag like.
const distanceMsg = document.querySelector('[gps-entity-place]').getAttribute('distanceMsg');
Here are my script code
window.onload = () => {
const scene = document.querySelector('a-scene');
return navigator.geolocation.getCurrentPosition(function (position) {
loadPlaces(position.coords)
.then((places) => {
places.forEach((place) => {
const latitude = place.location.lat;
const longitude = place.location.lng;
const marker = place.location.mark;
const pin = document.createElement('a-image');
pin.setAttribute('gps-entity-place', `latitude: ${latitude}; longitude: ${longitude}`);
pin.setAttribute('look-at', '[gps-camera]');
pin.setAttribute('name', place.name);
pin.setAttribute('src', `${marker}`);
pin.setAttribute('scale', '2, 2');
pin.addEventListener('loaded', () => window.dispatchEvent(new CustomEvent('gps-entity-place-loaded')));
const clickListener = function(ev) {
ev.stopPropagation();
ev.preventDefault();
const name = ev.target.getAttribute('name');
const el = ev.detail.intersection && ev.detail.intersection.object.el;
if (el && el === ev.target) {
const label = document.createElement('span');
const container = document.createElement('div');
container.setAttribute('id', 'place-label');
label.innerText = name;
container.appendChild(label);
document.body.appendChild(container);
setTimeout(() => {
container.parentElement.removeChild(container);
}, 1500);
}
};
pin.addEventListener('click', clickListener);
scene.appendChild(pin);
});
})
},
(err) => console.error('Error in retrieving position', err),
{
enableHighAccuracy: true,
maximumAge: 0,
timeout: 27000,
}
);
};
Thank you so much.
See the fundamentals of adding entity's via script
Looks like you need to Use A-frame register component instead of vanilla js. Very important to take this in and undertstand this and not try cherry-pick with this type of framework, otherwise, you risk using this out of context.
This is a finnicky framework where many things need to go right for location-based to work, so it's worth grabbing a coffee, taking some good time reading up.
Remember: Do not try to put A-Frame-related JavaScript in a raw tag after as we would with traditional 2D scripting. If we do, we’d have to take special measures to make sure code runs at the right time. See Running content scripts at the right time

View not updating after app suspension/resumption

I have a component in which i subscribe to an Observable. In the callback i set member variables in that component. Those variables’ values will then be displayed on the component page.
But, when the app goes out of scope (e.g. by manually doing so or by the Android OS-Popup asking for location permission) and comes back in, the view is not updated anymore (though the callback still receives new values as the console log proves).
A working example would a blank starter app with the following content of the homepage class (requires the cordova geolocation plugin)
export class HomePage implements OnInit {
lat = 0;
long = 0;
private subscription: Subscription;
constructor(private geolocation: Geolocation,
private zone: NgZone)
{
}
ngOnInit()
{
console.log('constructor() Subscribing');
this.renewSubscription();
}
renewSubscription()
{
if(this.subscription)
{
this.subscription.unsubscribe();
this.subscription = null;
}
this.subscription = this.geolocation
.watchPosition({ enableHighAccuracy : true})
.subscribe(this.onLocationChange);
}
private onLocationChange = (location: Geoposition) =>
{
if(location.coords)
{
console.log(location.coords.latitude + ':' + location.coords.longitude);
this.lat = location.coords.latitude;
this.long = location.coords.longitude;
}
else
{
console.dir('no coordinates');
console.dir(location);
}
}
}
and the following as a replacement for the Home page html content
{{ lat }}:{{ long }}.
As a bit of searching, i suspect that after the app resumption many of the the code does not live in the angular zone anymore or something like this, because when setting lat and long inside the zone explicitly it works again
this.zone.run(() => {
this.lat = location.coords.latitude;
this.long = location.coords.longitude;
});
The thing is that this is a very over-simplified version of the app i'm working on - which has a lot of subscriptions and indirections, so i was not able to find the code i have to put inside the angular zone manually.
Is there a way to run the app's code within the angular zone after app resumption just like it would after a normal app start?
EDIT
Putting the code into platform.resume won't work either.

Failed to execute 'start' on 'SpeechRecognition': recognition has already started

I am using a wrapper of Web Speech API for Angular6. I am trying to implement a system of starting-stopping after each 3.5s in order to be able to manipulate the results for these small parts.
Even though I stop the recognition, before starting it again, I keep getting this error Failed to execute 'start' on 'SpeechRecognition': recognition has already started.
As suggested in this post, I first verify whether the speech recognition is active or not and only if not active, I try to start it. https://stackoverflow.com/a/44226843/6904971
Here is the code:
constructor( private http: Http, private service: SpeechRecognitionService, private links: LinksService) {
var recognizing; // will get bool values to verify if recognition is active
this.service.onresult = (e) => {
this.message = e.results[0].item(0).transcript;
};
this.service.onstart = function () {
recognizing = true;
};
this.service.onaudiostart = function () {
recognizing = true;
};
this.service.onerror = function (event) {
recognizing = false;
};
this.service.onsoundstart = function () {
recognizing = true;
};
this.service.onsoundstart = function () {
recognizing = true;
};
this.record = () => {
this.service.start();
setInterval(root.ongoing_recording(), 3500);
};
var root = this;
var speech = '';
this.stop_recording = () => {
this.service.stop();
};
this.ongoing_recording = ()=> {
setTimeout(function(){
if( recognizing === true){
root.service.stop();
root.service.onend = (e) => {
recognizing = false;
speech = root.message;
var sentence = document.createElement('span');
sentence.innerHTML = speech + " ";
document.body.appendChild(sentence);
}
}
}, 3500);
setTimeout(function(){
if(recognizing === false){
root.service.start();
}
}, 3510);
};
}
start() {
this.service.start();
}
stop() {
this.service.stop();
}
record(){
this.record();
}
stop_recording(){
this.stop_recording();
}
ongoing_recording(){
this.ongoing_recording();
}
I think that the timing might not be good (with the setTimeout and interval). Any help would be much appreciated. Thank you! :)
I used Web Speech API for voice search functionality in my site and I was facing a similar sort of situation. It has one microphone icon which toggles the speech recognition on and off. It was working fine in the normal on and off of the button that started speech recognition but was breaking only if you test it rigorously with a continuous button toggle.
Solution:
The thing that worked for me is:
try{
//line of code to start the speech recognition
}
catch{
//line of code to stop the speech recognition
}
So I wrapped the .start() method which was breaking the application in a try block and then added the catch block to stop it. And even if it comes across this problem, on the next button click to turn on the speech recognition, it works. I hope you would be able to extract something from it.
one observation:
you run setInterval() every 3500 ms to invoke ongoing_recording(), but then use setTimeout() with 3500 ms again within ongoing_recording().
Besides that, maybe logging the error handler --where recognizing is also set to false-- could help finding a solution:
in past versions of the SpeechRecognition implementation, not every error did actually stop the recognition (I don't know if that is still the case).
So it might be the case, that recognizing is reset due to an error that did not actually stop the recognition; if this is really the cause of the error when restarting recognition, it could be just catched & ignored.
Also it might be worth trying to re-start the recognition in the onend handler (and onerror).
I am not sure what is the reason that is causing it in your code, but i had the same error and what caused it in my case was that I was calling start() twice in a row, so what fixed it was adding a variable to check if the recognition has started or stopped, so if it has started and I clicked it again it would return speach.stop() to avoid using start() again.
let recognition = new SpeechRecognition();
let status = 0;
document.querySelector(".mic").addEventListener("click",() => {
if (status == 1) {
status = 0;
return recognition.stop();
}
recognition.start();
status = 1;
recognition.onresult = function (event) {
status=0;
var text = event.results[0][0].transcript;
recognition.stop();
};
recognition.onspeechend = function () {
status = 0;
recognition.stop();
};
});

Filter google maps custom overlays depending on whether they fall within the current map bounds

I am struggling to find a proper way of rendering custom google map markers (overlays) in a react-redux project. What I have is a page showing a map and a search box. When someone searches for a place and the place is found, this triggers the map idle event, then I update map bounds and searched place info and save them in redux store. And then fetch data with current map bounds and city name. When data arrives, filter the listings (The filtering will go to the backend in future, which means the server will send already filtered listing which fall in the current viewport) and render custom overlays for each listing on the map.
On map idle event:
1) update map bounds and searched place name
2) fetch some data (json format) from a server
3) filter listings so we can render only the ones whose position falls in the current viewport (map bounds)
4) render custom overlay for each listing
On every map idle event, which appears to happen after you zoom or pan on the map, the whole process of updaitng, fetching, filtering and rendering repeats.
What I have done so far is that the project is working till the point when React needs to determine which overlays should be removed and which one should be re-drawn.
Actually what is not working correctly now is when the visible listings array updates, React just removes the listings which are at the end of the array (from the last index to 0) and not the correct one (the one whose position is out of the viewport).
Also sometimes, if you have searched for a place already and played around with the map a little bit and try to search for another place, the new place overlays are not positioned correctly. Instead they are way off the map viewport.
I am relatively new with all Ract, Redux and Google Maps Api technologies so I am aware I might be doing something really stupid. I hope someone here will be able to point me in the right direction. I have searched the entire net and I could not find a proper answer. I have found some helpful info about how to create custom overlays and how to create react component for google map and I also know there a couple of good npm modules which can do the job I want (such as this one: react-google-maps and this one: google-map-react) but they are all having their own problems and they are too complex for what I am trying to achieve.
I am sorry for pasting all of the code here but I am not sure how to represent the whole project environment in a jsbin or similar code bin. Please let me know if I need to make such a code working example and I will try.
Here is the code I have right now. Of course this is just the part which is important for the problem:
The map component
import React, { PropTypes, Component } from 'react';
import SearchBar from '../SearchBar';
import OverlayViewComponent from '../OverlayViewComponent';
import OverlayViewContent from '../OverlayViewContent';
import mapOptions from './cfg';
import MapStyles from './map.scss';
const propTypes = {
getListings: PropTypes.func.isRequired,
updateMapState: PropTypes.func.isRequired,
visibleListings: PropTypes.array.isRequired,
};
class GoogleMap extends Component {
constructor() {
super();
this._onMapIdle = this._onMapIdle.bind(this);
this.onPlacesSearch = this.onPlacesSearch.bind(this);
}
_initMap(mapContainer) {
// Create a new map
this._map = new google.maps.Map(mapContainer, mapOptions);
this._bindOnMapIdleEvent();
};
_bindOnMapIdleEvent() {
// Attach idle event listener to the map
this._map.addListener('idle', this._onMapIdle);
}
_onMapIdle() {
const { updateMapState, getListings } = this.props;
if (this._searchedPlace) {
console.log('ON MAP IDLE');
let mapBounds = this._map.getBounds().toJSON();
updateMapState(mapBounds, this._searchedPlace);
getListings();
}
};
onPlacesSearch(searchedPlace) {
if (searchedPlace.name !== '' && searchedPlace.geometry !== null) {
// Clear out the old marker if present.
if (this._searchedPlaceMarker) {
this._searchedPlaceMarker.setMap(null);
this._searchedPlaceMarker = null;
}
let bounds = new google.maps.LatLngBounds();
// Create a marker for the searched place.
this._searchedPlaceMarker = new google.maps.Marker({
map: this._map,
title: searchedPlace.name,
position: searchedPlace.geometry.location
});
if (searchedPlace.geometry.viewport) {
// Only geocodes have viewport.
bounds.union(searchedPlace.geometry.viewport);
} else {
bounds.extend(searchedPlace.geometry.location);
}
// Save currently searchedPlace into the class local variable
this._searchedPlace = searchedPlace;
// Set map so it contains the searchedPlace marker (Ideally it should be only one)
this._map.fitBounds(bounds);
} else {
return;
}
}
componentDidMount() {
// When component is mounted, initialise the map
this._initMap(this._mapContainer);
};
shouldComponentUpdate(nextProps) {
if (nextProps.visibleListings.length == this.props.visibleListings.length) {
return false;
} else {
return true;
}
};
componentWillUnmount() {
google.maps.event.clearInstanceListeners(this._map);
};
render() {
console.log('GOOGLEMAP RENDER');
return (
<div id="mapContainer">
<div id="mapCanvas" ref={(mapContainer) => this._mapContainer = mapContainer}></div>
<SearchBar onPlacesSearch={this.onPlacesSearch} />
{
this.props.visibleListings.map((listing, index) => {
return (
<OverlayViewComponent key={index} mapInstance={this._map} position={listing.geo_tag}>
<OverlayViewContent listingData={listing} />
</OverlayViewComponent>
);
})
}
</div>
);
}
};
GoogleMap.propTypes = propTypes;
export default GoogleMap;
The OverlayView Component
import React, { PropTypes, Component } from 'react';
import OverlayView from './utils/overlayViewHelpers';
const propTypes = {
position: PropTypes.array.isRequired,
mapInstance: PropTypes.object.isRequired,
};
class OverlayViewComponent extends Component {
componentDidMount() {
this._overlayInstance = new OverlayView(this.props.children, this.props.position, this.props.mapInstance);
};
componentWillUnmount() {
this._overlayInstance.setMap(null);
};
render() {
return null;
}
};
OverlayViewComponent.propTypes = propTypes;
export default OverlayViewComponent;
The OverlayView class
import ReactDOM from 'react-dom';
const EL_WIDTH = 30;
const EL_HEIGHT = 35;
function OverlayView(element, position, map) {
this.overlayContent = element;
this.point = new google.maps.LatLng(position[0], position[1]);
this.setMap(map);
}
OverlayView.prototype = Object.create(new google.maps.OverlayView());
OverlayView.prototype.constructor = OverlayView;
OverlayView.prototype.onAdd = function() {
console.log('onAdd');
this.containerElement = document.createElement('div');
this.containerElement.style.position = 'absolute';
this.containerElement.style.width = EL_WIDTH + 'px';
this.containerElement.style.height = EL_HEIGHT + 'px';
let panes = this.getPanes();
panes.overlayMouseTarget.appendChild(this.containerElement);
ReactDOM.render(this.overlayContent, this.containerElement);
};
OverlayView.prototype.draw = function() {
console.log('draw');
if (this.containerElement) {
let projection = this.getProjection();
let projectedLatLng = projection.fromLatLngToDivPixel(this.point);
console.log(projectedLatLng);
this.containerElement.style.top = projectedLatLng.y - EL_HEIGHT + 'px';
this.containerElement.style.left = projectedLatLng.x - Math.floor(EL_WIDTH / 2) + 'px';
}
};
OverlayView.prototype.onRemove = function() {
console.log('onRemove');
let parentEl = this.containerElement.parentNode;
parentEl.removeChild(this.containerElement);
ReactDOM.unmountComponentAtNode(this.containerElement);
};
export default OverlayView;
The OverlayView Content component
import React, { PropTypes } from 'react';
import markerIcon from '../../../images/icon-marker.png';
const propTypes = {
listingData: PropTypes.object.isRequired,
};
const OverlayViewContent = (listingData) => {
console.log('OverlayViewContent render');
return (
<div className="customIcon">
<img src={markerIcon} title={listingData.where} />
</div>
);
};
OverlayViewContent.propTypes = propTypes;
export default OverlayViewContent;
This might help you although GMaps is used for this app.
I think I have found the root of the problem.
Thanks to this post, and more specifically the comment from Sebastien Lorber. He made me take a more thourough look at the react documentation about the keys attributes
It appears that if I add a more unique key to the react components when I am looping trough the visibleListings array, everything works as expected. The problem was that i was using array indexes for the components keys. This way when the visibleListings array updates, the key for the overlay component which need to be unmount is removed from the array and react does not know which component should be unmount. Thus react always remove the last overlay component from the array.

Javascript pattern for maintaining state and creating nested objects that provide additional functionality

I am writing a Javascript API library that provides consumers with an interface that enables them to interact with our backend web services. It is envisioned that a consumer will be writing a javascript client web application that draws heavily on the API provided for by the library.
I have come up with this "pattern" for maintaining state and making functionality "available" as certain criteria are met (for example, an authenticated user is logged in client-side).
Is this an appropriate way to achieve that end? Or am I unwittingly breaking some convention or best practice that will bite me later on?
// file: clientApi.js (library)
ClientObject = function () {
this.objectname = "a client class";
}
ClientObject.prototype.loginUser = function(name) {
this.loggedin = true;
if (typeof this.User === 'undefined') {
this.User = new ClientObject.User(name);
}
}
ClientObject.User = function (name) {
this.username = name;
}
ClientObject.User.prototype.getProfile = function() {
return 'user profile';
}
// file: app.js (consuming application)
var testClient = new ClientObject();
console.log('testClient.User = ' + (typeof testClient.User)); // should not exist
testClient.loginUser('Bob'); // should login 'bob'
console.log('testClient.User = ' + (typeof testClient.User)); // should exist
console.log(testClient.User.username); // bob
testClient.loginUser('Tom'); // should not do anything
console.log(testClient.User.username); // bob still
console.log(testClient.User.getProfile()); // tada, new functionality available
My question: is this approach valid? Is there a pattern that I'm touching on that might offer a better explanation or method of achieving my end goal?
I asked a similar question here with a bunch of other ones, unfortunately the above code was somewhat lost in the noise: Javascript: creation of object from an already instantiated object versus the prototype
Your API should have some secrets. That's why do not make all your functions public. Let's analyze some parts of your code:
testClient.loginUser('Tom'); // should not do anything
But your implementation allows client to do next:
testClient.User = new ClientObject.User(name);
Now user will be changed to "Tom".
Let's change your clientApi.js code, using revealing prototype pattern:
ClientObject = function () {
this.objectname = "a client class";
this.username;
this.User;
this.loggedin;
}
ClientObject.prototype = function() {
var loginUser = function(name) {
this.loggedin = true;
if (typeof this.User === 'undefined') {
this.User = new User(name);
}
};
var User = function (name) {
this.username = name;
};
User.prototype.getProfile = function() {
return 'user profile';
};
return {
loginUser : loginUser
}
}()
Now client cannot change logged in User like in first version of the library. You can use some variations, but that's the idea.

Categories

Resources