Vaadin 14 - Error: Detected container element removed from DOM - vaadin

Update 1: ============
After remove "window.ShadyDOM={force:true};" then it worked. But this is causing other problem ;):
https://vaadin.com/forum/thread/17399734/leverage-browser-save-password-feature
#Override
public void configurePage(InitialPageSettings settings) {
// TODO Auto-generated method stub
settings.addMetaTag("mobile-web-app-capable", "yes");
settings.addMetaTag("apple-mobile-web-app-capable", "yes");
settings.addMetaTag("apple-mobile-web-app-status-bar-style", "black");
settings.addInlineWithContents(
InitialPageSettings.Position.PREPEND, "window.customElements=window.customElements||{};"
+ "window.customElements.forcePolyfill=true;" + "window.ShadyDOM={force:true};",
InitialPageSettings.WrapMode.JAVASCRIPT);
}
End Update. ============
I am trying to integrate Payal checkout to Vaadin 14.7 (Spring core, not spring boot).
Here is paypal-view.js
import { LitElement, html, css } from "lit-element";
let buttons;
let hasRendered = false;
class PaypalElement extends LitElement {
static get properties() {
return {
mood: {
type: String,
noAccessor: false,
hasChanged(newVal, oldVal) {
console.log("newVal " + newVal + " oldVal " + oldVal);
},
},
};
}
static get styles() {
return [
css`
mood_color {
color: green;
}
#paypal-button {
size: "responsive";
}
`,
];
}
firstUpdated(_changedProperties) {
let testFname = this.shadowRoot.getElementById("fname");
super.firstUpdated(_changedProperties);
if (buttons && buttons.close && hasRendered) {
buttons.close();
hasRendered = false;
}
buttons = window.paypal.Buttons({
// Set up the transaction
createOrder: function (data, actions) {
return actions.order.create({
application_context: {
brand_name: "Brand name",
user_action: "PAY_NOW",
//No shipping for in-tangible merchant
shipping_preference: "NO_SHIPPING",
payment_method: {
payee_preferred: "IMMEDIATE_PAYMENT_REQUIRED", // Pending status transactions will not be allowed if you pass this parameter.
payer_selected: "PAYPAL",
},
},
purchase_units: [
{
soft_descriptor: "CC_STATEMENT_NAME",
amount: {
value: "5.00",
},
},
],
});
},
// Finalize the transaction
onApprove: function (data, actions) {
const elementText = document.getElementById("fname");
return actions.order.capture().then(function (orderData) {
// Successful capture! For demo purposes:
console.log(
"Capture result",
orderData,
JSON.stringify(orderData, null, 2)
);
let transaction = orderData.purchase_units[0].payments.captures[0];
// Replace the above to show a success message within this page, e.g.
// const element = document.getElementById('paypal-button-container');
// element.innerHTML = '';
// element.innerHTML = '<h3>Thank you for your payment!</h3>';
// Or go to another URL: actions.redirect('thank_you.html');
document.getElementById("update-paypal-trans").innerHTML =
"update-paypal-trans = " + transaction.id;
// trigger lit event
testFname.value = transaction.id;
testFname.click();
document.getElementById("paypalelement").remove();
//console.log(elementText);
});
},
onError: function (error) {
console.log("onError", error, JSON.stringify(error, null, 2));
},
onCancel: function (data, actions) {
console.log("onCancel", data, JSON.stringify(data, null, 2));
document.getElementById("update-paypal-trans").innerHTML =
"testing 123";
// update shadow element
testFname.value = "12345 " + actions;
// trigger lit event
testFname.click();
},
});
// load paypal buttons and put them to element id="paypal-button-to-display" which is shadow dom
buttons
.render(this.shadowRoot.getElementById("paypal-button-to-display"))
.then(() => {
hasRendered = true;
})
.catch((err) => {
console.log(err)
// not mounted anymore, we can safely ignore the error
return;
});
}
// outside updates shadow element
updateShadow() {
this.shadowRoot.getElementById("test-update-shadow").innerHTML =
"test update shadow trans";
this.mood = "nice";
}
updateTask(e) {
console.log("updateTask: " + e);
}
updateTaskClick(e) {
console.log("updateTaskClick: " + e);
// call back-end
}
render() {
return html`
Web Components are
<span class="mood_color"> ${this.mood} and ${this.innerHTML}</span>!
<input
type="text"
id="fname"
name="fname"
value="${this.mood}"
#change=${(e) => this.updateTask(e.target.value)}
#click="${(e) => this.updateTaskClick(e.target.value)}"
/>
<div id="paypal-button-to-display"></div>
<br />
<div id="test-update-shadow">test-update-shadow-default</div>
<br />
<input
#click="${() => this.updateShadow()}"
id="myinput"
type="button"
value="update shadow button"
/>
`;
}
attributeChangedCallback(name, oldval, newval) {
super.attributeChangedCallback(name, oldval, newval);
console.log("attribute change: ", name, newval);
}
changeProperties() {
let randomString = Math.floor(Math.random() * 100).toString();
this.mood = "myProp " + randomString;
console.log("randomString change: ", randomString);
}
}
customElements.define("paypal-element", PaypalElement);
hello-world-view.ts
import { html, LitElement, customElement } from 'lit-element';
import './paypal-view';
import '#vaadin/vaadin-button';
import '#vaadin/vaadin-text-field';
#customElement('hello-world-view')
export class HelloWorldView extends LitElement {
createRenderRoot() {
// Do not use a shadow root
return this;
}
constructor() {
super();
}
render() {
return html`
<vaadin-text-field id="name" label="Your name"></vaadin-text-field>
<vaadin-button >Say hello</vaadin-button>
<paypal-elementt mood="great" id="paypalBut">hello customer</paypal-elementt>
`;
}
}
hello-world-view.ts
package com.lecompany.iad.view;
import com.lecompany.iad.app.MainView;
import com.vaadin.flow.component.Tag;
import com.vaadin.flow.component.UI;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.dependency.JsModule;
import com.vaadin.flow.component.littemplate.LitTemplate;
import com.vaadin.flow.component.notification.Notification;
import com.vaadin.flow.component.template.Id;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.router.RouteAlias;
/**
* A Designer generated component for the stub-tag template.
*
* Designer will add and remove fields with #Id mappings but does not overwrite
* or otherwise change this file.
*/
#PageTitle("Hello World")
#Route(value = "hello", layout = MainView.class)
#Tag("hello-world-view")
#JsModule("./src/view/hello-world-view.ts")
public class HelloWorldView extends LitTemplate {
#Id
private TextField name;
// #Id
// private Button sayHello;
public HelloWorldView() {
UI.getCurrent().getPage().addJavaScript("https://www.paypal.com/sdk/js?client-id=AejZZjQsvxS299I2_LSnRkJStp0AsBzScCqbGK1_W6RNJssNR5NVnKYd97dM2kOJnRMF1u1ldLGjOlZ5&currency=USD");
// sayHello.addClickListener(e -> {
// Notification.show("Hello " + name.getValue());
// });
}
}
And here is error:
I tested above codes in Spring boot, then it works fine.
but it got error in the normal spring code. Any advice on this ?
There is reported issue for Paypal.
Paypal reported issue

