I have this React Native code and I want to use the gameOfTheUser variable (which is created in the arrow function: userRef. ounce (' value'). then (snapshot =>)outside this one. How to do it?
export default class RouterScreen extends React.Component {
componentDidMount() {
const { navigate } = this.props.navigation;
fb.auth().onAuthStateChanged(user => {
if (user) {
// user exist
var userRef = fb.database().ref('users/' + user.uid);
userRef.once('value').then(snapshot => {
var gameOfTheUser = snapshot.child('game').val();
console.log('0 : ' + gameOfTheUser);
});
if (gameOfTheUser !== null) {
// gameOfTheUser --> error variable is not created
// user in game --> redirect to Game (#user, #game)
console.log('InGame');
var gameID = gameOfTheUser;
console.log('1 : ' + gameOfTheUser);
console.log('1 : ' + gameID);
navigate('Game', { user, gameID });
} else {
// user not in game --> redirect vers Choose (#user)
console.log('NotInGame');
navigate('Choose', { user });
}
} else {
// user doesn't exist
navigate('Auth');
}
});
}
You could :
1) update a var which isn't in this scope but in the global scope (using events for example)
2) set a state/props (depend if it change or not) and share it with a main component
Related
I am trying to return an object of business details, address phone number, and website from Google API. I had used places API only to encounter cors issues as it is a server-side API. The recommendation is to use Places library but there is no reference/examples that I can find that will return what I want.
story would be
user enters the query string
application returns a single/list of business contact info based on a text search query
so that they can be displayed in a directory style listing
Thanks :)
Here is a sample I made that loads the Maps Javascript in React natively so that you can follow Google Map's documentations (i.e. Place Autocomplete + Place Details):
https://stackblitz.com/edit/react-place-autocomplete-64427213
import React, { Component } from "react";
import { render } from "react-dom";
var map;
class Map extends Component {
constructor(props) {
super(props);
this.state = {
input: ""
};
this.handleInputChange = this.handleInputChange.bind(this);
}
onScriptLoad() {
map = new window.google.maps.Map(
document.getElementById(this.props.id),
this.props.options
);
this.placeAutocomplete();
}
componentDidMount() {
if (!window.google) {
var s = document.createElement("script");
s.type = "text/javascript";
s.src = `https://maps.google.com/maps/api/js?key=YOUR_API_KEY&libraries=places`;
var x = document.getElementsByTagName("script")[0];
x.parentNode.insertBefore(s, x);
// Below is important.
//We cannot access google.maps until it's finished loading
s.addEventListener("load", e => {
this.onScriptLoad();
});
} else {
this.onScriptLoad();
}
}
handleInputChange(event) {
const target = event.target;
const value = target.type === "checkbox" ? target.checked : target.value;
const name = target.name;
this.setState({
[name]: value
});
}
placeAutocomplete() {
var autocomplete = new google.maps.places.Autocomplete(input);
// // Bind the map's bounds (viewport) property to the autocomplete object,
// // so that the autocomplete requests use the current map bounds for the
// // bounds option in the request.
autocomplete.bindTo("bounds", map);
// Set the data fields to return when the user selects a place.
autocomplete.setFields([
"address_components",
"geometry",
"formatted_phone_number",
"name",
"website"
]);
autocomplete.addListener("place_changed", function() {
var place = autocomplete.getPlace();
if (!place.geometry) {
// User entered the name of a Place that was not suggested and
// pressed the Enter key, or the Place Details request failed.
window.alert("No details available for input: '" + place.name + "'");
return;
}
// If the place has a geometry, then present it on a map.
var marker = new google.maps.Marker({
position: place.geometry.location,
map: map
});
map.setCenter(place.geometry.location);
map.setZoom(12);
//add an info window
var content =
"name: " +
place.name +
"<br />Latitude: " +
place.geometry.location.lat() +
"<br />Longitude: " +
place.geometry.location.lng() +
"<br />Phone No: " +
place.formatted_phone_number;
var infoWindow = new google.maps.InfoWindow({
content: content
});
//add a listner to the marker when clicked to show the info Window
marker.addListener("click", e => {
infoWindow.open(map, marker);
});
});
}
render() {
return (
<div>
<div>
<h3>Autocomplete search:</h3>
<input id="input" type="text" placeholder="Enter a location" />
</div>
<div className="map" id={this.props.id} />
</div>
);
}
}
export default Map;
I am trying to access value of a class variable in function setRating() but the console print is "undefined".
export class UserFeedbackComponent implements OnInit {
rating: number;
constructor() {
this.rating = 3;
}
ngOnInit() {
//initial setup
console.log("Rating " + this.rating);
document.addEventListener('DOMContentLoaded', function() {
let stars = document.querySelectorAll('.star');
stars.forEach(function(star) {
star.addEventListener('click', setRating);
});
let temps = parseInt(document.querySelector('.stars').getAttribute('data-rating'));
console.log("Rating 2: " + this.rating);
let target = stars[temps - 1];
target.dispatchEvent(new MouseEvent('click'));
});
}
function setRating(ev) {
//Printing 'undefined' in console.log
console.log('At top: ' + this.rating);
let span = ev.currentTarget;
let stars = document.querySelectorAll('.star');
let match = false;
let num = 0;
stars.forEach(function(star, index) {
if (match) {
star.classList.remove('rated');
} else {
star.classList.add('rated');
}
//are we currently looking at the span that was clicked
if (star === span) {
match = true;
num = index + 1;
}
});
this.rating = num;
console.log("value after update: " + this.rating)
document.querySelector('.stars').setAttribute('data-rating', num.toString());
}
}
the "value after update: " console log prints "undefined" unless this.rating is assigned to num. Can someone please help me with how to access the value of rating variable in setRating() function and how to update its value?
It's a context binding issue, you have to bind the setRating function to the class this otherwise it is going to use its own this which is different than the classes this no having access to this.rating. You can achieve this by using setRating.bind(this).
You can start by changing the DOMContentLoaded to an arrow function so that you inherit the context's this like so:
document.addEventListener('DOMContentLoaded', () => {
// this.rating is visible here now
...
})
Then you can do the same to the forEach handler:
stars.forEach((star) => {
// this.rating is visible here now too
...
});
Finally, you can bind the this of your external function to the classes this:
star.addEventListener('click', setRating.bind(this));
Your final code would be something like bellow:
export class UserFeedbackComponent implements OnInit {
rating: number;
constructor() {
this.rating = 3;
}
ngOnInit() {
//initial setup
console.log("Rating " + this.rating);
document.addEventListener('DOMContentLoaded', () => {
let stars = document.querySelectorAll('.star');
stars.forEach((star) => {
star.addEventListener('click', setRating.bind(this));
});
let temps = parseInt(document.querySelector('.stars').getAttribute('data-rating'));
console.log("Rating 2: " + this.rating);
let target = stars[temps - 1];
target.dispatchEvent(new MouseEvent('click'));
});
}
function setRating(ev) {
//Printing 'undefined' in console.log
console.log('At top: ' + this.rating);
let span = ev.currentTarget;
let stars = document.querySelectorAll('.star');
let match = false;
let num = 0;
stars.forEach(function(star, index) {
if (match) {
star.classList.remove('rated');
} else {
star.classList.add('rated');
}
//are we currently looking at the span that was clicked
if (star === span) {
match = true;
num = index + 1;
}
});
this.rating = num;
console.log("value after update: " + this.rating)
document.querySelector('.stars').setAttribute('data-rating', num.toString());
}
}
Further observation: You are declaring a function inside a class, that is totally unnecessary, you can declare it as a member
export class UserFeedbackComponent implements OnInit {
...
setRating(ev) {
...
}
}
Then you don't even ave to bind it to call it like so:
star.addEventListener('click', ev => this.setRating(ev));
You do not have to define the function using the function keyword in the class. Declare it as a class member and you should be able to access it normally. This is the best practice in Typescript class declaration.
EX:
export class UserFeedbackComponent implements OnInit {
rating: number;
constructor() {
this.rating = 3;
}
ngOnInit() {
//initial setup
console.log("Rating " + this.rating);
......................more code............
}
setRating(ev) {
//Printing 'undefined' in console.log
console.log(this.rating); //should work fine
// do other stuff with class members
}
}
I'm trying to call a few methods dynamically in my React component.
so I have this code where I want to call a function stepOne, stepTwo, etc. whenever that step is achieved, but this needs to be dynamically called to add new steps in the future.
However I tried already a couple of methods (hasOwnProperty,typeof this[methodName], this.{methodName}()) and can't get to call the right method.
Here is my code:
class MyComponent extends React.Component<Props,State>{
steps = [
'stepOne',
'stepTwo',
'stepThree',
];
state = {step:1};
stepOne(){
return 'This is Step One';
}
stepTwo(){
return 'This is Step Two';
}
_getContent(){
let content = 'Step not exists';
const methodName = this.steps[this.state.step - 1];
if (typeof this[methodName] === 'function') {
content = this[methodName]();
}
return content;
}
render(){
return '<div>' + this._getContent() + '</div>'
}
}
In this example, I always get undefined in the typeof this[methodName] operation
Try creating the map of functions and bind this context to your created functions
class MyComponent extends React.Component<Props,State>{
constructor(props){
super(props);
this.stepOne = this.stepOne.bind(this);
this.stepTwo = this.stepTwo.bind(this);
this.funcMap = {
'1': this.stepOne,
'2': this.stepTwo
};
this.state = {step: '1'};
}
stepOne(){
return 'This is Step One';
}
stepTwo(){
return 'This is Step Two';
}
_getContent(){
let content = 'Step not exists';
const method = this.funcMap[this.state.step];
if (typeof method === 'function') {
content = method();
}
return content;
}
render(){
return '<div>' + this._getContent() + '</div>'
}
}
In my Service i have the vars i want to display and the getters for it:
var docsLoaded = 0;
var docsToLoad = null;
pouchService.getDocsLoaded = function () {
return docsLoaded;
};
pouchService.getDocsToLoad = function () {
return docsToLoad;
};
While the service is syncing, i want to count the synced docs
pouchService.syncNow = function () {
var foundLastSeq = false;
docsLoaded = 0;
docsToLoad = null;
remoteDB.info().then(function (remoteInfo) {
function findOutDiff(localPosition) {
docsToLoad = (remoteInfo.update_seq - localPosition) + 1;
console.log("docs to load: " + docsToLoad);
}
// start Sync progress
sync = localDB.sync(remoteDB, {live: false})
.on('change', function (info) {
console.log('AI change: ');
console.log(info);
if (info.direction === 'pull') {
if (foundLastSeq === false) {
foundLastSeq = true;
findOutDiff(info.change.last_seq);
}
}
console.log(docsLoaded + " from " + docsToLoad);
docsLoaded++;
})
In my HTML i want to display the progress like this:
{{pouchService.getDocsLoaded()}} from {{pouchService.getDocsToLoad()}}
Now i get sometimes a value from getDocsLoaded, but mostly its zero. When I cancel the Syncprogress i get the value where it's stopped.
So i get the value before it really starts and when it's over, but i want it during the sync progress. (on the console my my progressinfos are working as expected)
Any ideas?
The problem is in applying scope. Jim wrote a nice article about this problem:
jimhoskins.com/2012/12/17/angularjs-and-apply.html
Solved it:
$rootScope.$apply(function () {
docsLoaded++;
});
I am new to Angular/Ionic.
Before using Angular/Ionic, at the launch of my app, I was checking if we were under Phonegap or a browser using and storing this information in a global boolean variable and then checking if the app was online or offline and storing it to a global variable too, like this :
var isPhoneGap;
var connectionStatus;
isPhoneGap = checkIfPhoneGap();
//later in the code :
connectionStatus = checkIfOnline();
function checkIfPhoneGap() {
var app = document.URL.indexOf( 'http://' ) === -1 && document.URL.indexOf( 'https://' ) === -1; // && document.URL.indexOf( 'file://' );
if ( app ) {
return true;
} else {
return false;
}
}
function checkIfOnline() {
if ( isPhoneGap ) {
if (checkConnection() == "none" ) {
connectionStatus = 'offline';
} else {
connectionStatus = 'online';
}
function checkConnection() {
var networkState = navigator.network.connection.type;
var states = {};
states[Connection.UNKNOWN] = 'Unknown connection';
states[Connection.ETHERNET] = 'Ethernet connection';
states[Connection.WIFI] = 'WiFi connection';
states[Connection.CELL_2G] = 'Cell 2G connection';
states[Connection.CELL_3G] = 'Cell 3G connection';
states[Connection.CELL_4G] = 'Cell 4G connection';
states[Connection.NONE] = 'No network connection';
//console.log('Connection : ' + Connection);
//console.log('Connection type: ' + states[networkState]);
return networkState;
}
} else {
connectionStatus = navigator.onLine ? 'online' : 'offline';
}
return connectionStatus;
}
Now I would like to do the same with Angular/Ionic, I understand that I have to use a "Service". But is it the best way to make this information available through all the code ?
I am doing the following, but is it the "best practice" ?
in index.html :
<script src="js/app.js"></script>
<script src="js/controllers.js"></script>
<script src="js/services.js"></script>
in services.js :
angular.module('SnowBoard.services', [])
.factory('isPhoneGap', function() {
var appp = document.URL.indexOf( 'http://' ) === -1 && document.URL.indexOf( 'https://' ) === -1; // && document.URL.indexOf( 'file://' );
if ( appp ) {
return true;
} else {
return false;
}
})
;
in app.js :
angular.module('SnowBoard', ['ionic', 'SnowBoard.controllers', 'SnowBoard.services'])
.run(["isPhoneGap","$ionicPlatform", function(isPhoneGap, $ionicPlatform) {
$ionicPlatform.ready(function() {
// Hide the accessory bar by default (remove this to show the accessory bar above the keyboard
// for form inputs)
if(window.cordova && window.cordova.plugins.Keyboard) {
cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true);
}
if(window.StatusBar) {
// org.apache.cordova.statusbar required
StatusBar.styleDefault();
}
});
//CHECK IF ONLINE
connectionStatus = checkIfOnline(isPhoneGap);
//DEBUG
//var debugOptionUseLocalDB=0;
//updateProDB(connectionStatus, debugOptionUseLocalDB);
}])
.config(function($stateProvider, $urlRouterProvider) {
//...all state configurations
})
.config(function($stateProvider, $urlRouterProvider) {
//...
});
This works for now, but I need the boolean isPhoneGap to be available everywhere I need it (almost everywhere in my app).
Can you converge to the best practice to do this ?
Thanks
You should not set variables using $rootScope, and try to refrain from using $scope as much as possible. Using LocalStorage is okay, but this data will persist. I would recommend setting up a factory to store and retrieve variables using SessionStorage. SessionStorage is tied to the tab you have open, so the data is gone when it is closed.
This is one of my session storage services. I throw $cookieStorage in case local storage isn't available. Also, localStorage can only store strings. This is why you will see me converting objects and arrays to and from JSON as needed. After injecting sessionService, I need only call sessionService.store(name, data) to store a session variable or sessionService.persist(name, data) to store persistent data i.e. userName if "Remember Me" is checked. :
.service('sessionService', ['$cookieStore', function ($cookieStore) {
var localStoreAvailable = typeof (Storage) !== "undefined";
this.store = function (name, details) {
if (localStoreAvailable) {
if (angular.isUndefined(details)) {
details = null;
} else if (angular.isObject(details) || angular.isArray(details) || angular.isNumber(+details || details)) {
details = angular.toJson(details);
};
sessionStorage.setItem(name, details);
} else {
$cookieStore.put(name, details);
};
};
this.persist = function(name, details) {
if (localStoreAvailable) {
if (angular.isUndefined(details)) {
details = null;
} else if (angular.isObject(details) || angular.isArray(details) || angular.isNumber(+details || details)) {
details = angular.toJson(details);
};
localStorage.setItem(name, details);
} else {
$cookieStore.put(name, details);
}
};
this.get = function (name) {
if (localStoreAvailable) {
return getItem(name);
} else {
return $cookieStore.get(name);
}
};
this.destroy = function (name) {
if (localStoreAvailable) {
localStorage.removeItem(name);
sessionStorage.removeItem(name);
} else {
$cookieStore.remove(name);
};
};
var getItem = function (name) {
var data;
var localData = localStorage.getItem(name);
var sessionData = sessionStorage.getItem(name);
if (sessionData) {
data = sessionData;
} else if (localData) {
data = localData;
} else {
return null;
}
if (data === '[object Object]') { return null; };
if (!data.length || data === 'null') { return null; };
if (data.charAt(0) === "{" || data.charAt(0) === "[" || angular.isNumber(data)) {
return angular.fromJson(data);
};
return data;
};
return this;
}])
$cookieStore is part of ngCookies. Make sure you have angular-cookies.js included and load ngCookies as you would any module. Angular ngCookies
First off, I'm pretty new to both Ionic and Angular, however I had the same problem with my web app Angular and I have done following to get it working
assign variables to $rootScope, that way it's visible to all the controllers
assign variables to $scope , which is visible by current context. Ex: controller and the html pages uses that controller
localStorageService, because this will hold the values even after user refreshes the page.
Again please note, this is what I did in my Angular web app and might not be the best practices, but I hope you get the idea.
There are methods available in ionic.Platform that assist in getting back the data you need in regards to the device type.
http://ionicframework.com/docs/api/utility/ionic.Platform/
You could alternatively look at adding ngCordova, which has a bunch of useful wrappers around cordova plugins, including the device plugin.
http://ngcordova.com/docs/#Device