How to handle app-wide variables?


#1

Let’s say I’ve got a Meteor app with some app-wide variables, like system settings (tabSpaces = 2). These are stored in a Mongo collection. If I’m loading these variables into Session variables to be used globally, does it make sense to just define global helpers so they can be accessed in all templates?

Template.registerHelper('getSystemVar', function (varName) {
  return Session.get(varName);
});

BTW, using Session instead of just pulling straight from the Mongo collection, because users can temporarily alter these variables (but they shouldn’t be stored).

EDIT: I’m also loading the default variables into Session vars within the onCreated callback for my router’s layout template. Not sure if there’s a better place for this. They’re not really related or dependent on the layoutTemplate, but I do have to subscribe to the system settings collection and wait for that to be ready before loading the defaults into Session vars.


#2

I’m doing similar things and have seen this pattern elsewhere, so you’re surely not completely off track!

Basically we have the options of using Session or ReactiveVar for storing such global values, or of course putting them in a collection, “singleton”-style, which although does feel like too much of a hack to me. (i.e. single item in a collection with a single attribute “config” or similar)

The global helper for accessing the values is what I also use. I’m not sure what alternatives we have here. I suppose there should be some way to use a base controller with iron:router that all other controllers inherit from and “inject” the data there somehow, but I personally had trouble figuring out what exactly I could do with the getData() method in iron:router and what would break things (I broke things, a lot, and so just moved to global helpers etc).

Regarding where to load these values: To me something like

Meteor.startup(() => {
  Tracker.autorun((c) => {
      // ... load stuff into Session ...
      if ( /*... check if we've loaded everything now ...*/ ) {
        c.stop(); // don't need to reevaluate now if we don't want to override things that may have been changed in the session
      }
  })
})

seems like a reasonable approach, without unnecessarily relying on iron:router or something else that doesn’t need to be part of this particular aspect.

Cheers,
Denis


#3

That could work too. The “load stuff into Session” part is a bit of a concern. What’s going on there? We don’t have access to Template.subscribe within that context, so I’m wondering if there are any security concerns there as far as how much data is being published to the client.


#4

I don’t quite get what you mean by that. Obviously you would need to Meteor.publish('myConfigData') and Meteor.subscribe('myConfigData') on server/client respectively, and if you don’t send to the client what shouldn’t go there you’re safe, no? The client can’t modify things (on the server) you don’t allow it to explicitly.
The layoutTemplate should never be destroyed, so the scope of the subscription would essentially be the same – loaded on page load, never destroyed/canceled. By “security concerns” do you mean “concern about polluting the client with way too much data”? If yes, and we’re talking about “system settings”, then usually we’re also talking about maybe dozens of variables? It takes much more time/effort to cancel out subscriptions and resubscribe on route change than to keep an extra dozens or even a couple hundred probably, of extra variables around globally, all the time.


#5

Oh, right! Never mind, I was confused. I’ve been programming my brain to use the relatively new Template.subscribe and forgot about Meteor.subscribe for a minute. :wink:


#6

If user can alter this variables which should not be stored in the permanent DB, why not store them in a local collection ?
You can use my package https://atmospherejs.com/vjau/localcollections-persister which help manage those localcollections and get them to persist between hot code pushes (like Session !).

instead of loading the data into Session at startup, here is what you can do :

  • put your persistent data (settings) in a regular collection, let say SettingsColl
  • put the user temporary alteration of those settings (= session state) in a local collection, let say SessionSettingsColl

Now create an helper that does this:

var fooSetting = function(){
  return SessionSettingsColl.findOne({foo:bar}) || SettingsColl.findOne({foo:bar})
};

That way, when the settings (defaults) are only in the DB you will get them from SettingsColl.
When your user has altered the settings for the session, you will get them from SessionSettingsColl

I think Session is only useful for demo, and should not be used anymore.


#7

I didn’t get this. It would be nice if you could elaborate?


#8

Session is global. Global is bad.
You can use Reactive-dict instead for simple state (example true/false or a simple string).
For more elaborate states when you need multiples objects, i think a local collection is better.


#9

If Session were truly “bad,” I doubt it would’ve been included in the framework.

Black and white statements are bad. :wink:


#10

I’m curious, let’s say you have one template that’s a form, and when all the input fields are filled, a reactive variable is set (fieldsFilled = true) that should be able to be accessed by a totally separate template. Or maybe all templates need to globally access this. How would you construct that without using Session?


#11

The use of one global variable shared by multiple parts of code is called “common coupling”. That’s the second strongest coupling acording to common practices : http://en.wikipedia.org/wiki/Coupling_(computer_programming)
When multiples parts of your program needs to communicate between them, there is always some coupling and some global component (i didn’t say variable !) involved.
If you look again at that wikipedia page i mentionned, you will see that they talk about “message coupling” which is the lowest type of coupling. This is implemented in Meteor through browser events.
While i don’t necessarily advocate the use of events here, which don’t go very well with the “reactive programming” concepts, i think i can say again that Session is bad and should probably be deprecated from the framework.
Why did the MDG folks did implement it, then ? I’m not in their head, but i guess they thought it was useful for quick demos showing the principles of reactivity without too much boilerplate details that would confuse the meteor newbie.


#12

@vjau Ok. I understand all of that, but you didn’t answer my question, which was: how would you personally share data/variables/objects between templates? A local/client-only collection?


#13

There is no definitive, one solution for every problem, answer for this, or there would be no use for globals !. It depend on the problem at hand.
ATM patterns are a bit lacking in meteor developement, i suppose they will come as the community (and MDG) is gaining experience with this.
You ask me “how would you share data/variables/objects between templates”, that’s three different things. Data (persistent) is best shared through the database. Session state should imho be but in reactive-dict (with appropriate scope) for simple data like “isPostEditing”, and in a local collection for more complicated objects, objects graph, stuff that is not yet ready to be recorded in DB…
With blaze lacking atm true components, you have sometimes to use events to communicate between templates (example an html select “component” which must inform other parts of the app that a certain value has been selected).
There is no easy answer to this problem.


#14

Ok, so let’s say ReactiveVar or ReactiveDict for a simple variable like isEditing. Now how would you share that variable or dict between templates?


#15

For relatively global states like “isEditing”, i like to use a Registry, ie a global object with accessors methods :
var blogRegistry = new BlogRegistry() //singleton
if (blogRegistry.isEditing()){
//do stuff

Inside the isEditing() function, you query the appropriate reactive-dict

If you put all your code in packages (mandatory for big apps), you can put the registry in its own package, and only appropriate clients packages will have access to it.