Nativescript navigation, ngrx, and maps(google, MapBox) having subscriber/navigation issues - google-maps

EDIT: It appears my change detection and lifecycle breaks entirely after navigation to a different component. Why?
I'm having some strange problems with#ngrx/store on nativescript and nativescript map based plugins for google maps and mapbox. Code is correct, maps load perfect with data(markers set) but having issues with subscribing or navigating.
I'ved tried both and they work perfect until I attempt navigation;
With google maps:
Navigation and subscribing to data in other pages works perfect until I try to navigate back/forward again to the maps. I always get an error on google map's onReady method.
With MapBox:
navigation works fine including going back to maps. However, my async pipe fails to actually populate other page data until I navigate back to the original map component!!! I assume the subscriber don't get triggered upon navigating. I managed to get it almost working if I don't unsubscribe with ngOnDestroy() but this sends old or wrong data obviously.
Here is code
Map page(first component):
ngOnDestroy() {
this.subscription.unsubscribe();
}
this is the mapbox code but it's similar for googlemaps, executes with map is loaded and add markers(no problems here on both googlemaps or mapbox).
onMapReady(args) {
let mapMarkers = [];
this.subscription = this.store
.select('mainData')
.subscribe((data: any) => {
if (data !== null) {
this.markers = data.markers.map((mark) => {
return {
lat: mark.venue.lat,
lng: mark.venue.lon,
iconPath: this.iconMaker(mark.group, mark.sport),
userData: mark,
onTap: (marker) => {
let urlExt = "/event/" + mark.id; this.routerExtensions.navigate([urlExt]);
},
}
});
args.map.addMarkers(this.markers);
}
});
When I tap on a map marker, it navigates to this second page(event/:id) that displays the event data related to the map marker.
Event Component
Html:
<StackLayout *ngFor=" let model of models |async" orientation="vertical">
<StackLayout orientation="horizontal" style="padding: 3">
<Label class="h2" textWrap="true" text="Venue: "></Label>
<Label class="h2" textWrap="true" [text]="model.venue.name"></Label>
</StackLayout>
...
Component:
ngOnInit() {
this.route.params
.forEach((params) => {
this.id = +params['id'];
console.dir("Found match" + this.id);
if (params['id']) {
used async pipe to send data to html. In google maps this works perfect, in mapbox it doesn't trigger until I attempt to navigate away. I also attempted to just subscribe to the returned Observable but still same outcome in MapBox;Html not waiting for async loads fine.
this.models = this.mapService.getEvent(this.id);
});
}
});
}
This all works 100% perfect in google maps except I can't navigate back to my map component without it instantly crashing.
I would love for either to work.
I do get lots of errors
for unlinking rxjs module leading me to believe that might be an issue:
02-07 14:29:59.523 24939 24939 W System.err: remove failed: EACCES (Permission denied) : /data/local/tmp/org.nativescript.pickn/sync/tns_modules/rxjs/src/MiscJSDoc.ts
02-07 14:29:59.543 5475 5475 E audit : type=1400 msg=audit(1486499399.523:327875): avc: denied { unlink } for pid=24939 comm="ivescript.pickn" name="MiscJSDoc.ts" dev="sda17" ino=463318 scontext=u:r:untrusted_app:s0:c512,c768 tcontext=u:object_r:shell_data_file:s0 tclass=file permissive=0
02-07 14:29:59.573 24939 24939 W System.err: remove failed: EACCES (Permission denied) : /data/local/tmp/org.nativescript.pickn/sync/tns_modules/rxjs/src/observable/dom/MiscJSDoc.ts
02-07 14:29:59.593 5475 5475 E audit : type=1400 msg=audit(1486499399.573:328068): avc: denied { unlink } for pid=24939 comm="ivescript.pickn" name="MiscJSDoc.ts" dev="sda17" ino=463540 scontext=u:r:untrusted_app:s0:c512,c768 tcontext=u:object_r:shell_data_file:s0 tclass=file permissive=0

AFAIK, with ngrx, the subscription to your store should probably only happen once in your component, instead of every time the onMapReady(args) method is fired, which maybe happening when you navigate back to the map - verify this first by adding a console.log to the onMapReady(args) method.
From what I've seen (now) ngrx subscriptions belong in the constructor method and console.log has helped me understand the life-cycle of numerous components now... :-)
One suggestion might be to:
add a component flag called isMapboxReady
toggle it in the onMapReady method
move the subscription out into the constructor and add a check inside the subscription for if the isMapboxReadyflag is true before adding markers.

Related

How controllers and functions communicate between two separate files using .emit and how to tell controller hierarchy in AngularJS?

