Challenges in creating reusable components

Gábor Farkas
DoctuSoft Coding Style
5 min readApr 5, 2016

In this post, I take a rather simple and not necessarily real-life example of a reusable component and analyze the challenges we might face with it. I also offer solutions :) I mostly discuss Angular but the things I point out apply to most frameworks.

A simple example

Let this component be a simple pair of text inputs: entering a name and an email address.

Now let’s define this component in the Angular way, first the template:

And then the directive:

It looks simple enough. (See the working demo here: https://jsfiddle.net/hhcofcmds/2srto7vk/1/ )

Telerik also recently published a nice post about creating components in popular frameworks.

That’s easy, you say, but what about specialization?

Let’s separate two things: components that are more like widgets or controls (a datepicker, a combobox, a color chooser, etc), and specific reusable parts of our application.

When creating a reusable part, we might want to use that component in different ways at different places in our application. How do we do that? Let’s see some examples.

Specializing data-binding and validation

You’ll have noticed that the directive has two bound parameters for the two text fields. Actually we have other options: we can just bind a “person” object for example and in the component we assume that it has an “email” and a “name” property. Both solutions have their pros and cons.

But I have another point: what if I want to use this component in an Angular form and use validation rules as well? Where do wedefine which fields are required for example?

If, for example, the name field might be required in some places in our app, but be optional at other places, a simple solution comes to mind, use nameRequired and emailRequired parameters:

Well, this isn’t such a good idea. While in this particular case it might work fine, we cannot put all possible specializations in the component itself. We will probably want to have ng-disabled and maybe some other custom validation rules as well. You might have seen classes with 2–5 overloaded constructors, many of them with 5–6 boolean parameters, and their body flooded with if-s and switch-cases. We can do better than that, I’ve already written about this, but now let’s take a look at some other aspects of the problem.

Specializing behavior

There might be behavior baked into a component. A datepicker is a good example as it also has a quite complex inner state. Our current name-email example doesn’t even have a controller yet.

Now let’s suppose we have a database of already known name-email pairs. If the email is entered, we want to automatically fill the name. Where do we put this specialization? We can define an AutofillingNameEmailDirective that defines a controller that watches for scope changes and acts as desired. This way we can completely reuse our template. Nice! That’s what we want: to reuse code. (But note that if we didn’t have $scope.$watch and had to use ng-change in the template, then we couldn’t do this specialization without touching the template, so we would have the same problem as above)

Specializing rendering

Let’s suppose we want to have a version of our component that fits well in a form (e.g. it has field labels), and another version which is more compact and has only placeholder texts. While we can of course put all these in one template and show-hide parts with CSS, again, it’s not a good idea to put all possible specializations in one place and use switches to turn certain features on or off. It’s generally better to put these two cases in different templates, so that they can have totally different CSS rule sets if needed, and the html markup is clean. If we have the data binding and data validation rules separately from the markup, having two templates won’t be a problem. If we can only define these rules in the markup, we will have to duplicate these (and code duplication is never good for various reasons)

Separation of concerns

Separation of concerns is a nice principle to follow, but we have to know which concerns to separate. It’s quite common to separate the three aspects I mentioned: rendering, behavior and data-binding. If we handle these separately, it will be easier to extend or specialize, and thus easier to create the component we need at a particular place while also reusing code properly.

Data-binding and validation arebaked to our html template with Angular (or we can use the workaround I mentioned). Behavior and rendering are also easily mixed. Of course, behavior and rendering are inherently inter-dependent: when we define behavior, we need to know that certain elements exist in the view. I’ll write about this in a later post.

Different responsibilities in Angular (and some other common libraries)

With React, data-binding is not really an issue at all. Data propagation is handled in different ways. While it seems that behavior and rendering are also mixed in React, it’s still cleaner than in Angular, because we are not allowed to write imperative event handler code in JSX.

The cleanest rendering-behavior distinction I’ve seen is in Apache Wicket and Cycle.js. Apache Wicket uses only id-s from the html template when attaching controls on them. Cycle.js uses styleclasses to refer to rendered elements in event sources. The view doesn’t refer controllers in any way; it just leaves handles for the controller.

Solutions?

You’ll probably not face these problems in many of your applications. Mixing all these aspects up won’t really cause you difficulties in a large part of your application, depending what kind it is, of course. But once you encounter more complex cases, it’s good to know why certain difficulties exist, and know their root cause. Knowing this root cause will help you find a better solution.

The best solution for a given problem will depend on many things, but if you find out which aspects to separate, it will show you a path. Unfortunately, in Angular, not everything is easily possible. Many things that you can do in the templates, you cannot do on an API from the controller. I’ll show you some examples, offering actual solutions in future blogposts.

As a final word, I advocate code to define views. With code, it’s easier to separate the responsibilities that you want to separate in a particular case, and it’s also easier to extend or specialize.

--

--