Router Component

Router Component is a special type of content that can be loaded by Router when we specify route content using component or componentUrl properties.

It should help to better structure our apps, keep things in appropriate place, and make many things quicker and in a more clear and comfortable way.

Component Structure

If you know what is Vue component, then it will be much easier to understand as it looks pretty similar. Router Component is basically an object with the following properties (all properties are optional):

Property Type Description
template string Template7 template string. Will be compiled as Template7 template
render function Render function to render component. Must return full html string or HTMLElement
data function Component data, function must return component context data
style string Component CSS styles. Styles will be added to the document after component will be mounted (added to DOM), and removed after component will be destroyed (removed from the DOM)
methods object Object with additional component methods which extend component context
on object Object with page events handlers
Lifecycle Hooks
beforeCreate function Called synchronously immediately after the component has been initialized, before data and event/watcher setup.
created function Called synchronously after the component is created, context data and methods are available and component element $el is also created and available
beforeMount function Called right before component will be added to DOM
mounted function Called right after component was be added to DOM
beforeDestroy function Called right before component will be destoyed
destroyed function Called when component destroyed

All lifecycle hooks and methods automatically have their this context bound to the component context, so that you can access component data and methods. This means you should not use an arrow function to define a lifecycle method (e.g. created: () => this.doSomething()). The reason is arrow functions bind the parent context, so this will not be the component instance as you expect and this.doSomething will be undefined.

So the example route with page component may look like:

routes = [
  // ...
  {
    path: '/some-page/',
    // Component Object
    component: {
      template: `
        <div class="page">
          <div class="navbar">
            <div class="navbar-inner">
              <div class="title">{{title}}</div>
            </div>
          </div>
          <div class="page-content">
            <a @click="openAlert" class="red-link">Open Alert</a>
            <div class="list simple-list">
              <ul>
                {{#each names}}
                  <li>{{this}}</li>
                {{/each}}
              </ul>
            </div>
          </div>
        </div>
      `,
      style: `
        .red-link {
          color: red;
        }
      `,
      data: function () {
        return {
          title: 'Component Page',
          names: ['John', 'Vladimir', 'Timo'],
        }
      },
      methods: {
        openAlert: function () {
          var self = this;
          self.$app.dialog.alert('Hello world!');
        },
      },
      on: {
        pageInit: function (e, page) {
          // do something on page init
        },
        pageAfterOut: function (e, page) {
          // page has left the view
        },
      }
    },
  },
  // ...
]

Note that in component additional @ attribute is supported in component template. It is a shorthand method to assign event listener to the specified element. Specified event handler will be searched in component methods

Component Context

As we said above, all component methods and Template7 compiler are executed in the context of the component.

Component context is the object you have returned in component's data and methods from specified methods object, but also extended with the following useful properties:

Property Type Desctiption
$el object Dom7 instance with component HTML element
this.$el.find('p').addClass('red')
$
$$
$dom7
function Dom7 library:
this.$$('p').text('hello world')
$app object Framework7 app instance
this.$app.dialog.alert('Hello world!')
$root object Root data and methods you have specified in data and methods properties on app init
var app = new Framework7({
  // root data
  data: function () {
    return {
      username: 'johndoe'
    }
  },
  // root methods
  methods: {
    helloWorld: function () {
      app.dialog.alert('Hello world!');
    }
  }
});

// then in component:
console.log(this.$root.username); // -> 'johndoe';

this.$root.helloWorld(); // -> call alert
$route object Current route. Contains object with route query, hash, params, path and url
$router Router instance Related router instance
this.$router.back(); //navigate back
$theme object Object with md and ios boolean properties which indicating current theme. For example:
if (this.$theme.ios) { /* do something when iOS theme is active */ }
if (this.$theme.md) { /* do something when MD theme is active */ }

Component Page Events

Component page events handlers can be passed in on component property. They are usual DOM Page Events. Because they are DOM events, they accept event as first agrument, and Page Data as second argument. There only difference with usual DOM events is that their context (this) bound to component context and event handler name must be specified in camelCase format:

...
data: function () {
  return {
    username: 'johndoe',
  };
},
on: {
  pageMounted: function (e, page) {
    console.log('page mounted');
  },
  pageInit: function (e, page) {
    console.log(this.username); // -> 'johndoe'
  },
  pageBeforeIn: function (e, page) {
    console.log('page before in');
  },
  pageAfterIn: function (e, page) {
    console.log('page after in');
  },
  pageBeforeOut: function (e, page) {
    console.log('page before out');
  },
  pageAfterOut: function (e, page) {
    console.log('page after out');
  },
  pageBeforeRemove: function (e, page) {
    console.log('page before remove');
  },
}

Single File Component

It is not very comfortable to specify all component routes under same routes array, especially if we have a lot of such routes. This is why we can use componentUrl instead and out component into single file:

routes = [
  ...
  {
    path: '/some-page/',
    componentUrl: './some-page.html',
  },
  ..
];

And in some-page.html:

<!-- component template -->
<template>
  <div class="page">
    <div class="navbar">
      <div class="navbar-inner">
        <div class="title">{{title}}</div>
      </div>
    </div>
    <div class="page-content">
      <a @click="openAlert">Open Alert</a>
      <div class="list simple-list">
        <ul>
          {{#each names}}
            <li>{{this}}</li>
          {{/each}}
        </ul>
      </div>
    </div>
  </div>
</template>
<!-- component styles -->
<style>
  .red-link {
    color: red;
  }
</style>
<!-- rest of component data and methods -->
<script>
  // script must return component object
  return {
    data: function () {
      return {
        title: 'Component Page',
        names: ['John', 'Vladimir', 'Timo'],
      }
    },
    methods: {
      openAlert: function () {
        var self = this.$app.dialog.alert('Hello world!');
      },
    },
    on: {
      pageInit: function () {
        // do something on page init
      },
      pageAfterOut: function () {
        // page has left the view
      },
    }
  }
</script>

Well, now it is much cleaner. The <template> and <style> tags will be automatically converted to the same properties of exported component.

You may think that it is not valid to have a direct return statement in script, but it is ok because parser put the content of the script tag into function body, so it is ok here