Meteor - Routing - FlowRouter

meteor-routing

https://github.com/meteorhacks/fast-render
https://github.com/kadirahq/flow-router - done reading
https://kadira.io/academy/meteor-routing-guide
https://kadira.io/academy/meteor-routing-guide/content/introduction-to-flow-router
https://atmospherejs.com/kadira/flow-router
https://themeteorchef.com/snippets/client-side-routing-with-flow-router/
https://meteorhacks.com/flow-router-and-subscription-management/ - done reading
https://blog.tableflip.io/flow-router-some-useful-patterns/
https://medium.com/@satyavh/using-flow-router-for-authentication-ba7bb2644f42#.cqqtxgc5n
http://massimilianomarini.com/flowrouter-and-flowlayout-101/
https://atmospherejs.com/useraccounts/flow-routing
http://meteorcapture.com/flow-router-examples-with-blaze-and-react/
https://kadira.io/academy/meteor-routing-guide/content/subscriptions-and-data-management

What are the features offered by FlowRouter?

  1. FlowRouter is a very simple router for Meteor. It does routing for client-side apps and does not handle rendering itself. It exposes a great API for changing the URL and reactively getting data from the URL. However, inside the router, it's not reactive. FlowRouter is designed with performance in mind and it focuses on what it does best: routing

How can we use FlowRouter?

meteor add kadira:flow-router

FlowRouter.route('/home', {
  action(params, queryParams) {
    jQuery('.mainContent').hide();
    jQuery('#mainContent1').show();
  }           
});         

FlowRouter.route('/tos', {
  action(params, queryParams) {
    jQuery('.mainContent').hide();
    jQuery('#termAndCondition').show();
  }         
});           
FlowRouter.route('/', {
  action(params, queryParams) {
    jQuery('.mainContent').hide();
    jQuery('#mainContent1').show();
  }
});

FlowRouter seems to have problem with #. If our links are like /home, /faq, etc, we just need to code our links like that, and we should be ok. We do not need to set up an event and use FlowRouter.go('…');

How can we define a simple route using FlowRouter?

FlowRouter.route('/lists/:_id', {
  name: 'Lists.show',
  action(params, queryParams) {
  }
});

When the route is matched, the action method executes, and you can perform any actions you need to. The name property of the route is optional, but will let us refer to this route more conveniently later on.

FlowRouter.route('/blog/:postId', {
    // do some action for this route
    action: function(params, queryParams) {
        console.log("Params:", params);
        console.log("Query Params:", queryParams);
    },

    name: "<name for the route>" // optional
});

The above route will be activated when you visit a url like FlowRouter.go('/blog/my-post?comments=on&color=dark'). After you've visit the route, this will be printed in the console:

Params: {postId: "my-post"}
Query Params: {comments: "on", color: "dark"}

What happens when a route is activated?

After you've visit a route, first it will call triggers, then subscriptions and finally action. After that happens, none of those methods will be called again for that route visit.

Why should we keep the route information in the lib directory?

You can define routes anywhere in the client directory. But, we recommend to add them in the lib directory. Then fast-render can detect subscriptions and send them for you.

How can we give a route a name?

FlowRouter.route('/lists/:_id', {
  name: 'Lists.show',
  action(params, queryParams) {
  }
});

How can we define a parameterized route?

Consider the following URL pattern, used in the code snippet above:

FlowRouter.route('/lists/:_id', {
  name: 'Lists.show',
  action(params, queryParams) {
  }
});

Notice that one segment of the URL is prefixed by _. This means that it is a url parameter, and will match any string that is present in that segment of the path. Flow Router will make that part of the URL available on the params property of the current route.

The URL could contain an HTTP query string (the part after an optional ?). Flow Router will split it up into named parameters, which it calls queryParams.

The values in params and queryParams are always strings because FlowRouter don’t have any way of encoding data types. For example, if you wanted a parameter to represent a number, you might need to use parseInt(value, 10) to convert it when you access it.

How can we programmatically go to a parameterized route?

FlowRouter.go('/deck/:_id', {_id: result});

How can we access route information?

