How do I make an Angular2 application update instantly on all connected devices as the events are fired? - http

I need to build a restaurant management mobile application with Angular2 and Ionic2 and a website for the same restaurant with Angular2 that constantly make http connections to store and retrieve data from the database in order to maintain the latest data.
For example, If an order request was fired from a waiter's mobile phone, the new order is posted to the database, which I can accomplish. the chef needs to get the instant notification that a new order has been created. Also, the data needs to be reflected in other employees' phones and also on the website.
All I can come up with is the use of setInterval, but since I've never done anything like this before, I'm not sure if this is the correct way.
component
orders: Order[];
constructor(private orderService: OrderService) {
setInterval(function() {
this.orderService
.getOrders()
.subscribe(
(orders: Order[]) => this.orders = orders,
(error: Response) => console.log(error)
)}, 3000);
}
placeanOrder(order) {
this.orderService.postOrder(order);
}
service
getOrders() Observable<any>{
this.http.get(...).map(...);
}
placeOrder(order) {
this.http.post(...).map(...);
}
When I try something like this, I get the same error logged to the console every second.
Cannot read property 'getOrders' of undefined
Why am I getting the error?
How would I convert the Observable json data retrieved from server into my interface data type, in this case, of type Order?
What is a better approach to this?

Cause wrong usage of callbacks! You have to use the arrow-syntax!
wrong: setInterval(function() {
right: setInterval(() => {
Just describe your function with correct types:
getOrders(): Observable<Order[]> {
Nothing else needed. Property-names needs to be the same (interface/json)!
It's ok to use a timer. "Better" could be a never-closed-connection to the server, so server could SEND you that data and client don't have to POLL.
https://www.websocket.org/
http://socket.io/

Related

RTK Query - Delete cached data upon cacheEntryAdded

Currently we have an api endpoint that requests a single 'Group' via ID.
We have a WebSocket subscription set up, and in the onCacheEntryAdded definition, we handle cases where that Group is updated, or deleted.
When we receive an update message from the websocket, we trigger the following;
updateCachedData((draft) => {
draft = response;
}
Which updates the entry, as expected.
However, what is the approach we should use if we want to remove the entry entirely? Upon 'delete' messages from the websocket, I would assume I could simply set draft as undefined, but that doesn't seem to be the case.
updateCachedData((draft) => {
draft = response;
}
actually does not update anything here in the first place.
updateQueryResults has the same rules as produce from immer or normal createSlice case reducers: you can modify the object in state (or draft in this case), but you cannot reassign the variable itself. If you want to do that, you have to
updateCachedData((draft) => {
return response;
}
instead.
In the same fashion, you can
updateCachedData((draft) => {
return null;
}
too, but that will not remove the full cache entry, it will only set the data to null. (undefined won't work!)
The cache entry will only be removed once there is no more component using it (by not using it with useQuery) - and then it will be removed automatically after 60 seconds.

Meteor publish overwrites another publish

I have two publish method as below but when I subscribe to one of the publish method in client search page, it is being overwritten with the other one which is meant for index page.
Server
Meteor.publish("task.index", function() {
TaskCollection.find()
}
Meteor.publish("task.index.search", function(state) {
TaskCollection.find({ state: state })
}
Client - search page
Meteor.subscribe("task.index.search", state)
// this will always be overwritten with "task.index" published collection
Client - index page
Meteor.subscribe("task.index")
Does anyone know how to avoid this?
Welcome to SO!
There is a high chance the "override" you see is just the normal Meteor behaviour for Publish/Subscribe mechanism.
Your "task.index" publication sends all your TaskCollection documents to the Client.
Therefore any other publication on that same TaskCollection will send documents that the Client already knows.
Then in your Client, filtering some documents from TaskCollection is independent from your subscription and publication. Just perform your TaskCollection.find({ state: state }) Client side, and you will get the documents you need.
When you publish only a subset of documents of a Collection, it happens that what you publish is exactly already the filtered documents that you want to display on your Client, therefore on your Client you just display all Collection documents you know of. But you have to understand that these are 2 different steps:
Subscription to send some documents to the Client. Several Subscriptions may be set, filling the same Collection on the Client.
Filtering on the Client, based on the documents sent by the (possibly several) Subscription(s).
See also: Publish subscribe doesn't seem to work
If your client index and search pages are different templates, you can subscribe to the documents at respective template level.
Client - search page:
Template.search.created = function () {
const template = this;
template.subscribe('task.index.search', state);
}
Template.search.rendered = function () {
console.log("Client search : " + TaskCollection.find().fetch().length);
}
Client - index page:
Template.index.created = function () {
const template = this;
template.subscribe('task.index');
}
Template.index.rendered = function () {
console.log(""Index : " + TaskCollection.find().fetch().length);
}
But, it is always advisable to filter the documents on the client as well.

Meteor GroundDB granularity for offline/online syncing

Let's say that two users do changes to the same document while offline, but in different sections of the document. If user 2 goes back online after user 1, will the changes made by user 1 be lost?
In my database, each row contains a JS object, and one property of this object is an array. This array is bound to a series of check-boxes on the interface. What I would like is that if two users do changes to those check-boxes, the latest change is kept for each check-box individually, based on the time the when the change was made, not the time when the syncing occurred. Is GroundDB the appropriate tool to achieve this? Is there any mean to add an event handler in which I can add some logic that would be triggered when syncing occurs, and that would take care of the merging ?
The short answer is "yes" none of the ground db versions have conflict resolution since the logic is custom depending on the behaviour of conflict resolution eg. if you want to automate or involve the user.
The old Ground DB simply relied on Meteor's conflict resolution (latest data to the server wins) I'm guessing you can see some issues with that depending on the order of when which client comes online.
Ground db II doesn't have method resume it's more or less just a way to cache data offline. It's observing on an observable source.
I guess you could create a middleware observer for GDB II - one that checks the local data before doing the update and update the client or/and call the server to update the server data. This way you would have a way to handle conflicts.
I think to remember writing some code that supported "deletedAt"/"updatedAt" for some types of conflict handling, but again a conflict handler should be custom for the most part. (opening the door for reusable conflict handlers might be useful)
Especially knowing when data is removed can be tricky if you don't "soft" delete via something like using a "deletedAt" entity.
The "rc" branch is currently grounddb-caching-2016 version "2.0.0-rc.4",
I was thinking about something like:
(mind it's not tested, written directly in SO)
// Create the grounded collection
foo = new Ground.Collection('test');
// Make it observe a source (it's aware of createdAt/updatedAt and
// removedAt entities)
foo.observeSource(bar.find());
bar.find() returns a cursor with a function observe our middleware should do the same. Let's create a createMiddleWare helper for it:
function createMiddleWare(source, middleware) {
const cursor = (typeof (source||{}).observe === 'function') ? source : source.find();
return {
observe: function(observerHandle) {
const sourceObserverHandle = cursor.observe({
added: doc => {
middleware.added.call(observerHandle, doc);
},
updated: (doc, oldDoc) => {
middleware.updated.call(observerHandle, doc, oldDoc);
},
removed: doc => {
middleware.removed.call(observerHandle, doc);
},
});
// Return stop handle
return sourceObserverHandle;
}
};
}
Usage:
foo = new Ground.Collection('test');
foo.observeSource(createMiddleware(bar.find(), {
added: function(doc) {
// just pass it through
this.added(doc);
},
updated: function(doc, oldDoc) {
const fooDoc = foo.findOne(doc._id);
// Example of a simple conflict handler:
if (fooDoc && doc.updatedAt < fooDoc.updatedAt) {
// Seems like the foo doc is newer? lets update the server...
// (we'll just use the regular bar, since thats the meteor
// collection and foo is the grounded data
bar.update(doc._id, fooDoc);
} else {
// pass through
this.updated(doc, oldDoc);
}
},
removed: function(doc) {
// again just pass through for now
this.removed(doc);
}
}));

Meteor/MongoDB pull data reactively

I have a method that checks for all unread messages belonging to a user. When the app loads, this number appears next to the "Messages" drop down. In Meteor, how would I update this count or variable for when a new message comes in or when the user reads an unread message? Pretty much I needs the method to send down the new count anytime a message status changes without refreshing the app itself.
I'm familiar with the Tracker.autorun functionality but I don't think it'll help with this situation. What's the best practice for approaching this?
Use Publish/Subscribe. It is always reactive. If you do not want to have all unread messages sent to the client straight away and counted there, you create a custom collection that justs count the number of unread messages and publishes that count. Look at the example a bit down in the linked page that starts with
// server: publish the current size of a collection
This is exactly your use case.
I have exactly this setup for new messages. In my header I have:
<li>Messages <span class="counter">{{Messages.count}}</span></li>
And then I have a helper that returns the cursor:
Template.header.helpers({
Messages: function(){ return Messages.find(); }
});
In the old days, before David Weldon set me straight I used to have a helper to return the count, now I just refer to the count directly in the blaze html template.
Now, in this approach I'm subscribing to the Messages collection so that new messages are transmitted to the client and can then be counted locally. This is on the assumption that they are going to be read soon. If you want to avoid this step then you should probably publish a Stats collection or include a stats key in the user object so that just the count itself can be synced via pub-sub.
You can just have a field like read, and update like:
Method for marking one message as read:
markRead: function(messageId){
Messages.update(messageId, {
$set: {
read: true //this needs to be set to false when its inserted
}
})
}
Bulk update method (assuming all messages have receiverId saved):
markAllRead: function(){
Messages.update({receiver: Meteor.userId(), read:false}, {
$set: {
read: true
}
}, {multi: true})
}
You can count read:false ones to retrieve count and you don't have to write anything else
Helper:
count: function(){
//even if your publish/subscribe is correct, the count we want is from messages that are not read and the receiver is current user.
return Messages.find({receiver: Meteor.userId(), read: false }).count();
}
Event:
'click .elementClass': function(){
//both users see the messages and they can both click. We want to update the right message for the right user. Otherwise, the other user can mark the message as read when the receiver is the other user which they shouldn't be able to do. You can do a simple check on the client side, and another check in the method if necessary.
if(this.receiver === Meteor.userId()){
Meteor.call('markAsRead', this._id)
}
}
Let me know if it solves your problem/answers all your questions.

Refreshing page with meteor iron router

Here is the problem :
I am currently programming a chatapp based on what i found on github (https://github.com/sasikanth513/chatDemo)
I am refactoring it with iron-router.
When I go to the page (clicking on the link) I get an existing chatroom (that's what I want)
When I refresh the page (F5) I get a new created chatroom ! (what i want is getting the existing chatroom ...)
Here is the code in ironrouter :
Router.route('/chatroom', {
name: 'chatroom',
data: function() {
var currentId = Session.get('currentId'); //id of the other person
var res=ChatRooms.findOne({chatIds:{$all:[currentId,Meteor.userId()]}});
console.log(res);
if(res){
Session.set("roomid",res._id);
}
else{
var newRoom= ChatRooms.insert({chatIds:[currentId, Meteor.userId()],messages:[]});
Session.set('roomid',newRoom);
}
}
});
You can find my github repo with the whole project : https://github.com/balibou/textr
Thanx a lot !
Your route data depends on Session variables which will be erased after a refresh. You have a few options but the easiest would be to put the room id directly into the route: '/chatroom/:_id'. Then you can use this.params._id to fetch the appropriate ChatRooms document. Note that you could still keep '/chatroom' for cases where the room doesn't exist, however you'd need to redirect to '/chatroom/:_id' after the insert.
In meteor, the Session object is empty when the client starts, and loading/refreshing the page via HTTP "restarts" the client. To deal with this issue, you could persist the user's correspondent id in a Meteor.user attribute, so that you could easily do:
Router.route('/chatroom', {
name: 'chatroom',
data: function() {
var currentId = Meteor.user().profile.correspondentId;
var res=ChatRooms.findOne({chatIds:{$all:[currentId,Meteor.userId()]}});
console.log(res);
if(res){
Session.set("roomid",res._id);
}
else{
var newRoom= ChatRooms.insert({chatIds:[currentId, Meteor.userId()],messages:[]});
Session.set('roomid',newRoom);
}
}
});
This would work, with the proper permissions, but I would recommend not allowing the direct update of that value on the client (I don't know if you want users to be able to override their correspondentId). So if you want to secure this process, replace all that code with a server method call, where your updates are safer.
Another (and more common case) solution was given by David Weldon, if you don't mind having ids in your URL (and therefore not a single url)

Resources