It is a Vaadin bug and reported here:
Vaadin bug

Related

Apple Pay using #stripe/stripe-react-native is not working showing some hook call error

When i am trying to implement Apple Pay using #stripe/stripe-react-native then is not working showing some hook call , Code & Error showing below:
import { StripeProvider, useApplePay} from '#stripe/stripe-react-native';
const { presentApplePay, confirmApplePayPayment } = useApplePay();
export default class App (){
handlePayPress = async () => {
const {error, paymentMethod} = await presentApplePay({
cartItems: [
{
label: 'payment label',
amount: '50', // amount as string
type: 'final',
},
],
country: 'US', // enter any country code supported by stripe,
currency: 'USD', // enter any currency supported by stripe,
});
if (error) {
Alert.alert(error.code, error.message);
} else {
const {error: confirmApplePayError} = await confirmApplePayPayment(
clientSecret,
);
confirmApplePayPayment(clientSecret);
if (confirmApplePayError) {
Alert.alert(confirmApplePayError.code, confirmApplePayError.message);
} else {
Alert.alert('Success', 'The payment was confirmed successfully!');
}
}
};
...
...
}
You can create a seperate functional component for that and call in your class component like this
export default class App (){
render() {
return(
<ApplePayFunction data={'your data here'}/>
);
}
}
export default function ApplePayFunction(props) {
const { presentApplePay, confirmApplePayPayment } = useApplePay();
const handlePayPress = async () => {
const { error, paymentMethod } = await presentApplePay({
cartItems: [
{
label: "payment label",
amount: "50", // amount as string
type: "final",
},
],
country: "US", // enter any country code supported by stripe,
currency: "USD", // enter any currency supported by stripe,
});
if (error) {
Alert.alert(error.code, error.message);
} else {
const { error: confirmApplePayError } = await confirmApplePayPayment(
clientSecret
);
confirmApplePayPayment(clientSecret);
if (confirmApplePayError) {
Alert.alert(confirmApplePayError.code, confirmApplePayError.message);
} else {
Alert.alert("Success", "The payment was confirmed successfully!");
}
}
};
// Return UI Views below
return null;
}
Hooks must always be called inside functional components. Code refactor for reference.
import { StripeProvider, useApplePay } from "#stripe/stripe-react-native";
export default function App() {
const { presentApplePay, confirmApplePayPayment } = useApplePay();
const handlePayPress = async () => {
const { error, paymentMethod } = await presentApplePay({
cartItems: [
{
label: "payment label",
amount: "50", // amount as string
type: "final",
},
],
country: "US", // enter any country code supported by stripe,
currency: "USD", // enter any currency supported by stripe,
});
if (error) {
Alert.alert(error.code, error.message);
} else {
const { error: confirmApplePayError } = await confirmApplePayPayment(
clientSecret
);
confirmApplePayPayment(clientSecret);
if (confirmApplePayError) {
Alert.alert(confirmApplePayError.code, confirmApplePayError.message);
} else {
Alert.alert("Success", "The payment was confirmed successfully!");
}
}
};
// Return UI Views below
return null;
}

