Access/store ReactiveDict object as a whole, not as a separate instances


#1

Hi,

Is there a solution to replace the separate ReactiveDict elements calls:

userProfile = new ReactiveDict();

userProfile.set(‘email’, Meteor.user().profile.email);
userProfile.set(‘nick’, Meteor.user().profile.nick);

Meteor.users.update({ _id: Meteor.userId() }, { $set: { ‘profile.nick’: userProfile.get(‘nick’) } });
Meteor.users.update({ _id: Meteor.userId() }, { $set: { ‘profile.email’: userProfile.get(‘email’) } });

with just

userProfile = Meteor.user().profile;
Meteor.users.update({ _id: Meteor.userId() }, { $set: { ‘profile’: userProfile.giveMeWholeObject() } });


#2

The following will return the complete dict key/value pairs as an object

console.log(userProfile.keys);

but the values will still be in EJSON format. You would have to parse them before using them, so something like this

console.log(_.mapObject(userProfile.keys, (key, value) => EJSON.parse(value)));

should give you the object you’re looking for.


#3

ReactiveDict is probably not what you want in this case. What you probably want is a single ReactiveVar which holds an object which can be altered. The ReactiveVar then triggers re-run at that level. That of course is complicated and expensive.

You could also extend the existing ReactiveDict object by modifying it’s structure a bit. By default it has a series of observers based on the key, but you’d want to add an observer based on the whole object and a get that triggers that.

A (very rough) example of how one might start to go about this:

var oldSet = ReactiveDict.set;
_.extend(ReactiveDict.prototype, {
	set: function (keyOrObject, value) {
		var ret = oldSet.call(this, keyOrObject, value);
		var self = this;
		if (self.keyDeps['_allValues']) {
			self.keyDeps['_allValues'].changed();
		}
		return ret;
	},
	getAll: function () {
		var self = this;
		self._ensureKey('_allValues');
		self.keyDeps['_allValues'].depend();
		return self.keys;
	}
});

getAll will return an object with each value being an EJSON string. It will also set up a dependency on the entire dictionary.

I’ll leave it as an exercise for you to improve this if you want to use it. Recommendations for improvement:

  1. Iterate over self.keys and EJSON parse each one.
  2. Find out if anything will actually change when set is called and only trigger the dependency changed method if it actually did.

For reference, the entire code of reactive dict is in one file in the github repository: https://github.com/meteor/meteor/blob/devel/packages/reactive-dict/reactive-dict.js


#4

Thanks guys, that opened my mind a little.


#5

To follow up on @hwillson’s comment, you should be able to use .all() to retrieve the object, like so:

'click .js-save' (e, i) {
  let myObject;

  _.map(i.myDict.all(), function (val, key) {
    myObject[key] = val;
  });
} 

You can also do the following:

myDict = new ReactiveDict();

myDict.set({
  email: Meteor.user().profile.email,
  nick: Meteor.user().profile.nick
});

#6

Thanks all, again.

And just to summaries my attempts. I have landed with such a strange construction:

Template.profile.onCreated(function () {
    this.userProfile = new ReactiveVar();
    this.userProfile.set(Meteor.user().profile);
    console.log('onCreated profile is:');
    console.log(this.userProfile);
});

and then to keep the reactivnes inside helpers:

Template.profile.helpers({
 
 getProfileComment: function () {
    let tmp = Template.instance().userProfile.get(); << keep helper reactive by get() call
 return tmp.comment;    << return one of the profile fields value, 
                              
} });