In addition to passing in the parameters as arguments to the action function on the route, Flow Router makes a variety of information available via (reactive and otherwise) functions on the global singleton FlowRouter. As the user navigates around your app, the values of these functions will change (reactively in some cases) correspondingly.

Like any other global singleton in your application (see the data loading for info about stores), it’s best to limit your access to FlowRouter. That way the parts of your app will remain modular and more independent. In the case of FlowRouter, it’s best to access it solely from the top of your component hierarchy, either in the “page” component, or the layouts that wrap it. Read more about accessing data in the UI article.

It’s useful to access information about the current route in your code. Here are some reactive functions you can call:

  1. FlowRouter.getRouteName() gets the name of the route
  2. FlowRouter.getParam(paramName) returns the value of a single URL parameter
  3. FlowRouter.getQueryParam(paramName) returns the value of a single URL query parameter

In our example of the list page from the Todos app, we access the current list’s id with FlowRouter.getParam('_id') (we’ll see more on this below).

How can we group routes for better organization?

var adminRoutes = FlowRouter.group({
  prefix: '/admin',
  name: 'admin',
  triggersEnter: [function(context, redirect) {
    console.log('running group triggers');
  }]
});

// handling /admin route
adminRoutes.route('/', {
  action: function() {
    BlazeLayout.render('componentLayout', {content: 'admin'});
  },
  triggersEnter: [function(context, redirect) {
    console.log('running /admin trigger');
  }]
});

// handling /admin/posts
adminRoutes.route('/posts', {
  action: function() {
    BlazeLayout.render('componentLayout', {content: 'posts'});
  }
});

In the above code, we define the adminRoutes variables using the FlowRouter.group method. Notice that the group has a name, a prefix, and the triggersEnter array of callbacks. We then use the adminRoutes variable to define individual routes (/admin and /admin/posts).

How can we define nested groups of routes?

You can even have nested group routes as shown below:

var adminRoutes = FlowRouter.group({
    prefix: "/admin",
    name: "admin"
});

var superAdminRoutes = adminRoutes.group({
    prefix: "/super",
    name: "superadmin"
});

// handling /admin/super/post
superAdminRoutes.route('/post', {
    action: function() {

    }
});

In the above code, we define the variable adminRoutes using the FlowRouter.group method. We then use adminRoutes.group to define the sub-group superAdminRoutes.

How can we determine the name of the group that the current route is in?

FlowRouter.current().route.group.name

This can be useful for determining if the current route is in a specific group (e.g. admin, public, loggedIn) without needing to use prefixes if you don't want to. If it's a nested group, you can get the parent group's name with:

FlowRouter.current().route.group.parent.name

Are current route properties reactive?

No. Some parts of FlowRouter are reactive. However, current route properties are not reactive, but can be combined with FlowRouter.watchPathChange() to get group names reactively.

Does FlowRouter handle rendering or layout management?

No. FlowRouter does not handle rendering or layout management. For that, we can use:

  1. Blaze Layout for Blaze
  2. React Layout for React

Then you can invoke the layout manager inside the action method in the router:

FlowRouter.route('/blog/:postId', {
    action: function(params) {
        BlazeLayout.render("mainLayout", {area: "blog"});
    }
});

What are triggers in FlowRouter?

Triggers are the way FlowRouter allows you to perform tasks before you enter into a route and after you exit from a route. Here's how you can define triggers for a route:

FlowRouter.route('/home', {
  // calls just before the action
  triggersEnter: [trackRouteEntry],
  action: function() {
    // do something you like
  },
  // calls when when we decide to move to another route
  // but calls before the next route started
  triggersExit: [trackRouteClose]
});

function trackRouteEntry(context) {
  // context is the output of `FlowRouter.current()`
  Mixpanel.track("visit-to-home", context.queryParams);
}

function trackRouteClose(context) {
  Mixpanel.track("move-from-home", context.queryParams);
}

Can we define triggers for a group of routes?

var adminRoutes = FlowRouter.group({
  prefix: '/admin',
  triggersEnter: [trackRouteEntry],
  triggersExit: [trackRouteEntry]
});
function trackRouteEntry(context) {
  // context is the output of `FlowRouter.current()`
  Mixpanel.track("visit-to-home", context.queryParams);
}

