What's the best practice is terms of putting logic in routers vs. templates?


#1

Hello, I’m struggling a bit with deciding where some of my application logic should go (perhaps it doesn’t matter and is just a stylistic thing), and am wondering what other people do.

An example would be a paged listing of customers. One method of accomplishing this would be to have some pretty extensive routing code:

CustomersListController = RouteController.extend({  
    template: 'customers',  
    recordsPerPage:  parseInt(Meteor.settings.public.recordsPerPage),   
    currentPage: function() {     
        return parseInt(this.params.page) || 1;  
    },
    subscriptions: function() {
        var skipCount = (this.currentPage() - 1) * this.recordsPerPage;
        this.customersSub = Meteor.subscribe('customers', 
            skipCount, CustomerSortSettings.sortField(), CustomerSortSettings.sortDirection());  
    },  
    customers: function() {
        var sortParams = {};
        sortParams[CustomerSortSettings.sortField()] = CustomerSortSettings.sortDirection();
        return Customers.find({}, { sort: sortParams }); 
    },
    prevPageLink: function() {
        var prevPage = this.currentPage() === 1 ? 1 : this.currentPage() - 1;
        return Router.routes.customers.path({page: prevPage});
    },
    nextPageLink: function() {
        var totalCustomers = Counts.get('customerCount');
        var nextPage = this.currentPage() * this.recordsPerPage < totalCustomers ?
            this.currentPage() + 1 : this.currentPage();
        return Router.routes.customers.path({page: nextPage});
    },
    data: function() {
        return {      
            customers: this.customers(),      
            ready: this.customersSub.ready,
            prevPage: this.prevPageLink(),
            nextPage: this.nextPageLink()
        };  
    }
});

Putting a bunch of logic in the route feels wrong to me, but maybe this is just because I come from a more traditional MVC background where route code tends to be pretty sparse.

The other option is to put a lot of this code in a template helper

var currentPage = function() {     
    return parseInt(Router.current().params.page) || 1;  
}

Template.customers.helpers({
  prevPage: function() {
      var pp = currentPage() === 1 ? 1 : currentPage() - 1;
        return Router.routes.customers.path({page: pp});
  },
  nextPage: function() {
        var totalCustomers = Counts.get('customerCount');
        var nextPage = currentPage() * parseInt(Meteor.settings.public.recordsPerPage) 
            < totalCustomers ? currentPage() + 1 : currentPage();
        return Router.routes.customers.path({page: nextPage});
    },
  customers: function() {
        var sortParams = {};
        sortParams[CustomerSortSettings.sortField()] = CustomerSortSettings.sortDirection();
        return Customers.find({}, { sort: sortParams }); 
    },
});

Any thoughts on the best approach?


#2

We put our logic in the Templates.

Templates run on the Client, so it reduces the stress on the server to offload the logic. Plus, our Router is a nexus for the application so we try to keep it very thin.


#3

I’ve started splitting up my router code and putting related Routes into the same file as the template code recently. It’s just a way to organize things, not functionally different but I like it because all the top-level logic for one “page” is in one place (assuming nothing too unwieldy).
Then you can easily add separate functions to handle any more complex logic and move between helpers or routers. It feels fugly to insert that inside the router itself, agreed.


#4

I have tried the two approaches and finally concluded that putting the logic in the template is better for me.

I think MDG is somehow on the same wavelength, as I recall them writing that putting subscriptions as close as possible to where they are used is better (see also the recent addition of Template.subscriptionsReady).

However, there are strong advocates of putting the logic in routes. @arunoda might want to comment here.


#5

This is where I think putting subscriptions is a good idea inside the router.

  • Normally subscriptions are aligned with routes
  • Then it’s a good idea to declare them with routes and we can easily see what we are subscribing (No need go though a set of files to see what we are doing)
  • When we put subscriptions in the router, we can decide on the server which kind of data we need to send to the client (To works with Fast Render)

But, none of that above is not a concern for you, go with template level subs.

Anyway, I strongly disagree doing rendering on the client. I suggest to use a simple router like Flow Router.