This is my first post on stack overflow so I am really green and really new with AngularJS and ASP.Net and having a lot of problems with $rootscope.$emit. From what I have read online $rootscopes are parent scopes so all values exposed there are visible to all controllers and templates and scopes are functions inside of controllers. It seems like you can "emit" up through the controller hierarchy a call to another controller by using $rootscope.$emit("Name of $rootscope.$on function name") the $rootscope.$on listens for that call and then does whatever is in its function. The thing I am having trouble with is when I do my
$rootscope.$emit("LoadPrintDetailsModal", {});
it never seems to reach
$rootscope.$on("LoadPrintDetailsModal", function(event,args) {}.
So the question is am I misunderstanding how $emit or how controller hierarchy works or is there a problem in my code?
I have already tried using emit and I hit the debugger in indexController.js file after a call from a button in my Index.cshtml file but then when I make the
$rootScope.$emit("LoadPrintDetailsModal", {});
it does not get picked up by my printableController.js file where
$rootScope.$emit("LoadPrintDetailsModal", function (event, args) {});
// (Index.cshtml) Button in Index.cshtml file that calls "LoadPrintModal" //function in indexController
<button type="button" data-toggle="modal" data-target="#dvPrintModal"
ng-click="LoadPrintModal()">
Print
</button>
// (indexController.js)scope.LoadPrintModal in indexController.js that tries
// to emit "LoadPrintDetails" to $rootscope.%on("LoadPrintDetailsModal",
// function (event, args) in printableçontroller.js
$scope.LoadPrintModal = function () {
debugger;
$rootScope.$emit("LoadPrintDetailsModal", {});
};
// (printableController.js) file where rootScope.on is located and is supposed to pick up the emit
app.controller('PrintableController', function ($scope, $rootScope) {
$rootScope.$on("LoadPrintDetailsModal", function (event, args) {
debugger;
$scope.printModal();
});
$scope.printModal = function () {
console.log("Hello World");
};
)};
The expected result should be a console log of hello world and we should hit the debugger in printableController.js file
Use $rootScope.$broadcast:
$rootScope.$broadcast("LoadPrintDetailsModal", {});
The $broadcast method dispatches events down the scope heirarchy.
The $emit method dispatches events up the heirarchy.
For more information, see
AngularJS Developer Guide - Scope Event Propagation
To capture broadcast events, use $scope.$on:
app.controller('PrintableController', function ($scope, $rootScope) {
̶$̶r̶o̶o̶t̶S̶c̶o̶p̶e̶.̶$̶o̶n̶(̶"̶L̶o̶a̶d̶P̶r̶i̶n̶t̶D̶e̶t̶a̶i̶l̶s̶M̶o̶d̶a̶l̶"̶,̶ ̶f̶u̶n̶c̶t̶i̶o̶n̶ ̶(̶e̶v̶e̶n̶t̶,̶ ̶a̶r̶g̶s̶)̶ ̶{̶
$scope.$on("LoadPrintDetailsModal", function (event, args) {
debugger;
$scope.printModal();
});
$scope.printModal = function () {
console.log("Hello World");
};
)};
From the Docs:
Only use .$broadcast(), .$emit() and .$on() for atomic events
Events that are relevant globally across the entire app (such as a user authenticating or the app closing). If you want events specific to modules, services or widgets you should consider Services, Directive Controllers, or 3rd Party Libs
Injecting services and calling methods directly is also useful for direct communication
Directives are able to directly communicate
For more information, seed
AngularJS Wiki - Best Practices
It's hard to know what is happening without:
seeing the hierarchy of the components. Is the emitting component is down in the hierarchy from the catching component. $emit is sending messages up. $browdcast is sending messages down.
Seeing how you inject $rootscope.
Regarding 2. $rootscope injection gives you the main $scope of the application. Doing $emit from it won't gives us anything as there are no $scopes that are above the $rootScope.
My guess you want to inject $scope which represent the specific scope in the hierarchy for that controller/component. Then $emiting will propagate upward to the catching controller/component properly.

Loading Aurelia breaks Google API

I have created a reproduction of this bug here (ugly use of Aurelia but to prove the point): https://jberggren.github.io/GoogleAureliaBugReproduce/
If I load Google API and try to list my files in Google Drive my code derived from Googles quickstart works fine. If I use the same code after loading Aurelia I get a script error from gapi stating
Uncaught Error: arrayForEach was called with a non array value
at Object._.Sa (cb=gapi.loaded_0:382)
at Object._.eb (cb=gapi.loaded_0:402)
at MF (cb=gapi.loaded_0:723)
at Object.HF (cb=gapi.loaded_0:722)
at Object.list (cb=gapi.loaded_0:40)
at listFiles (index.js:86)
...
When debugging it seems to be some sort of array check (Chroms says 'native code') that failes after Aurelia is loaded. In my search for an answer I found two other people with the same problem but no solution (Aurelia gitter question, SO Question). Don't know if to report this to the Aurelia team, Google or where the actual problem lays.
Help me SO, you are my only hope.
This is not a perfect solution but works.
aurelia-binding
https://github.com/aurelia/binding/blob/master/src/array-observation.js
Aurelia overrides Array.prototype.* for some reasons.
gapi (especially spreadsheets)
Gapi lib checks to make sure that is it native code or not.
// example
const r = /\[native code\]/
r.test(Array.prototype.push)
conclusion
So, we have to monkey patching.
gapi.load('client:auth2', async () => {
await gapi.client.init({
clientId: CLIENT_ID,
discoveryDocs: ['https://sheets.googleapis.com/$discovery/rest?version=v4'],
scope: 'https://www.googleapis.com/auth/spreadsheets',
});
// monkey patch
const originTest = RegExp.prototype.test;
RegExp.prototype.test = function test(v) {
if (typeof v === 'function' && v.toString().includes('__array_observer__.addChangeRecord')) {
return true;
}
return originTest.apply(this, arguments);
};
});

Google chrome web push api bug

What is this bug? When sending web pushing browser Google Chrome "sometimes" gives a second message with the text: "This site has been updated in the background."
I want to make it only one message
This text I found in source Chrome
This site has been updated in the background.
github.com/scheib/chromium/blob/master/chrome/app/resources/generated_resources_en-GB.хтб
How to get rid of this message.
The way it works is a feature not a bug.
Here is an issue that explains your situation in Chrome: https://code.google.com/p/chromium/issues/detail?id=437277
And more specific code comment in Chromium code:
https://code.google.com/p/chromium/codesearch#chromium/src/chrome/browser/push_messaging/push_messaging_notification_manager.cc&rcl=1449664275&l=287
What might have happened is some of the push messages sent to the client did not result in showing a notification.
Hope that helps
The reason this often occurs is the promise returned to event.waitUntil() didn't resolve with a notification being shown.
An example that might show the default push notification:
function handlePush() {
// BAD: The fetch's promise isn't returned
fetch('/some/api')
.then(function(response) {
return response.json();
})
.then(function(data) {
// BAD: the showNotification promise isn't returned
showNotification(data.title, {body: data.body});
});
}
self.addEventListener(function(event) {
event.waitUntil(handlePush());
});
Instead you could should write this as:
function handlePush() {
// GOOD
return fetch('/some/api')
.then(function(response) {
return response.json();
})
.then(function(data) {
// GOOD
return showNotification(data.title, {body: data.body});
});
}
self.addEventListener(function(event) {
const myNotificationPromise = handlePush();
event.waitUntil(myNotificationPromise);
});
The reason this is all important is that browsers wait for the promise passed into event.waitUntil to resolve / finish so they know the service worker needs to be kept alive and running.
When the promise resolves for a push event, chrome will check that a notification has been shown and it falls into a race condition / specific circumstance as to whether Chrome shows this notification or not. Best bet is to ensure you have a correct promise chain.
I put some extra notes on promises on this post (See: 'Side Quest: Promises' https://gauntface.com/blog/2016/05/01/push-debugging-analytics)

Polymer + page.js flash notifications with paper-toast

I'm building a mid sized app with Polymer and used the Polymer Starter Kit to kick things off which uses page.js for routing.
I want to implement flash message functionality using the paper-toast element.
In other technologies/frameworks this is implemented by checking to see if a property exists when the route is changed.. if it does, it shoes the relevant flash/toast message.
How... with Polymer & Page.js is it possible to replicate this type of functionality? Page.js doesn't seem to have any events for changed routes.
The only way I can think is to create a proxy function for the page('/route') function that I have to call every time I want to go to a new page which then calls the actual page function. Is there a better way?
I've implemented this like follows for the time being... seems to be ok but if anyone can suggest improvements let me know.
In routing.html
window.addEventListener('WebComponentsReady', function() {
// Assign page to another global object
LC.page = page;
// Define all routes through this new object
LC.page('/login', function () {
app.route = 'login';
app.scrollPageToTop();
});
....
//implement remaining routes
// page proxy... to intercept calls
page = function(path) {
// dispatch event
document.dispatchEvent(new Event('LC.pageChange', {path: path}));
// call the real page
LC.page(path);
};
});
Then where you want to listen.. in my case in a lc-paper-toast element added to the index.html file of the app I can now listen to when the page is changed...
ready: function() {
document.addEventListener('LC.pageChange', function(e){
console.log('page change' , e);
}, false);
}
Only thing to be aware of is that all page changes must be called with page('/route') otherwise it won't go through the proxy.

Chrome Push Notification: This site has been updated in the background

While implementing the chrome push notification, we were fetching the latest change from our server. While doing so, the service-worker is showing an extra notification with the message
This site has been updated in the background
Already tried with the suggestion posted here
https://disqus.com/home/discussion/html5rocks/push_notifications_on_the_open_web/
But could not find anything useful till now. Is there any suggestion ?
Short Answer: You should use event.waitUntil and pass a promise to it, which returns showNotification eventually. (if you have any other nested promises, you should also return them.)
I was expriencing the same issue but after a long research I got to know that this is because delay happen between PUSH event and self.registration.showNotification(). I only missed return keyword before self.registration.showNotification()`
So you need to implement following code structure to get notification:
var APILINK = "https://xxxx.com";
self.addEventListener('push', function(event) {
event.waitUntil(
fetch(APILINK).then(function(response) {
return response.json().then(function(data) {
console.log(data);
var title = data.title;
var body = data.message;
var icon = data.image;
var tag = 'temp-tag';
var urlOpen = data.URL;
return self.registration.showNotification(title, {
body: body,
icon: icon,
tag: tag
})
});
})
);
});
Minimal senario:
self.addEventListener('push', event => {
const data = event.data.json();
event.waitUntil(
// in here we pass showNotification, but if you pass a promise, like fetch,
// then you should return showNotification inside of it. like above example.
self.registration.showNotification(data.title, {
body: data.content
})
);
});
I've run into this issue in the past. In my experience the cause is generally one of three issues:
You're not showing a notification in response to the push
message. Every time you receive a push message on the device, when
you finish handling the event a notification must be left visible on
the device. This is due to subscribing with the userVisibleOnly:
true option (although note this is not optional, and not setting it
will cause the subscription to fail.
You're not calling event.waitUntil() in response to handling the event. A promise should be passed into this function to indicate to the browser that it should wait for the promise to resolve before checking whether a notification is left showing.
For some reason you're resolving the promise passed to event.waitUntil before a notification has been shown. Note that self.registration.showNotification is a promise and async so you should be sure it has resolved before the promise passed to event.waitUntil resolves.
Generally as soon as you receive a push message from GCM (Google Cloud Messaging) you have to show a push notification in the browser. This is mentioned on the 3rd point in here:
https://developers.google.com/web/updates/2015/03/push-notificatons-on-the-open-web#what-are-the-limitations-of-push-messaging-in-chrome-42
So it might happen that somehow you are skipping the push notification though you got a push message from GCM and you are getting a push notification with some default message like "This site has been updated in the background".
This works, just copy/paste/modify. Replace the "return self.registration.showNotification()" with the below code. The first part is to handle the notification, the second part is to handle the notification's click. But don't thank me, unless you're thanking my hours of googling for answers.
Seriously though, all thanks go to Matt Gaunt over at developers.google.com
self.addEventListener('push', function(event) {
console.log('Received a push message', event);
var title = 'Yay a message.';
var body = 'We have received a push message.';
var icon = 'YOUR_ICON';
var tag = 'simple-push-demo-notification-tag';
var data = {
doge: {
wow: 'such amaze notification data'
}
};
event.waitUntil(
self.registration.showNotification(title, {
body: body,
icon: icon,
tag: tag,
data: data
})
);
});
self.addEventListener('notificationclick', function(event) {
var doge = event.notification.data.doge;
console.log(doge.wow);
});
I was trying to understand why Chrome has this requirement that the service worker must display a notification when a push notification is received. I believe the reason is that push notification service workers continue to run in the background even after a user closes the tabs for the website. So in order to prevent websites from secretly running code in the background, Chrome requires that they display some message.
What are the limitations of push messaging in Chrome?
...
You have to show a notification when you receive a push message.
...
and
Why not use Web Sockets or Server-Sent Events (EventSource)?
The advantage of using push messages is that even if your page is closed, your service worker will be woken up and be able to show a notification. Web Sockets and EventSource have their connection closed when the page or browser is closed.
If you need more things to happen at the time of receiving the push notification event, the showNotification() is returning a Promise. So you can use the classic chaining.
const itsGonnaBeLegendary = new Promise((resolve, reject) => {
self.registration.showNotification(title, options)
.then(() => {
console.log("other stuff to do");
resolve();
});
});
event.waitUntil(itsGonnaBeLegendary);
i was pushing notification twice, once in the FCM's onBackgroundMessage()
click_action: "http://localhost:3000/"
and once in self.addEventListener('notificationclick',...
event.waitUntil(clients.matchAll({
type: "window"
}).then...
so i commented click_action, ctrl+f5 to refresh browsers and now it works normal