function trackRouteClose(context) {
  Mixpanel.track("move-from-home", context.queryParams);
}

How can we define triggers globally?

FlowRouter.triggers.enter([cb1, cb2]);
FlowRouter.triggers.exit([cb1, cb2]);

// filtering
FlowRouter.triggers.enter([trackRouteEntry], {only: ["home"]});
FlowRouter.triggers.exit([trackRouteExit], {except: ["home"]});

As you can see from the last two examples, you can filter routes using the only or except keywords. But, you can't use both only and except at once. If you'd like to learn more about triggers and design decisions, visit here.

How can we redirect with triggers?

You can redirect to a different route using triggers. You can do it from both enter and exit triggers.

FlowRouter.route('/', {
  triggersEnter: [function(context, redirect) {
    redirect('/some-other-path');
  }],
  action: function(_params) {
    throw new Error("this should not get called");
  }
});

Every trigger callback comes with a second argument: a function you can use to redirect to a different route. Redirect also has few properties to make sure it's not blocking the router.

  1. redirect must be called with an URL
  2. redirect must be called within the same event loop cycle (no async or called inside a Tracker)
  3. redirect cannot be called multiple times

Check this PR to learn more about our redirect API.

How can we stop callbacks with triggers?

In some cases, you may need to stop the route callback from firing using triggers. You can do this in before triggers, using the third argument: the stop function. For example, you can check the prefix and if it fails, show the notFound layout and stop before the action fires.

var localeGroup = FlowRouter.group({
  prefix: '/:locale?',
  triggersEnter: [localeCheck]
});

localeGroup.route('/login', {
  action: function (params, queryParams) {
    BlazeLayout.render('componentLayout', {content: 'login'});
  }
});

function localeCheck(context, redirect, stop) {
  var locale = context.params.locale;

  if (locale !== undefined && locale !== 'fr') {
    BlazeLayout.render('notFound');
    stop();
  }
}

In the above code, localeCheck is used as a trigger. It is given the third parameter, stop. On certain condition, stop is invoked. This probably stop the action part as well.

Is the FlowRouter.getParam(paramName) method reactive?

Yes. It is a reactive function which you can use to get a parameter from the URL.

// route def: /apps/:appId
// url: /apps/this-is-my-app

var appId = FlowRouter.getParam("appId");
console.log(appId); // prints "this-is-my-app"

Is the FlowRouter.getQueryParam(queryStringKey) method reactive?

Yes. It is a reactive function which you can use to get a value from the queryString.

// route def: /apps/:appId
// url: /apps/this-is-my-app?show=yes&color=red

var color = FlowRouter.getQueryParam("color");
console.log(color); // prints "red"

How can we generate a URL given a path definition, parameters, and query parameters?

Use FlowRouter.path(pathDef, params, queryParams). The path method generate a path from a path definition. Both params and queryParams are optional. Special characters in params and queryParams will be URL encoded.

var pathDef = "/blog/:cat/:id";
var params = {cat: "met eor", id: "abc"};
var queryParams = {show: "y+e=s", color: "black"};

var path = FlowRouter.path(pathDef, params, queryParams);
console.log(path); // prints "/blog/met%20eor/abc?show=y%2Be%3Ds&color=black"

If there are no params or queryParams, this will simply return the pathDef as it is.

How can we use named route instead of pathDef with the path method?

FlowRouter.route("/blog/:cat/:id", {
    name: "blogPostRoute",
    action: function(params) {
        //...
    }
})

var params = {cat: "meteor", id: "abc"};
var queryParams = {show: "yes", color: "black"};

var path = FlowRouter.path("blogPostRoute", params, queryParams);
console.log(path); // prints "/blog/meteor/abc?show=yes&color=black"

How can we tell FlowRouter to go to another route?

FlowRouter.go(pathDef, params, queryParams);

This will get the path via FlowRouter.path based on the arguments and re-route to that path. You can call FlowRouter.go like this as well:

FlowRouter.go("/blog");

What is the difference between FlowRouter.path and FlowRouter.url?