#6

I just read the flow-router documentation and like it for its simplicity. I think Iron-router is great, but its control flow is complex and I have a hard time debugging route issues…

@arunoda Is Flow Router & Iron-Router provide complimentary functionalities.
Are there cases where we should use one vs. the other?
If I have an existing application using Iron-Router, can I use flow router for only certain routes?

Thanks
-S


#7

Iron Router is great and we used it for a lot of apps.

Flow Routes comes with a lot of things that we learned when building apps with blaze. I don’t think both routers can be used at once.

There is a lengthy section comparing the difference between Iron Router and Flow Router.


#8

Definitely, Subscriptions belong with Routes.


#9

For traditional web sites (with traditional pages) I agree, but what if you, for example, want infinite scrolling (new docs are loaded as you scroll down)? In cases as these, I imagine it would be easier to let templates handle (parts of) the subscriptions. Of course you can solve it in a routing way, but that would require more work, and you may not want the user to be able to bookmark the page. For example, when you search for an image on Google, you can’t bookmark which images you’re viewing at the moment.


#10

I prefer pulling as much as I can into the Route Controllers.

I think of them less as “Routes” and more as “Controllers” in the traditional MVC / Rails (I think) / MVC-Web-Framework - Controller - Fashion.

By that I mean the Controller “controls”, and by that decides on the logic on what gets rendered etc. Also it’s possible to define Template helpers on the Route Controllers which are then accessible from all templates rendered by the Controller. As I think about it right now, I like to let my Controllers to decide what to show, the event handling, navigation, form building, method calls etc I then do on the template level.

That way functionality isn’t tied to a specific template (which might create extra work when refactoring / renaming templates) and doesn’t need to be duplicated across templates while at the same time. And in contrast to Template.registerHelper(), it’s only for the specific templates rendered by the controller and its child templates.

So that’s perfect DRY (don’t repeat yourself) right there, while I also always know where to find the different helpers and the logic for the route / “page”.

Also it’s possible to inherit route controllers from one another, which makes for additional code reuse (shared template helpers, as an example).

I can recommend the EventedMind - Tutorial for the ironRouter-package: https://www.eventedmind.com/classes/using-iron-router to get a good overview about the possibilities.

With the new meteor 1.0.4 - release there’s template level subscription goodness coming, and that’s great, I’m excited to see how that’ll work out.

I hope to investigate a more view/component based model (i also still need to look into web components) of structuring apps.

But I think I like using Route Controllers to decide what to show. Eg, subscribing to the appropriate “base” - collections, preparing the data for the templates and also providing common helpers.

Actually I also like to use the pattern to put a lot of the common helpers into classes and to let meteor make the objects coming from .find() and .findOne into an object of the class using the Collections’ “transform” - function.

This allows me to write nice getters and other helpers which allow me to do much more from the templates (blaze is actually quite clever calling functions and evaluating params in a dotted.object.notation.chain).

So I can do things like this in my templates:

{{#each customers}}
Name: {{name}}
Profile URL: {{getProfileUrl}}
BestFriend: {{getBestFriend}}
BestFriends’ Profile URL: {{getBestFriend.getProfileUrl}}
AllFriends:
{{#each getFriends}}
Hi {{name!}}
Profile URL: {{getProfileUrl}}
{{/each}}
{{/each}}

NICE! :smiley:

Coffeescript is a real help in this regard though, at least for me.

Stuff like nextPageLink and prevPageLink would probably go to the template though, at least until I had a similar “pattern” of links for different pages, and I then would maybe think about coming up with a good structure for them, but I didn’t have that particular case yet.

Hope that wasn’t too much of a ramble, but I need to get back to work! Seeya!


#11

There are exceptions to /almost/ every rule, for sure.

Personally, I find infinity scrolling to be one of the more annoying edge cases for the entire stack. Its gross and I have never seen an implementation that makes me happy.

Sure, simple straight lists are easy enough, but as soon as your data becomes more complicated or your sorting becomes variable things go south fast