Access parent template ReactiveVar from child template?

That is exactly the case where you should use sessions. Or set the var in your parent template so you have it in the data context of your child template like this:

{{> childTemplate reactiveVar=reactiveVar}}

Do not use _ attributes unless you know exactly what you are doing.

3 Likes

Another vote for using Session (or something else, like a Session wrapper, i don’t like global variables very much).
The job of your child input template is just to get an input, not to know when to draw itself, or changing state of variables from parents.
Imho, you should track somewhere else that your input has been recorded, that the input template should be destroyed, and the focus put where you want to. That’s not the responsabilitiy of your input template.
Now, this is perhaps the limitation of reactive programming, there are some situations like that where your UI has a “story”, a sequence of steps that can’t be described by relations alone. I wonder if React solves thoses problems.

I had meant to mention in the original post that Session would not work because I couldn’t have this data set at a global scope. Was meant to indicate the editability of a grid cell within a grid using a common template, so the variable had to be ‘scoped’ to the cell that was being edited.

I ended up solving this using an approach similar to what was suggested by krevativ. Instead of storing the ReactiveVar in my template in the template’s ‘created’ function, I created the ReactiveVar in my data context which is available to my child template.

1 Like

Yeah, I said my idea was a horrible idea and I didn’t even use it… :smile:

To me it seems like a totally reasonable way of thinking, especially since Blaze has become more sophisticated.

For instance, using the features of Blaze.render you can render a modal anywhere on the page and still keep the view hierarchy intact, with the child view reporting upwards by means of a TemplateVar on the parent.

Traversing views manually is a dead end, but with a couple of utility methods (workman:templating-ext), it’s not a problem.

Here is a function to get a parent instance from its name (source):

Blaze.TemplateInstance.prototype.parentInstance = function(templateName)
    {
    if (!/^Template\./.test(templateName))
        templateName = 'Template.' + templateName;
    var view = this.view;
    while (view = view.parentView)
        if (view.name === templateName)
	        return view.templateInstance();
    };

Also, have a look at meteor-template-extension.

1 Like

@framnk, could you give us more detailed information about how you implemented your solution?

@davidystephenson I can only assume he used Template.parentData() to access the parent data context

How can reactiveVars be accessed via Template.parentData()?

1 Like

@davidystephenson

<body>
{{> parent reactive=someVar }}  <!-- someVar is a reactive var. in Iron-router, it would be initialized in the route's data property -->
</body>

<template name="parent">
 {{> child }}
</template>

Template.child.created = function() {

    this.foo = Template.parentData().reactive // child can access the parent's data context, where the reactive var is
}

my personal note is I would never use this pattern unless the child template is exclusively used together with the parent template, as it couples the templates in an error prone way. (i.e. an uninformed programmer steps in and splits the two templates, unaware of the effects on the child template)
I would use this pattern only if I wanted to split a big template to smaller templates for convenience, not practical modularity

an alternative suggestion is passing the reactive var to the child’s own data context in order to minimize coupling

@chenroth

Will this work even if the variable was declared as a new ReactiveVar in the parents template’s created variable?

unfortunately, no.

The inability in Meteor to store read/write template data, with similar ease of access as the read-only data context, has given me much grief :frowning:

To me, the mandatory access pattern of instance variables (i.e. declaring variables on the instance on template creation, such as this.foo = “bar”) is anti-DRY and error prone

I share your concern. I have been looking everywhere for a way to do access and modify an external template’s reactive variables, but cannot seem to find a way.

@davidystephenson @chenroth

Hy, maybe you may vote for my PR and follow this discussion on

2 Likes

My guess is that MDG is not willing to invest time on this because it it only a partial fix to a more global problem: how to create UI components in Meteor (see here).
It would be great if @dgreensp could complete your github thread by sharing his plan on that :- )

I know this is an old topic, but it’s at the top of Google search results, so I thought it would be worthwhile to give my two cents on the matter. I like the following pattern:

  • Create a handler for your ReactiveVar in the parent template, for example for an array:
Template.parent.onCreated(function parentOnCreated() {
  this.things = new ReactiveVar([]);
  this.thingsHandler = {
    push: (value) => {
      const arr = this.things.get();
      arr.push(value);
      this.things.set(arr);
    },
    remove: (value) => {
      const arr = this.things.get();
      const index = arr.indexOf(value);
      if (index > -1) {
        arr.splice(index, 1);
        this.things.set(arr);
      }
    }
  };
});