submitCount keeps reseting unexpectedly

I have a simple form that submits an update request to a server. I would like to display a success/error message for a few seconds after the form is submitted and then reset the form. However, if the user happens to submit subsequent requests in between the last submit and the end of the delay to perform the reset, then I don't want to perform the reset.
To accomplish this I was hoping to use the submitCount found in the FormState. However, I am finding that the submitCount is resetting automatically - even when I don't call reset at all. Also when I call reset and pass to it keepSubmitCount: true
Here's a skeleton of what I'm doing:
import { IToolPanelParams, SelectionChangedEvent } from "ag-grid-community";
import React, { useEffect, useMemo, useState } from "react";
import styled from "styled-components";
import { FieldValues, SubmitErrorHandler, SubmitHandler, useForm, useFormState} from "react-hook-form";
interface MyFieldValues extends FieldValues {
note: string;
}
export const MyToolPanel = (props: IToolPanelParams) => {
const [selected, setSelected] = useState<Record[]>();
const { register, handleSubmit, reset, control } = useForm<MyFieldValues>();
const { errors, submitCount, isSubmitting, isSubmitted, isSubmitSuccessful } = useFormState({ control });
...
const onSaveSubmit: SubmitHandler<MyFieldValues> = async (data, e) => {
return peform_update();
}
useEffect(() => {
if(isSubmitted) {
console.log(`successful? (${isSubmitSuccessful}) submit on #${submitCount}`);
const currSubmitCount = submitCount;
setTimeout(() => {
if(!isSubmitting && submitCount == currSubmitCount) {
console.log(`reset after submit #${currSubmitCount}`);
/*reset({
keepValues: true,
keepDefaultValues: true,
keepSubmitCount: true
});
*/
}
}, 3000);
}
}, [isSubmitted, submitCount, reset])
const onError: SubmitErrorHandler<BulkEditNotesFieldValues> = (errors) => {
console.error(`BulkEditNotesToolPanel::onError - errors.note. type: ${errors.note?.type}. types: ${JSON.stringify(errors.note?.types)}. message: ${errors.note?.message}`);
}
return (
<Container>
<Spin spinning={isSubmitting}>
<h2>Bulk Edit Records</h2>
{ !!selected?.length && (<>
<form onSubmit={handleSubmit(onSaveSubmit, onError)}>
<label htmlFor="Note">
<textarea
style={textAreaStyle}
{...register("note", {
required: { value: true, message: 'Note required'},
pattern: { value: /[^\s]/, message: 'Note cannot be empty'}
})}
/>
</label>
{ errors?.note && (
<Error>{errors.note.message}</Error>
)}
<button
type="submit"
style={submitStyle}
disabled={!selected?.length}
>
Save { selected?.length > 1? 'Records' : 'Record' }
</button>
</form>
</>)}
{ isSubmitted && isSubmitSuccessful && (
<SuccessMessage>Successfully Updated The Selected Record(s)</SuccessMessage>
)}
{ updateError && (<>
<Error>An error occurred while attempting to update the selected records...</Error>
</>)}
</Spin>
</Container>
);
};
While I can of course create my own submit counter via useState, I was hoping to figure out why react-hook-form's submitCount is resetting on me and how to prevent that? Thanks!

