How to keep Templates independent of Routers

I wanted to separate the codes in Template Layer from Router Layer.

For example, I want not to use Router APIs in the Template Layer such as Router.current(), Router.params(), or Session variables.

So, I used data context to send params or query params from Router to Template.

I thought that Router should do just routing and Template codes should not depend on Router codes.

However, I never have seen such code snippets.

Am I wrong?

// router
Router.route('/category/:_id', {
  name: 'categoryView',
  data: function() {
    return {
      params: this.params,
      queryParams: this.params.query
    };
  }
});

// templates
Template.categoryView.onCreated(function() {
  var instance = this;
  var data;

  instance.increment = 20;
  instance.limit = new ReactiveVar(instance.increment);
  instance.loaded = new ReactiveVar(0);

  instance.autorun(function() {
    data = Template.currentData();
    var limit = instance.limit.get();

    instance.subscribe('releasedCategoryView',
      data.params._id, { limit: limit },
      function() { instance.loaded.set(limit); }
    );

    instance.subscribe('releasedPostsListCount',
      { categoryId: data.params._id });    
  });       
});

Therefore, I can separate Template Layer from Router Layer without using Router APIs and Session variables.

What is your opinion?

:thumbsup: I think decoupling your templates from the router gives you:

  • You can test your templates easier. No router mocking required.
  • You can use you templates independently from the router. Makes it easy to use another router and reuse templates in another context (different route, different parents).

I think you can even go a step further and just pass the data to the template that it needs. This would make the template completely independent from route concepts as params and queryParams. Something like:

// router
Router.route('/category/:_id', {
  name: 'categoryView',
  data: function() {
    return {
      _id: this.params._id
    };
  }
});

// templates
Template.categoryView.onCreated(function() {
  var instance = this;
  var data;

  instance.increment = 20;
  instance.limit = new ReactiveVar(instance.increment);
  instance.loaded = new ReactiveVar(0);

  instance.autorun(function() {
    data = Template.currentData();
    var limit = instance.limit.get();

    instance.subscribe('releasedCategoryView',
      data._id, { limit: limit },
      function() { instance.loaded.set(limit); }
    );

    instance.subscribe('releasedPostsListCount',
      { categoryId: data._id });    
  });       
});

Thanks Sanjo,

However, it seems that no one use this pattern.

Flowrouter even said that using data context is not good.

I want to know whether passing the params to data context is good or bad.

Actually, I think that Flowrouter is wrong.

It said that router.current() is evil since it makes unpredictable re-run and re-rendering of the template.

But, I think that Router APIs should be excluded from Template codes.
And if data context cause the re-run and re-rendering, it has to be resolved the other ways.

Routers should do only routing with passing the input parameters to Templates.

I think you need to read the sections again and completely. I think the points that Arunoda makes are all valid and they don’t say anything against your approach.

Regarding data context:
It just says that Flow Router does not have the data context thing built in that Iron Router has because Flow Router does not have template rendering built in. Instead you use the layout manager or something else (e.g. Blaze.renderWithData) to render your templates. Rendering templates is still usually triggered by route changes.

Regarding router.current() is evil:
Just read the last part. The problem is that router.current() triggers reactive reruns when anything from the route changes. Instead of this you should be very specific about what part of the URL (which params or query params) your care. This way you only have reactive reruns when you really want to. This results in better performance.

@sanjo Flow Router certainly looks down upon data contexts. They call it an antipattern.

https://kadira.io/academy/meteor-routing-guide/content/subscriptions-and-data-management

One of the essential parts of a Meteor app is data and subscription management. In the past, we invoked subscriptions in the router itself and manage data inside it. However, now we consider that to be an anti-pattern.

Yes and I agree that it is antipattern.
Router should do routes and when we want it to be not responsive, we cant handle data context there.
As in Router itself, even if you call subscription, data is still not there yet.

So you than hack it and spawn another autorun to check for sub state and than re-render. Or wait for 4 different event types and re-render like crazy - good old iron router times.

The flow way I expect to be render layout and than in different layouts or sub-templates handle subscriptions and data contexts.
Just decide which layouts/templates will be wrappers fetching/preparing data and which will be displaying passed data context.
Simple as that, pure convention.

It seemed that there are some misunderstandings on my posts.

I do not think that all of the Flow router is wrong.

What I want to know is how to deliver some data from Router layer to Template layer.
The params or query params in Router or request path are the data, I think.

Flowrouter recommends that you should call the data through Router APIs in Template codes.

When you use the Flowrouter, you should call FlowRouter.getParam(paramName) or FlowRouter.getQueryParam(key) in Template.onCreated(…).

However, I think that you should put the data into Template state in Router layer. And to do this, the data context is the appropriate attributes of Template object. For what I know, the data context is not defined by Iron router but defined in Template object.

Well, from kadira:blaze-layout example

<template name="layout1">
  {{> Template.dynamic template=top}}
  {{> Template.dynamic template=main}}
</template>

And in Router

BlazeLayout.render('layout1', { top: "header", main: "postList" });

I am not using it that way atm, but I would expect that you can pass any kind of property to layout1 and use it there, for example some keyword which you pass down to component so it knows what to fetch