FlowRouter.url(pathDef, params, queryParams)

ust like FlowRouter.path, but gives the absolute url. (Uses Meteor.absoluteUrl behind the scenes.)

What does the FlowRouter.setParams method do?

FlowRouter.setParams(newParams)

This will change the current params with the newParams and re-route to the new path.

// route def: /apps/:appId
// url: /apps/this-is-my-app?show=yes&color=red

FlowRouter.setParams({appId: "new-id"});
// Then the user will be redirected to the following path
//      /apps/new-id?show=yes&color=red

What does the FlowRouter.setQueryParams method do?

FlowRouter.setQueryParams(newQueryParams)

Just like FlowRouter.setParams, but for queryString params. To remove a query param set it to null like below:

FlowRouter.setQueryParams({paramToRemove: null});

What is the purpose of the FlowRouter.getRouteName method?

Get the name of the current route.

How can we obtain the name of the current route reactively?

Tracker.autorun(function() {
  var routeName = FlowRouter.getRouteName();
  console.log("Current route name is: ", routeName);
});

How can we use the FlowRouter.watchPathChange method?

The FlowRouter.current method get the current state of the router. This API is not reactive. If you need to watch the changes in the path simply use FlowRouter.watchPathChange(). The FlowRouter.watchPathChange method reactively watch the changes in the path. If you need to simply get the params or queryParams use dedicated APIs like FlowRouter.getQueryParam().

Tracker.autorun(function() {
  FlowRouter.watchPathChange();
  var currentContext = FlowRouter.current();
  // do anything with the current context
  // or anything you wish
});

What is the purpose of the FlowRouter.withReplaceState(fn) method?

Normally, all the route changes made via APIs like FlowRouter.go and FlowRouter.setParams() add a URL item to the browser history. For example, run the following code:

FlowRouter.setParams({id: "the-id-1"});
FlowRouter.setParams({id: "the-id-2"});
FlowRouter.setParams({id: "the-id-3"});

Now you can hit the back button of your browser two times. This is normal behavior since users may click the back button and expect to see the previous state of the app. But sometimes, this is not something you want. You don't need to pollute the browser history. Then, you can use the following syntax.

FlowRouter.withReplaceState(function() {
  FlowRouter.setParams({id: "the-id-1"});
  FlowRouter.setParams({id: "the-id-2"});
  FlowRouter.setParams({id: "the-id-3"});
});

Now, there is no item in the browser history. Just like FlowRouter.setParams, you can use any FlowRouter API inside FlowRouter.withReplaceState. We named this function as withReplaceState because, replaceState is the underline API used for this functionality. Read more about replace state & the history API.

What is the purpose of FlowRouter.reload method?

FlowRouter routes are idempotent. That means, even if you call FlowRouter.go() to the same URL multiple times, it only activates in the first run. This is also true for directly clicking on paths. So, if you really need to use FlowRouter.reload method to reload the route.

What is the purpose of the FlowRouter.wait() and FlowRouter.initialize() methods?

By default, FlowRouter initializes the routing process in a Meteor.startup() callback. This works for most of the apps. But, some apps have custom initializations and FlowRouter needs to initialize after that. So, that's where FlowRouter.wait() comes to save you. You need to call it directly inside your JavaScript file. After that, whenever your app is ready call FlowRouter.initialize().

// file: app.js
FlowRouter.wait();
WhenEverYourAppIsReady(function() {
  FlowRouter.initialize();
});

For more information visit issue #180.

How can we highlight the active route?

One situation where it is sensible to access the global FlowRouter singleton to access the current route’s information deeper in the component hierarchy is when rendering links via a navigation component. It’s often required to highlight the “active” route in some way (this is the route or section of the site that the user is currently looking at). A convenient package for this is zimme:active-route:

meteor add zimme:active-route

In the Todos example app, we link to each list the user knows about in the App_body template:

{{#each list in lists}}
  <a class="list-todo {{activeListClass list}}">
    ...

    {{list.name}}
  </a>
{{/each}}

We can determine if the user is currently viewing the list with the activeListClass helper:

Template.App_body.helpers({
  activeListClass(list) {
    const active = ActiveRoute.name('Lists.show')
      && FlowRouter.getParam('_id') === list._id;

    return active && 'active';
  }
});

How can we render based on route?

Now we understand how to define routes and access information about the current route, we are in a position to do what you usually want to do when a user accesses a route—render a user interface to the screen that represents it. In this section, we’ll discuss how to render routes using Blaze as the UI engine. If you are building your app with React or Angular, you will end up with similar concepts but the code will be a bit different.

When using Flow Router, the simplest way to display different views on the page for different URLs is to use the complementary Blaze Layout package. First, make sure you have the Blaze Layout package installed:

meteor add kadira:blaze-layout

To use this package, we need to define a “layout” component. In the Todos example app, that component is called App_body:

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

(This is not the entire App_body component, but we highlight the most important part here). Here, we are using a Blaze feature called Template.dynamic to render a template which is attached to the main property of the data context. Using Blaze Layout, we can change that main property when a route is accessed.

We do that in the action function of our Lists.show route definition:

FlowRouter.route('/lists/:_id', {
  name: 'Lists.show',
  action() {
    BlazeLayout.render('App_body', {main: 'Lists_show_page'});
  }
});

What this means is that whenever a user visits a URL of the form /lists/X, the Lists.show route will kick in, triggering the BlazeLayout call to set the main property of the App_body component.

How can we change route programmatically?

In some cases you want to change routes based on user action outside of them clicking on a link. For instance, in the example app, when a user creates a new list, we want to route them to the list they just created. We do this by calling FlowRouter.go() once we know the id of the new list:

import { insert } from '../../api/lists/methods.js';

Template.App_body.events({
  'click .js-new-list'() {
    const listId = insert.call();
    FlowRouter.go('Lists.show', { _id: listId });
  }
});

You can also change only part of the URL if you want to, using the FlowRouter.setParams() and FlowRouter.setQueryParams(). For instance, if we were viewing one list and wanted to go to another, we could write:

FlowRouter.setParams({_id: newList._id});

Of course, calling FlowRouter.go(), will always work, so unless you are trying to optimize for a specific situation it’s better to use that.

How can we store data in URLs?

The URL is really just a serialization of some part of the client-side state the user is looking at. Although parameters can only be strings, it’s possible to convert any type of data to a string by serializing it. In general if you want to store arbitrary serializable data in a URL param, you can use EJSON.stringify() to turn it into a string. You’ll need to URL-encode the string using encodeURIComponent to remove any characters that have meaning in a URL:

FlowRouter.setQueryParams({data: encodeURIComponent(EJSON.stringify(data))});

You can then get the data back out of Flow Router using EJSON.parse(). Note that Flow Router does the URL decoding for you automatically:

const data = EJSON.parse(FlowRouter.getQueryParam('data'));

How can we redirect the user to another page?

Usually, we can redirect in response to a user’s action by calling FlowRouter.go() and friends, like in our list creation example above, but if a user browses directly to a URL that doesn’t exist, it’s useful to know how to redirect immediately.

If a URL is simply out-of-date (sometimes you might change the URL scheme of an application), you can redirect inside the action function of the route:

FlowRouter.route('/old-list-route/:_id', {
  action(params) {
    FlowRouter.go('Lists.show', params);
  }
});

The above approach will only work for static redirects. However, sometimes you need to load some data to figure out where to redirect to. In this case you’ll need to render part of the component hierarchy to subscribe to the data you need. For example, in the Todos example app, we want to make the root (/) route redirect to the first known list. To achieve this, we need to render a special App_rootRedirector route:

FlowRouter.route('/', {
  name: 'App.home',
  action() {
    BlazeLayout.render('App_body', {main: 'App_rootRedirector'});
  }
});

The App_rootRedirector component is rendered inside the App_body layout, which takes care of subscribing to the set of lists the user knows about before rendering its sub-component, and we are guaranteed there is at least one such list. This means that if the App_rootRedirector ends up being created, there’ll be a list loaded, so we can simply do:

Template.App_rootRedirector.onCreated(function rootRedirectorOnCreated() {
  // We need to set a timeout here so that we don't redirect from inside a redirection
  //   which is a limitation of the current version of FR.
  Meteor.setTimeout(() => {
    FlowRouter.go('Lists.show', Lists.findOne());
  });
});

If you need to wait on specific data that you aren’t already subscribed to at creation time, you can use an autorun and subscriptionsReady() to wait on that subscription:

Template.App_rootRedirector.onCreated(function rootRedirectorOnCreated() {
  // If we needed to open this subscription here
  this.subscribe('lists.public');

  // Now we need to wait for the above subscription. We'll need the template to
  // render some kind of loading state while we wait, too.
  this.autorun(() => {
    if (this.subscriptionsReady()) {
      FlowRouter.go('Lists.show', Lists.findOne());
    }
  });
});

How can we do redirect after user action?

Often, you just want to go to a new route programmatically when a user has completed a certain action. Above we saw a case (creating a new list) when we wanted to do it optimistically—i.e. before we hear back from the server that the Method succeeded. We can do this because we reasonably expect that the Method will succeed in almost all cases. However, if we wanted to wait for the method to return from the server, we can put the redirection in the callback of the method:

Template.App_body.events({
  'click .js-new-list'() {
    lists.insert.call((err, listId) => {
      if (!err) {
        FlowRouter.go('Lists.show', { _id: listId });  
      }
    });
  }
});

You will also want to show some kind of indication that the method is working in between their click of the button and the redirect completing. Don’t forget to provide feedback if the method is returning an error.

How can we deal with the page not found issue?

If a user types an incorrect URL, chances are you want to show them some kind of amusing not-found page. There are actually two categories of not-found pages. The first is when the URL typed in doesn’t match any of your route definitions. You can use FlowRouter.notFound to handle this:

// the App_notFound template is used for unknown routes and missing lists
FlowRouter.notFound = {
  action() {
    BlazeLayout.render('App_body', {main: 'App_notFound'});
  }
};

The second is when the URL is valid, but doesn’t actually match any data. In this case, the URL matches a route, but once the route has successfully subscribed, it discovers there is no data. It usually makes sense in this case for the page component (which subscribes and fetches the data) to render a not-found template instead of the usual template for the page:

<template name="Lists_show_page">
  {{#each listId in listIdArray}}
    {{> Lists_show (listArgs listId)}}
  {{else}}
    {{> App_notFound}}
  {{/each}}
<template>

How can we generate URLs?

You can generate the URLs yourself using FlowRouter.pathFor, but it is more convenient to use the arillo:flow-router-helpers package that defines some helpers for you:

meteor add arillo:flow-router-helpers

Now that you have this package, you can use helpers in your templates to display a link to a certain route. For example, in the Todos example app, our nav links look like:

<a href="{{pathFor 'Lists.show' _id=list._id}}" title="{{list.name}}"
    class="list-todo {{activeListClass list}}">

Why did the people at Kadira create FlowRouter?

There were a lot of re-renders in the UI and we had no control over them. Iron Router waitOn gives little control over page loading patterns. Everything is based around the router and tightly coupled to it (especially rendering). FlowRouter is a minimalistic router that uses page.js behind the scenes. Here are some of the characteristics of Flow Router:

  1. It does routing and has a reactive API.
  2. It registers subscriptions but does not wait for them.
  3. It does not re-run a route based on reactive changes.

It doesn’t do rendering at all. We decoupled rendering from the router so it can be used with any kind of rendering framework. So, Flow Router can work with Blaze, React, Famous or any other rendering framework. For an example, you can use Flow Layout for rendering Blaze templates or use React Meteor for rendering React components.

As a community we’ve built a lot of apps with Iron Router and those apps are tightly coupled with it. Iron Router tries to do everything from routing, subscriptions and to rendering. I’m not going to say whether that’s a good or bad thing.

In contrast, Flow Router only tries to do a few things and APIs are designed with UI performance in mind. With Flow Router, you can predict what’s going to happen next. We made some major decisions when designing it. Here they are:

  1. You can’t use reactive content inside the router.
  2. When a user visits a route, the route’s action and subscriptions methods are invoked. But they are never re-run for any reason unless the user changes the route.
  3. FlowRouter.current() is not reactive. We introduced a new set of APIs as substitutes. See why.
  4. Flow Router registers only subscriptions
  5. There is no concept like waitOn.
  6. It doesn’t do server-side routing.

What are the characteristics of FlowRouter?

Here are some of the characteristics of Flow Router:

  1. It does routing and has a reactive API.
  2. It registers subscriptions but does not wait for them.
  3. It does not re-run a route based on reactive changes.

It doesn’t do rendering at all. We decoupled rendering from the router so it can be used with any kind of rendering framework. So, Flow Router can work with Blaze, React, Famous or any other rendering framework. For an example, you can use Flow Layout for rendering Blaze templates or use React Meteor for rendering React components.

Does FlowRouter do server-side routing?

No.

Can we use route-level subscription with template-level subscription?

Yes. Even though subscription’s state will be used in templates, we can define subscriptions in either inside routes or inside templates. We can use both depending on the use case. https://meteorhacks.com/flow-router-and-subscription-management/

How can we implement route-level subscription?

FlowRouter.route('/blog/:pageId', {
  subscriptions: function(params) {
    this.register('blogCategories', Meteor.subscribe('categories'));
    this.register('currentPost', Meteor.subscribe('post', params.pageId));
    this.register('currentComments', Meteor.subscribe('comments', params.pageId));
  },

  action: function() {
    // We render the template with Flow Layout
    FlowLayout.render('page');
  }
});
9Er9is4L90.png

Here’s the isReady template helper:

Template.page.helpers({
  isReady: function(sub) {
    if(sub) {
      return FlowRouter.subsReady(sub);
    } else {
      return FlowRouter.subsReady();
    }
  }
});

Does FlowRouter support Fast Render?

Flow Router supports Fast Render by default. Simply add Fast Render to your app and you’ll get the initial data for subscriptions you’ve registered. That’s another reason we should define subscriptions in the router.

When should we implement template-level subscription instead of route-level subscription?

Now let’s look at cases where the route will not change after a user action. Here are some examples:

  1. Chat solutions
  2. Infinite scrolling (like in Facebook)
  3. Charts and Gauges (like in Kadira)
  4. Data-arranging apps (like Trello)

For these cases, template-level subscriptions can be used to define subscriptions and manage them. This is how you can use template-level subscription for a chat solution:

Template.chatWidget.onCreated(function () {
  var self = this;
  self.subscribe("recentChatMessages");
});

You can render the chatApp template like this:

QaOwGlbKCa.png

If you follow template-level subscription approach, Fast Render can’t detect subscriptions since they are inside templates.

What is the purpose of the FlowRouter.onRouteRegister(cb) method?

This API is specially designed for add-on developers. They can listen for any registered route and add custom functionality to FlowRouter. This works on both server and client alike.

FlowRouter.onRouteRegister(function(route) {
  // do anything with the route object
  console.log(route);
});

Let's say a user defined a route like this:

FlowRouter.route('/blog/:post', {
  name: 'postList',
  triggersEnter: [function() {}],
  subscriptions: function() {},
  action: function() {},
  triggersExit: [function() {}],
  customField: 'customName'
});

Then the route object will be something like this:

{
  pathDef: '/blog/:post',
  name: 'postList',
  options: {customField: 'customName'}
}

So, it's not the internal route object that FlowRouter uses.

Does FlowRouter support Hashbang URLs?

Yes. To enable hashbang urls like mydomain.com/#!/mypath simple set the hashbang option to true in the initialize function:

// file: app.js
FlowRouter.wait();
WhenEverYourAppIsReady(function() {
  FlowRouter.initialize({hashbang: true});
});

How can we convert a package to support FlowRouter?

Router is a base package for an app. Other projects like useraccounts should have support for FlowRouter. Otherwise, it's hard to use FlowRouter in a real project. Now a lot of packages have started to support FlowRouter. So, you can use your your favorite package with FlowRouter as well. If not, there is an easy process to convert them to FlowRouter. https://github.com/kadirahq/flow-router

Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License