Ng2-Charts Unexpected change chart's color when data is changed

In my project I use ng2-charts. All works fine and chart is shown as expected (data, labels, chart's colors), but when data is changed then color of chart become grey by default. May someone help to correct problem with chart's color?
Here is my code:
import { ChartDataSets } from 'chart.js';
import { Color, Label } from 'ng2-charts';
...
export class JuridicalBidPrimaryComponent extends BidComponent {
lineChartData: ChartDataSets[];
lineChartLabels: Label[];
lineChartLegend = true;
lineChartType = 'line';
lineChartColors: Color[] = [
{
backgroundColor: 'rgba(148,159,177,0.2)',
borderColor: 'rgba(148,159,177,1)'
},
{
backgroundColor: 'rgba(77,83,96,0.2)',
borderColor: 'rgba(77,83,96,1)'
}];
options: any = {
legend: { position: 'bottom' }
}
constructor(
...//inject services
) {
super();
this.initData();
};
initData(): void {
this.lineChartData = [];
this.lineChartLabels = [];
if (this.cabinetId)
this.getData(this.year);
}
getData(year: number) {
this.isLoading = true;
var limitPromise = this.juridicalLimitService.getPrimary(this.cabinetId, year).catch(error => {
this.notificationService.error(error);
return Observable.throw(error);
});
var analyticsPromise = this.juridicalAnalyticsService.getUsedEnergy(this.cabinetId, year).catch(error => {
this.notificationService.error(error);
return Observable.throw(error);
});
forkJoin([limitPromise, analyticsPromise]).subscribe(data => {
this.limits = data[0];
this.lineChartLabels = data[1].map(e => e.Period);
this.lineChartData.push(
{
data: data[1].map(e => e.Limit),
label: 'Bid'
},
{
data: data[1].map(e => e.Used),
label: 'Used'
}
);
this.isLoading = false;
}, error => {
this.isLoading = false;
});
}
}
export abstract class BidComponent {
cabinetId: number;
isLoading: boolean = false;
#Input("periods") periods: BaseDictionary[];
#Input("cabinetId") set CabinetId(cabinetId: number) {
this.cabinetId = cabinetId;
this.initData();
}
abstract initData(): void;
}
As you can see this component is partial and I use setter to listen of cabinetId changes.
Here is html part:
...
<canvas baseChart width="400" height="150"
[options]="options"
[datasets]="lineChartData"
[labels]="lineChartLabels"
[legend]="lineChartLegend"
[chartType]="lineChartType"
[colors]="lineChartColors"></canvas>
...
And I use this component as:
<app-juridical-bid-primary [cabinetId]="cabinetId"></app-juridical-bid-primary>
I find similar question similar question, but, unfortunately, don't understand answer
After some hours of code testing I find answer. It is needed to correct code from question:
...
import * as _ from 'lodash'; //-- useful library
export class JuridicalBidPrimaryComponent extends BidComponent {
lineChartData: ChartDataSets[] = [];
lineChartLabels: Label[] = [];
...
initData(): void {
/*this.lineChartData = [];
this.lineChartLabels = [];*/ //-- this lines is needed to remove
if (this.cabinetId)
this.getData(this.year);
}
getData(year: number) {
...
forkJoin([limitPromise, analyticsPromise]).subscribe(data => {
this.limits = data[0];
this.lineChartLabels.length = 0;
this.lineChartLabels.push(...data[1].map(e => e.Period));
if (_.isEmpty(this.lineChartData)) {
//-- If data array is empty, then we add data series
this.lineChartData.push(
{
data: data[1].map(e => e.Limit),
label: 'Замовлені величини'
},
{
data: data[1].map(e => e.Used),
label: 'Використано'
}
);
} else {
//-- If we have already added data series then we only change data in data series
this.lineChartData[0].data = data[1].map(e => e.Limit);
this.lineChartData[1].data = data[1].map(e => e.Used);
}
this.isLoading = false;
}, error => {
this.isLoading = false;
});
}
}
As I understand ng2-charts, if we clean dataset (lineChartData) and add new data then the library understand this as create new series and don't use primary settings for the ones. So we have to use previous created series.
I hope it will be useful for anyone who will have such problem as I have.

