kiriappeee
kiriappeee

Reputation: 87

Why are all Meteor template helpers within a with/each block called when an unrelated field changes in the parent?

When using a #with block in Meteor templates, if I edit a single field in the outermost logical section of the block, it still causes a call of all the template helpers within the #with block to run again. This applies to each too. To demonstrate this I have a uploaded a project to github. It can be found at:

https://github.com/adnancin/meteor-test-template-render

If you download and run that project, you'll see a page that renders the following:

In the default data set there is one project owner, two projects, and two tasks where the two tasks belong to the first project.

The projects are under a #with OwnerProject binding.

The projects are listed using a #each binding.

Tasks are under the projects also listed using a #each binding.

I've given two buttons to change data. One changes the first project data. The other changes the project owner's first name. The template helpers have console logs to show which one's run.

When the project data is changed, the template helpers for the tasks under that project are run. When the owner's first name is changed, every single helper is run. Neither case should happen according to the blaze docs. It should only be the helper for the field that has changed.

Is this a bug? Or is it a feature for which there is an obvious/non obvious workaround?

Upvotes: 2

Views: 409

Answers (1)

kiriappeee
kiriappeee

Reputation: 87

Well, I solved the problem. Before getting into the details, it should be said that Meteor has chosen to implement the data template helpers to rerun for any change in the parent data context. Why? I'm still not sure. (Waiting on a reply). In the mean time, I realised that if the data context document changing can trigger a full set of re runs, then the best thing to do is to remove the changeable information from a data context. I've updated my project on github (link is in the question) to reflect this.

The template previously looked like

projecttemplate.html


    <template name="projectTemplate">
        {{#with superTemplate}}
            {{fullName}}
            {{#each multiProject}}
                <h3>Here is the {{this.name}}</h3>
                <p>And this is the {{type}}</p>
                <p>And here they are combined: {{nameType}}</p>
                <p>And these are the tasks associated with it </p>
                {{> tasksTemplate}}
            {{/each}}
        {{/with}}
    </template>

In this, the super template set the context for everything else. The Project Owner had a reference to the projects owned by her and that was used for the multiple projects. In the multiple projects loop, the data context for the each task under that project was a complete project object.

The current template

projecttemplate.html

    <template name="projectTemplate">
        {{#with ownerProjectId}}
            {{#with ownerProjectData}}
                {{fullName}}
            {{/with}}
            {{#each multiProject}}
                {{#with singleProjectData}}
                    <h3>Here is the {{this.name}}</h3>
                    <p>And this is the {{type}}</p>
                    <p>And here they are combined: {{nameType}}</p>
                    <p>And these are the tasks associated with it </p>
                {{/with}}
                {{> tasksTemplate}}
            {{/each}}
        {{/with}}
    </template>

This template changed the data context. Instead of using a full Project owner object, it only used that owner's Id. To display the owner's properties, a with binding to a template helper called ownerProjectData was used. The ownerProjectData helper fetches the OwnerProject object based on the id and returned it. This helps separate the data which is needed for the projects from the data which is not needed.

This pattern is applied again to the each binding on the projects. Instead of using the complete project model, I use only the project id field which is all that is really needed for the tasks. For the area that I need to display the project information, I once again use a helper that get the full Project Model based on the Id passed in each iteration in the loop.

A peculiarity that I ran into that is really important was how modelling my data changed whether or not a full rerun of helpers would be called when I added a project. At the point where inserting a new project would cause a full rerun, the OwnerProject data doc looked like this:

    {
    OwnerProject:
        _id: "ownersautoassignedid"
        firstName: "some",
        lastName: "example",
        projects:[
            "idofproject1","idofproject2"
        ]
    }

In this case, the OwnerProject kept a reference to all projects owned by her. If I added a new project under her, I would insert the project, get its id and then update the owner. This however, would cause a full rerun of all the helpers. Instead, I removed the projects property from the owner and changed the project structure to have a reference to the owner id. On the template side I got the project IDs by doing a select where owner id was equal to the superTemplate context id. This time when inserting a new project only the template helpers for the new project were called and the other items were not affected.

There you have it. A long answer describing a fairly simple solution to a problem that you will definitely hit if you follow the standard documentation for meteor. Hope this helps in the future.

Don't forget to look at the source in the github project and check the commit history and the github issues. It'll describe how I got here.

Upvotes: 1

Related Questions