18 Aug

Best Practices for Creating Knockout Components

With the release of version 3.2.0, knockout introduced Components, which are a clean and powerful way of organizing and structuring code. We have increasingly used components in our projects since then and enhanced our understanding about what is a good component and what is not. In this blog post, I will present our best practices for creating components.

Components – general principles

The idea behind components is to create self-contained chunks of code that fulfill two main purposes:

  • reusing pieces of code
  • simplifying code by breaking it into smaller pieces which each encapsulate a certain functionality; this makes it easier to maintain and understand the code

In order to be self-contained and thus easily reusable, components must be only loosely coupled to the part of the application they are embedded in.

Registration

Knockout components combine a viewmodel and a template. In order to start using a component, it must be registered with the ko.components.register function. The function specifies the viewmodel and template, both of which should be loaded from external files and not hardcoded into the registration.

Of all the different ways of specifying the viewmodel, we use the createViewModel factory function. It is called with the parameters params and componentInfo, where params is an object whose key/value pairs are the parameters passed from the component binding or custom element, and componentInfo.element is the element into which the component is being injected. By passing both params and componentInfo into the constructor, we ensure that both are accessible in the viewmodel:

View

For injecting a component into a view, we use it as a custom element. We prefer this over the knockout component binding, which binds the components to regular div elements, because it is much more elegant and straightforward:

To supply parameters to the component, we provide a key-value object as params attribute. The properties of this key-value object are defined in the containing viewmodel and will be received by the component’s viewmodel constructor.

The component’s view should have as its outer element a distinctive div container with the component name as its class name:

This makes it easier to address the component and to provide component-specific css.

Since we want the components to be reusable, the view must not contain any ids. Having ids in the component would make it impossible to use a component multiple times within one page.

Params

The properties which are passed over as params into the component are the only means of communication between the component and the containing viewmodel. In order to maintain a loose coupling, we must never pass the complete parent viewmodel into the component. This would make the component very hard to reuse and the viewmodel very hard to change.
Instead, only those properties and functions should be sent to the component that are really necessary for displaying and manipulating the component’s view elements.

For better keeping track of what the properties are meant to be used for, we have established the following naming convention: if the component needs to call a function of its containing view model, the parameter for this function should have the word ‘callback’ in its name (such as ‘cancelCallback’). If, on the other hand, the containing viewmodel needs to call a function from the component, the function’s name should contain ‘ComponentAction’, such as “findAddressComponentAction”.

Since a component should solely be concerned with itself and not with the container in which it is embedded, we should never pass over a view element from outside the component. Moreover, trying to access a component via their containing elements is a dangerous thing to do especially if a component is used more than once within the container. Instead, a component can be accessed unambiguously via the componentInfo which is provided by knockout’s createViewModel function and should be passed into the constructor if the viewmodel needs to access it.

Viewmodel

A component should act as a black box which takes care of all the functionality that is expected from it. Therefore it should implement all the component-related logic. For example, if we create a table component, the viewmodel must provide the functionalities for searching, sorting, scrolling, etc., rather than the containing viewmodel.

Besides, the viewmodel class should always have a dispose function in which any resources are released that are not collected by the garbage collector, such as subscriptions, event handlers or ko.computed properties (if they are not pure computed properties). The viewmodel should have this dispose function even if the function is empty, as a reminder that we do need to remember to clean up when we extend the component later. The dispose function is called every time just before the container element is removed from the DOM.

Share this

Leave a reply