ReactNative IOS Testflight Firebase MapStateToProps Update Issue

I am having an extremely weird issue where I don't even know where to begin to debug since it only happens when I get the app into test flight. Basically I am trying to load channels based on geolocation. Some automatically load and then some are loaded if less than 100 miles from a mountain (lets call these PUBLIC and PRIVATE channels- both of which are in the same list). I have these 2 firebase calls in my action creator and push them into an array and then call dispatch after. I have an issue with the FlatList where it loads the PUBLIC channels and only when I scroll do the PRIVATE channels. There are a bunch of things I have tried including the most common from that specific github issue (flatlist updating) 'removeClippedSubviews={false}', extra data, pure components, etc, but none have worked.
Instead I found a way to get around this (I know it isn't the best, but I just want a solution that works for now) by just setting a timeout and waiting for the channels before dispatching the action:
setTimeout(function(){ dispatch(loadPublicChannelsSuccess(data)); }, 10);
Unfortunately, now is when the crazy issue comes in. Basically, this works for debug, release/ everything I have tried with XCode, but when it gets to my device the render method basically sits at a loading indicator until I switch tabs with react navigation. This makes no sense to me since it doesn't happen always (maybe 80%) of the time, and only in test flight so far. I had never seen this before setting the timeout either so not really sure where to begin:
render() {
const {loadPublicChannels, loading, publicChannels, checkedInMountain, selectedMountain} = this.props;
return !loadPublicChannels && publicChannels && !loading
? (
<MessagePanelPublic publicChannels={publicChannels} selectedMountain={selectedMountain}/>
) : (
<LoadingAnimation />
);
}
actions
export const getUserPublicChannels = () => {
return (dispatch, state) => {
dispatch(loadPublicChannels());
// get all mountains within distance specified
let mountainsInRange = state().session.mountainsInRange;
// get the user selected mountain
let selectedMountain = state().session.selectedMountain;
// see if the selected mountain is in range to add on additional channels
let currentMountain;
mountainsInRange
? (currentMountain =
mountainsInRange.filter(mountain => mountain.id === selectedMountain)
.length === 1
? true
: false)
: (currentMountain = false);
// mountain public channels (don't need to be within distance)
let currentMountainPublicChannelsRef = FIREBASE_REF_CHANNEL_INFO.child(
"Public"
)
.child(`${selectedMountain}`)
.child("Public");
// mountain private channels- only can see if within range (geolocation)
let currentMountainPrivateChannelsRef = FIREBASE_REF_CHANNEL_INFO.child(
"Public"
)
.child(`${selectedMountain}`)
.child("Private");
// get public channels
return currentMountainPublicChannelsRef
.orderByChild("key")
.once("value")
.then(snapshot => {
let publicChannelsToDownload = [];
snapshot.forEach(channelSnapshot => {
let channelId = channelSnapshot.key;
let channelInfo = channelSnapshot.val();
// add the channel ID to the download list
publicChannelsToDownload.push({ id: channelId, info: channelInfo });
});
// if mountain exists then get private channels/ if in range
if (currentMountain) {
currentMountainPrivateChannelsRef
.orderByChild("key")
.once("value")
.then(snapshot => {
snapshot.forEach(channelSnapshot => {
let channelId = channelSnapshot.key;
let channelInfo = channelSnapshot.val();
publicChannelsToDownload.push({
id: channelId,
info: channelInfo
});
});
});
}
return publicChannelsToDownload;
})
.then(data => {
setTimeout(function(){ dispatch(loadPublicChannelsSuccess(data)); }, 10);
});
};
};
reducer related to public channels
case types.LOAD_PUBLIC_CHANNELS:
return {
...state,
loadPublicChannels: true
};
case types.LOAD_PUBLIC_CHANNELS_SUCCESS:
console.log("PUBLIC");
console.log(action.publicChannels);
console.log(action);
return {
...state,
publicChannels: action.publicChannels,
loadPublicChannels: false,
messages: {}
};
case types.LOAD_PUBLIC_CHANNELS_ERROR:
return {
...state,
channelsPublicError: action.error,
loadPublicChannels: false
};
container which calls mapStateToProps and mapDispatchToProps
class MessagePanelPublicContainer extends Component {
constructor(props) {
super(props);
}
// get public and private channels from redux
async componentDidMount() {
this.props.getUserPrivateChannels();
this.props.loadCurrentUser();
// this.props.getUserPublicChannels();
}
componentDidUpdate(prevProps) {
if (this.props.mountainsInRange && prevProps.mountainsInRange !== this.props.mountainsInRange || prevProps.selectedMountain !== this.props.selectedMountain) {
this.props.getUserPublicChannels();
}
}
lessThan12HourAgo = (date) => {
return moment(date).isAfter(moment().subtract(12, 'hours'));
}
render() {
const {loadPublicChannels, loading, publicChannels, checkedInMountain, selectedMountain} = this.props;
return !loadPublicChannels && publicChannels && !loading
? (
<MessagePanelPublic publicChannels={publicChannels} selectedMountain={selectedMountain}/>
) : (
<LoadingAnimation />
);
}
}
const mapStateToProps = state => {
return {
publicChannels: state.chat.publicChannels,
loadPublicChannels: state.chat.loadPublicChannels,
currentUser: state.chat.currentUser,
loading: state.chat.loadCurrentUser,
mountainsInRange: state.session.mountainsInRange,
selectedMountain: state.session.selectedMountain,
};
}
const mapDispatchToProps = {
loadCurrentUser,
getUserPublicChannels,
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(MessagePanelPublicContainer);
component
import React, { Component } from "react";
import { View, Text, FlatList, ImageComponent } from "react-native";
import { connect } from "react-redux";
import PropTypes from "prop-types";
import { ListItem, Icon, Button } from "react-native-elements";
import { withNavigation } from "react-navigation";
import LinearGradient from "react-native-linear-gradient";
import styles from "./Styles";
import moment from 'moment';
import FastImage from 'react-native-fast-image';
class MessagePanelPublicComponent extends Component {
render() {
// rendering all public channels
const renderPublicChannels = ({ item }) => {
return (
<ListItem
leftAvatar={{
source: { uri: item.info.ChannelPicture },
rounded: false,
overlayContainerStyle: { backgroundColor: "white" },
ImageComponent: FastImage
}}
title={item.info.Name}
titleStyle={styles.title}
chevron={true}
bottomDivider={true}
id={item.Name}
containerStyle={styles.listItemStyle}
/>
);
};
const renderText = () => {
return (
<View style={styles.extraTextContainer}>
<Text style={styles.extraText}>
Get within 100 miles from resort or select a closer resort to see more channels...
</Text>
<Icon
name="map-marker"
type="font-awesome"
size={40}
iconStyle={styles.extraIcon}
/>
</View>
);
};
return (
<View style={styles.container}>
<View style={styles.channelList}>
<FlatList
data={this.props.publicChannels}
renderItem={renderPublicChannels}
// keyExtractor={item => item.Name}
keyExtractor={(item, index) => index.toString()}
extraData={this.props.publicChannels}
removeClippedSubviews={false}
/>
{this.props.publicChannels.length < 3 ? renderText() : null}
</View>
</View>
);
}
}

NativeScript with Angular2 data binding not working with geolocation

I am developing a simple POC to display the current position.
The program receives the information from the geolocation.watchLocation however the location is not bind and displayed on the screen until I press the button STOP Monitoring. To be noted that the log is correctly showing the coordinates
JS: Start Monitoring ...
JS: Location: 49.6411839:6.0040451
JS: Stop Monitoring ...
import {Component} from "#angular/core";
import {Router} from "#angular/router";
import geolocation = require("nativescript-geolocation");
#Component({
selector: "TrackingPageSelector",
template:`
<StackLayout>
<Button [text]="watchId==0 ? 'Start Monitoring' : 'Stop Monitoring'" (tap)="buttonStartStopTap()"></Button>
<Label class="labelValue" [text]="latitude"> </Label>
<Label class="labelValue" [text]="longitude"> </Label>
</StackLayout>`
})
export class TrackingPageComponent {
latitude: number = 0;
longitude: number = 0;
watchId: number = 0;
constructor(private router: Router) {}
ngOnInit() {
if (!geolocation.isEnabled()) geolocation.enableLocationRequest();
}
public buttonStartStopTap() {
if (this.watchId != 0) {
console.log('Stop Monitoring ...');
geolocation.clearWatch(this.watchId);
this.watchId = 0;
} else {
console.log('Start Monitoring ...');
let _self = this;
this.watchId = geolocation.watchLocation(
function (loc) {
if (loc) {
_self.latitude = loc.latitude;
_self.longitude = loc.longitude;
console.log(`Location: ${_self.latitude}:${_self.longitude}`);
}
},
function(e){
this.errorMsg = e.message;
},
{desiredAccuracy: 3, updateDistance: 10, minimumUpdateTime: 1000 * 3}); // Should update every X seconds
}
}
}
If you use ()=> instead of function () then you don't need the _self hack.
It looks like you need to invoke change detection manually. NgZone seems not to cover geolocation.watchLocation()
constructor(private cdRef:ChangeDetectorRef) {}
...
this.watchId = geolocation.watchLocation(
(loc) => {
if (loc) {
this.latitude = loc.latitude;
this.longitude = loc.longitude;
this.cdRef.detectChanges();
console.log(`Location: ${this.latitude}:${this.longitude}`);
}
},
(e) => {
this.errorMsg = e.message;
},
or alternatively zone.run(() => ...) like
constructor(private zone:NgZone) {}
...
this.watchId = geolocation.watchLocation(
(loc) => {
if (loc) {
this.zone.run(() => {
this.latitude = loc.latitude;
this.longitude = loc.longitude;
console.log(`Location: ${this.latitude}:${this.longitude}`);
});
}
},
(e) => {
this.errorMsg = e.message;
},
If you only change local fields cdRef.detectChanges() is the better option, if you call methods that might modify some state outside of the current component, zone.run(...) is the better option.

Resources