in my custom Polymer element, i have a method that queries a firebase instance like so:
_updateUser: function(user) {
if(this._firebaseRef) {
this._firebaseRef.off()
}
if(user) {
var userLocation = ['xxxxx.firebaseio.com', 'users', this.user.uid].join('/');
this._firebaseRef = new Firebase(userLocation);
var query = this._firebaseRef.orderByChild("username")
query.on("value", function(messageSnapshot) {
messageSnapshot.forEach(function(messageData){
this.set('usernames', []);
console.log(this);
});
},null, this);
}
},
However, I keep getting the following error Uncaught TypeError: this.set is not a function. It seems that this is not set as the context as expected as per the api documentation
May I know how I can call this.set in my callback successfully? Thanks.
You need to pass the context into the forEach function as well.
messageSnapshot.forEach(function (messageData) {...}, this)
or
messageSnapshot.forEach(function (messageData) {...}.bind(this))
Related
I implemented a hook function, where I attach some createdAt and updatedAt fields to the doc that is inserted to a collection. I can attach this to any collection like this:
export const insertHook = function (doc) {
try {
const user = Meteor.user();
doc.createdBy = user && user._id ? user._id : null;
doc.createdAt = new Date().getTime();
} catch (e) {
console.err(e);
}
};
Attaching the hook to the collection is basically passing it via a third option in the constructor:
class HookedCollection extends Mongo.Collection {
constructor(name, options, hooks={}) {
super(name, options);
this.insertHook = hooks.insertHook;
}
insert(doc, callback) {
if (this.insertHook && Meteor.isServer)
this.insertHook.call(this, doc);
}
}
export const MyDocs = new HookedCollection("mydocs", {}, {insertHook});
In a Meteor method I just do a normal insert:
Meteor.methods({
insertDoc:function(doc) {
//check doc...
return MyDocs.insert(doc);
}
});
Which creates basically the following error:
Error: Meteor.userId can only be invoked in method calls or publications.
I tried several ways of bind but always ended up in this error. Is there really no way at all to bind the userId to the function?
According to Meteor docs Meteor.userId() is available anywhere but publish functions (Server side Publish function).
You aren't using Meteor.userId() directly in the method but in a callback (see discussion in this github issue). You can pass the userId information to your callback function as a parameter from the method, for example:
// Using Meteor.userId()
Meteor.methods({
insertDoc:function(doc) {
//check doc...
return MyDocs.insert(doc, Meteor.userId());
}
});
// Or using this.userId
Meteor.methods({
insertDoc:function(doc) {
//check doc...
return MyDocs.insert(doc, this.userId());
}
});
As a general rule use Meteor.userId() in the client (that queries the database) and this.userId in the server. More information in this other question Meteor - Why should I use this.userId over Meteor.userId() whenever possible? and in Meteor forums
I have a simple Meteor subscription, and I display a loading message while the data is being loaded. But I don't know how to display error message if subscription failed.
export const MyAwesomeComponent = createContainer(() => {
let sub = Meteor.subscribe('some-data');
if (!sub.ready()) return { message: 'Loading...'};
if (sub.failed()) return { message: 'Failed.' }; // How to do this?
return {
data: Data.find().fetch()
}
}, MyInternalRenderComponent);
Problem is, the subscription object doesn't have a failed() method, only a ready() query. How to pass the failure of a subscription as props in a createContainer() method?
I know the Meteor.subscribe method has an onStop callback for this case, but I don't know how to glue it toghether that to pass a property.
After a lot of researching I managed to get this working and I think it answers your question.
Bear in mind I'm using Meteor 1.6, but it should give you the info to get it working on your side.
On the publication/publish:
try {
// get the data and add it to the publication
...
self.ready();
} catch (exception) {
logger.error(exception);
// send the exception to the client through the publication
this.error(new Meteor.Error('500', 'Error getting data from API', exception));
}
On the UI Component:
const errorFromApi = new ReactiveVar();
export default withTracker(({ match }) => {
const companyId = match.params._id;
let subscription;
if (!errorFromApi.get()) {
subscription = Meteor.subscribe('company.view', companyId, {
onStop: function (e) {
errorFromApi.set(e);
}
});
} else {
subscription = {
ready: () => {
return false;
}
};
}
return {
loading: !subscription.ready(),
company: Companies.findOne(companyId),
error: errorFromApi.get()
};
})(CompanyView);
From here all you need to do is get the error prop and render the component as desired.
This is the structure of the error prop (received on the onStop callback from subscribe):
{
error: String,
reason: String,
details: String
}
[Edit]
The reason there is a conditional around Meteor.subscribe() is to avoid an annoying infinite loop you'd get from the natural withTracker() updates, which would cause new subscriptions / new errors from the publication and so on.
Is there a way to watch when one property in an object changes? I tried
var unwatch = obj.$watch(function() {
console.log("data changed");
});
That fired when any property in the object changes. I tried
var unwatch = obj.myProperty.$watch(function() {
console.log("data changed");
});
That returned an error message: "TypeError: obj.myProperty.$watch is not a function".
I tried
var myTargetValue = $firebaseObject(ref.myProperty);
That returned an error message: "TypeError: Cannot read property 'ref' of undefined".
You'll have to create a $firebaseObject for the property. But, using the Firebase SDK tends to be more useful than $watch().
JSBin Demo
angular.module('app', ['firebase'])
.constant('FirebaseUrl', 'https://34474165.firebaseio-demo.com/')
.service('rootRef', ['FirebaseUrl', Firebase])
.controller('MyCtrl', MyController);
function MyController($scope, $firebaseObject, rootRef) {
var objRef = rootRef.child('obj');
var obj = $firebaseObject(objRef);
var objMessage = $firebaseObject(rootRef.child('obj/message'));
// watch the whole object
var unwatch = obj.$watch(function(a) {
console.log(a);
});
// watch a child property
objMessage.$watch(function(a) {
console.log(a, 'msg');
});
// get the whole object as it changes
objRef.on('value', function(snap) {
console.log(snap.val());
});
// get the child property as it changes
objRef.child('message').on('value', function(snap) {
console.log(snap.val());
});
}
Short answer - Use vanilla Firebase (i.e. don't use Angularfire)
To watch a string, number or boolean property with Angularfire it's best to use vanilla Firebase:
// firebase reference
db = firebase.database().ref().child("myTable");
// watch the property
db.child("someProperty").on("value", function(snapshot) {
$scope.message = snapshot.val();
});
Why not Angularfire, and $value:
Using $firebaseObject and passing in a reference to a primitive (string, number, boolean) returns an object like the following (reference documentation):
{
$id: "myProperty",
$priority: null,
$resolved: true,
$value: "Hi mom!"
}
In this example, the string I'm trying to watch is contained in the $value property of the returned object. I was inclined to assign the $value property to the $scope, however this wouldn't work:
// WONT WORK
$scope.foo = $firebaseObject(db.child("myProperty")).$value;
There's a clash between Angular's object naming conventions and Firebase's object naming conventions that causes some trouble here. According to this "Important Note" in the documentation, Angular's $watch function ignores properties with a $ prefix. In other words, the view is not going to update if you assign the $value property of the returned object to $scope.
Thus, it's best to just use vanilla firebase to solve this problem (see top). Hope people find this helpful.
I'm stumped here. I can't get it to find a collection from the onCreated method. If I log the data.source_id right before the call and then do the same lookup in the console, it finds it. Is there something special about onCreated or something? Am I just doing it wrong?
client/setup.js
Meteor.subscribe('source_elements');
Meteor.subscribe('internal_elements');
client/submit.js
Router.route('/element/submit', function() {
this.render('submit', {
data: {
source_id: this.params.query.source_id,
},
});
});
Template.submit.onCreated(function() {
var data = Template.instance().data;
var source_element = SourceElements.findOne({'_id': data.source_id});
console.log(source_element); // EMPTY!!
});
Template.submit.helpers({
element: function() {
var data = Template.instance().data;
var source_element = SourceElements.findOne({'_id': data.source_id});
console.log(source_element); // RESULT!!
return source_element;
},
});
Subscriptions are asynchronous. It looks like you are creating the template before the data has arrived at the client. By the time you execute the find in the console, the client has received the data.
Inside your onCreated function, you could use Tracker.autorun to specify a function that will be rerun when the SourceElements collection changes (that's what all template helpers do behind the scenes):
Tracker.autorun(function() {
var element = SourceElements.findOne({'_id': data.source_id});
console.log(element);
});
This function will be called immediately. At this point, findOne will probably return undefined because the subscription is not ready yet. Once the data has arrived, the function will be called again and you can process the returned elements.
Consider the following code :
Template.fullDoc.rendered = function() {
// Get triggered whenever the selected document id changes
this.autorun(function() {
var docId = isolateValue(function() {
return Template.currentData().selectedDoc._id;
});
...
});
}
This code doesn't work. Inside isolateValue(), Template.currentData() sometimes triggers an exception: Exception from Tracker recompute function: Error: There is no current view (this corresponds to the fact that Template.instance() returns null).
So how do you set a reactive dependency on a subpart of a template data context?
You could recreate the isolateValue behaviour in a way which doesn't cause Template.instance() to get set to null sometimes.
$ meteor add reactive-var
Template.fullDoc.rendered = function () {
var docIdVar = new ReactiveVar();
this.autorun(function () {
docIdVar.set(Template.currentData().selectedDoc._id);
});
this.autorun(function () {
var docId = docIdVar.get();
// ...
});
}
This makes use of the fact that setting a ReactiveVar to the same value it already has will not trigger an invalidation. (By default this only works for primitives; for objects you'll need to pass a custom equalsFunc when you construct the ReactiveVar. If _id is a string, you're fine. If it's ObjectID you probably aren't.)