Using pipe in *ngFor, the page sometimes updates, sometimes not - meteor

I am using angular2-meteor, I already use pure: false. But the pipe sometimes run, sometimes not. See my comments in the code for details of the problem.
Thanks
<div *ngFor="#user of (users|orderByStatus)">
{{user.status.online}}
</div>
users:Mongo.Cursor<Meteor.User>;
ngOnInit()
{
this.subscribe('users', () => {
this.autorun(() => {
this.users = Meteor.users.find();
});
}, true);
}
import {Pipe} from 'angular2/core';
#Pipe({
name: 'orderByStatus',
pure: false
})
export class OrderByStatusPipe {
transform(usersCursor:Mongo.Cursor<Meteor.User>):Array<Meteor.User> {
console.log("OrderByStatusPipe runs");
// (1) If I only do these two lines, the change of other users' status can show on the screen immediately.
// let users = usersCursor.fetch();
// return users;
// (2) If sort users by status, the page sometimes updates, sometimes not when user status change.
// If not update automatically, I click that part of screen, it will update then.
let users:Array<Meteor.User> = usersCursor.fetch();
users.sort((a, b) => {
return (a.status.online === b.status.online) ? 0 : (a.status.online ? -1 : 1);
});
return users;
}
}

UPDATE: The bug seems fixed.
I think the problem is related with angular2-meteor.
At last I found a working way using sort in when you try to get data from Mongo. So not using sort pipe any more.
But you cannot use users:Mongo.Cursor<Meteor.User> with *ngFor, need fetch() first and use Array<Meteor.User>, otherwise it will show this error when the order of list changes:
Cannot read property 'status' of undefined
But then the list won't update automatically in UI. So you need use NgZone.
So the final working code is like this:
<div *ngFor="#user of users)">
{{user.status.online}}
</div>
users:Array<Meteor.User>; // here cannot use users:Mongo.Cursor<Meteor.User>
constructor(private _ngZone:NgZone) {}
ngOnInit()
{
this.subscribe('users', () => {
this.autorun(() => {
this._ngZone.run(() => {
this.users = Meteor.users.find().fetch();
});
});
}, true);
}

I don't know exactly what is behind the calls Meteor.users.find() and usersCursor.fetch() but I think the retrieval of your users should be done outside the filter itself. I guess that one part is done in the filter (with usersCursor.fetch()?) and this could be the problem...

Related

I have a problem with autosaving data in VueJS, the autosave doesn't complete when I change the current note

so I have a problem this problem with my app. I'm not sure what is the right way to implement this autosaving feature. When you change the note's title or it's content, there is a debounce function that goes off in 2 seconds and if you change to the current note before the debounce update is complete it never updates. Let me know if I've done a poor job of explaining or if there is something that I need to clarify, Thanks!
Here's a video of what occurs: https://www.loom.com/share/ef5188eec3304b94b05960f403703429
And these are the important methods:
updateNoteTitle(e) {
this.noteData.title = e.target.innerText;
this.debouncedUpdate();
},
updateNoteContent(e) {
this.noteData.content = e;
this.debouncedUpdate();
},
debouncedUpdate: debounce(function () {
this.handleUpdateNote();
}, 2000),
async handleUpdateNote() {
this.state = "loading";
try {
await usersCollection
.doc(this.userId)
.collection("notes")
.doc(this.selectedNote.id)
.update(this.noteData)
.then(() => this.setStateToSaved());
} catch (error) {
this.state = "error";
this.error = error;
}
},
setStateToSaved() {
this.state = "saved";
},
Why running every two seconds ?
And an async wait is a really bad approach in a component.
To autosave the note I recommend that you add an eventListener on window closing or on changing the tab event, Like in the video provided (whatever event suits you best)
created () {
window.addEventListener('beforeunload', this.updateNote)
}
Where your updateNote function is not async.
But if you really want to save on each change.
You can make a computed property that looks like this:
note: {
get() {
return this.noteData.title;
},
set(value) {
this.noteData.title = value;
this.state= 'loading'
usersCollection.doc(this.userId)
.collection("notes")
.doc(this.selectedNote.id)
.update(this.noteData)
.then(() => this.setStateToSaved());
}
},
And the add v-model="note" to your input.
Imagine the user will type 10 characters a second That's 10 calls meaning 10 saves
EDIT:
Add a property called isSaved.
On Note change click if(isSaved === false) call your handleUpdateNote function.
updateNoteTitle(e) {
this.noteData.title = e.target.innerText;
this.isSaved = false;
this.debouncedUpdate();
}
and in your function setStateToSaved add this.isSaved = true ;
I don't know if your side bar is a different component or not.
If it is, and you are using $emit to handle the Note change, then use an event listener combined with the isSaved property.