Template.parent.helpers({
  thingsHandler: () => Template.instance().thingsHandler
});
  • Pass it as the child template’s data context:
  {{> child things=thingsHandler}}
  • Use it:
Template.child.onCreated(function childOnCreated() {
  this.data.things.push('apple');
});

Template.child.events({
  'click .item': function pushItem() {
    this.things.push(this.name);
  }
});

Sorry, I’m not very imaginative with example names.

2 Likes

Agreed that this is old, but figured I’d share a solution I’m pretty happy with currently. Some hacked together ideas that allow you to modify vars from “anywhere”. For clarity’s sake, I’ll call the template with the reactiveVar the dataTemplate and the template looking to access/modify that var the seekerTemplate.

Some notes: I typically wrap dataTemplates in a ready check, as looking for a reactiveVar (or dict, or function) attached to an external template can result in a race condition, and even though the variable itself is reactive, if the initial lookup fails, then you’re stuck. This is mitigated by the fact that the seekerTemplate is usually a child of the dataTemplate. It is possible to use the strategy with sibling templates, but be warned that additional checks may be necessary to guarantee that the template instance is ready.

If anyone is interested, let me know as I’ve been considering documenting some of this stuff out. I’ve been using “components” for a while to access the properties of other templates for a while, and have found it to be pretty helpful.

Also, I’ve found that attaching the display:none div with a class name that matches the template name gives a reasonably safe method of fetching the template instance from Blaze.getView, but I’d be interested to hear other approaches.

<!-- client.html -->
<body>
  <h1>Welcome to Meteor!</h1>
  {{> dataTemplate}}
</body>

<template name="dataTemplate">
  <div class="dataTemplate" style="display:none;"></div>
  {{#if templateReady}} <!-- any conditionals come after the first div -->
    <!-- template stuff -->
    <p>Current State: {{ currentState }}</p>
    {{> seekerTemplate}}
  {{/if}}
</template>

<template name="seekerTemplate">
  <p>Current State: {{ getReactiveVar 'dataTemplate' 'currentStateVar' }}</p>
</template>
// client.js
import { Template } from 'meteor/templating';
import { ReactiveVar } from 'meteor/reactive-var';

import './main.html';

Template.dataTemplate.onCreated(function() {
  let self = this;
  self.currentStateVar = new ReactiveVar(true);
  self.templateReadyVar = new ReactiveVar(false);
});

Template.dataTemplate.onRendered(function() {
  this.templateReadyVar.set(true);
});

Template.dataTemplate.helpers({
  currentState () {
    return Template.instance().currentStateVar.get();
  },
  templateReady () {
    return Template.instance().templateReadyVar.get();
  }
});

Template.registerHelper('getReactiveVar', function (templateName, varName){
  let elem = document.getElementsByClassName(templateName)[0];
  if (Blaze.getView(elem)) {
    console.log(Blaze.getView(elem));
    if (typeof Blaze.getView(elem)._templateInstance[varName] !== 'undefined') {
      return Blaze.getView(elem)._templateInstance[varName].get();
    } else {
      console.log('no reactive var');
    }
  } else {
    console.log('no view');
  }
});

For fun in the browser, run:

elem = document.getElementsByClassName('dataTemplate')[0];
Blaze.getView(elem)._templateInstance['currentStateVar'].set('hello, world');

This is an awful lot of boilerplate to achieve something that can be done in 2 lines with import/export. Why not just export a reactive variable in a file and then import it into the necessary template files? Using scoped reactive vars like this obliviates a lot of component communication spaghetti we would have to write in the older es5 syntax.

//accounts_helpers.js
import { ReactiveVar } from "meteor/reactive-var"
export const isTyping = new ReactiveVar(false);
//enter_email_component.js
import { isTyping } from "./accounts_helpers.js"
...
//enter_fullname_component.js
import { isTyping } from "./accounts_helpers.js"
...
//enter_password_component.js
import { isTyping } from "./accounts_helpers.js"
...
8 Likes

Hopefully everyone looking for a solution makes it to the bottom. @streemo 's answer provides the most elegant way of passing local reactive variables between components

1 Like

@streemo I’m not sure if this is a good idea if you have more than one email, fullname or password component (so more than one form). As the Reactive Var is global it would change or modify all the other forms