meteor: publication / subscription not working

I can't for the life of me figure out why nothing shows up client-side in this meteor app. I have tried all the advise in all the related topics and nothing seems to work. I'm using msavin:mongol and I don't even see the subscription on the client at all, despite console.log() debug output indicates that it is there with the current number of entries.
/imports/api/friends.js:
export const Friends = new Mongo.Collection('friends');
Friends.deny({ insert() { return true; }, update() { return true; }, remove() { return true; } }); // access to collections only through method calls
/imports/api/server/friends.js:
import { Meteor } from 'meteor/meteor';
import { Friends } from '../friends.js';
Meteor.publish('friends.all', function(){
return Friends.find();
})
/imports/ui/pages/friends.js:
import { Friends } from '/imports/api/friends.js';
import './friends.html';
Template.friends.onCreated(function() {
this.subscribe('friends.all');
});
Template.friends.helpers({
friends: ()=>{ return Friends.find(); }
});
/imports/ui/pages/friends.html:
<template name="friends">
<h1 class="ui header">Friends</h1>
{{#if Template.subscriptionsReady}}
<h2 class="ui heder">friends list:</h2>
<div class="ui list">
{{#each friend in friends}}
<div class="item">{{friend.name}} ({{friend.email}})</div>
{{/each}}
</div>
{{/if}}
</template>
The "friends list" header shows up, so the subscriptionsReady call returns, but I don't get any data (verified that data exists in the database).
I've also tried moving the subscription into the router (using ostrio:flow-router-extra) and there the waitOn() function never returns when I add the subscription
What is going on here?
If you are missing to include your publication on the server then your client's subscription will immediately be 'ready' but there will be no error message.
This can cause a lot of confusion when creating templates with template-level-subscriptions.
In order to check if a publication exists or not, you can use on the server after start the (undocumented) server.publish_handlers array. It keeps a record of the registered publications.
Meteor.startup( () => {
console.log( Meteor.server.publish_handlers );
});
This comes in very handy, if you have designed your api in way that it keeps track of its intended publications:
friendsdef.js
// definition file for Friends collection and it's
// surrounding functionality
export const FriendsDef = {
collectionName: 'friends',
schema: { ... },
methods: { ... },
publications: {
all: {
name: 'friends.all'
}
}
}
someServerStartup.js
// make sure on startup, that all intended
// publications are registered
Meteor.startup( () => {
Object.values( FriendsDef.publications).forEach( pub => {
if (!Meteor.server.publish_handlers[pub.name]) {
throw new Error('publication should exist, but does not');
}
});
});
The same works with method_handlers for methods. Use these data structures with a well defined API and you will decrease errors of missing includes or misspelled names a lot.

Vuejs & Firestore - How to Update when Data Changes in Firestore

I've gone through a bunch of tutorials and docs but cannot seem to be able to update on page when data changes in Firestore (NOTE: not Firebase)
Heres what I have currently which is working fine except if data changes in the DB it is not reflected on the page itself unless I refresh. Code below is within script tags:
import { recipeRef } from '../../firebase';
export default {
data() {
return {
recipes: []
}
},
firestore: {
recipes: recipeRef
},
created() {
db.collection('recipes').get().then((onSnapshot) => {
this.loading = false
onSnapshot.forEach((doc) => {
let data = {
'id': doc.id,
'name': doc.data().name
}
this.recipes.push(data)
})
})
}
I'm not using Vuex. Adding data, editing and reading works fine. Just not reflecting changes once data has changed. Maybe there is a life cycle hook Im supposed to be using? For "onSnapshot" - Ive tried "snap", "querySnapshot" etc. No luck.
Thanks in advance.
Remove the get() and just replace with snapshot - like so
created() {
db.collection('recipes').onSnapshot(snap => {
let foo = [];
snap.forEach(doc => {
foo.push({id: doc.id, name: doc.data().name})
});
}
});
I am not familiar with the firestore API, but glancing through the docs, it looks like calling get() is how you query a single time. Where you have onSnapshot should really be querySnapshot -- that is, the results of a one query. See:
https://firebase.google.com/docs/firestore/query-data/get-data
versus:
https://firebase.google.com/docs/firestore/query-data/listen
So to get live updates, it looks like you need to create a listener, like so:
db.collection('recipes')
.onSnapshot(function(snapshot) {
snapshot.forEach(function(doc) {
// Find existing recipe in this.recipes
// and swap in the new data
});
}, function(error) {
// handle errors
});
I think you will need to add that listener in addition to the get() query you are currently doing. Hope this helps!

Firebase: Get notified just after the firebase-function trigger promise completed

In my app I pushed some object to my firebase-database and immediately after that (after the then-promise fully filled) I fetch the object (with the returned key) from the database (with the on-value method).
In addition, I make some changes on the pushed object using the firebase-functions.
How can I receive the object (in the app) just after the changes and not before? (like other ordinary backend services)
I hope this helps you, I have not tested this piece of code but it should help you in the right direction.
Also dont use this exact code in production, there is plenty room for improvement, this is just an example code.
exports.testFunction = functions.https.onRequest((req, res) => {
if (req && req.body) {
if (
req.body.hasOwnProperty('name') &&
req.body.hasOwnProperty('age')
) {
const person = {
name: req.body['name'],
age: req.body['age']
}
// Make some changes to the person object
person['hobby'] = 'Programmer';
// Add object to FireStore
admin
.firestore()
.collection('/persons')
.add(person)
.then((success) => {
// Return the added & changed person
res.status(201).send(JSON.stringify(person));
})
.catch((error) => {
// Error
console.error('Something broke', error)
res.status(500).send();
});
}
else {
// Error
res.status(500).send({err: 'Missing property'});
}
}
else {
// Error
res.status(500).send({err: 'Missing something'});
}
});

Meteor template updates before result of Meteor.users.update

I'm trying to figure out how to prevent a template from updating until Meteor.users.update() finishes.
First I'm trying to make sense of the documentation and the use of an optional callback argument in order to sort out what is happening.
Here is what I have:
Meteor.users.update(Meteor.userId(),
{$set:{'profile.reviewList': []}},
[],
function(err, result){
if (err){
console.log('oh no!');
} else {
console.log('Result achieved: '+this.profile.reviewList);
}
});
Currently the console.log('Result achieved: '+this.profile.reviewList); always returns something like ...TypeError: Cannot read property 'reviewList' of undefined... the first time though which tells me its firing before the result comes back.
I'm sure I'm not implementing the callback properly, but I was attempting to model this answer: How do you ensure an update has finished in meteor before running a find?
I'd really just like to delay the re-rendering of the associated template until the property gets created.
Any help will be greatly appreciated.
You assume that scope (this) in callback function return user object, which is wrong.
If you want to get user object in that callback simply query it there:
var user = Meteor.users.find(Meteor.userId()).fetch()
Another thing, you passed empty array as 2nd argument which is not needed.
Meteor.users.update(
Meteor.userId(),
{
$set: {
'profile.reviewList': 'testData'
}
},
function(err, result) {
if (err) {
console.log('oh no!');
} else {
var user = Meteor.users.find(Meteor.userId()).fetch();
console.log('Result achieved: ' , user && user.profile && user.profile.reviewList);
}
}
);

Resources