Dumb Components and Visual Feedback in Angular Apps

Posted on by Tero Parviainen (@teropa)

Angular applications are becoming increasingly component-oriented. Angular 2 is all about components. In Angular 1, component patterns and APIs are emerging.

As we move towards component-style applications, we should think about how we organize components and divide responsibilities between them. One useful way to think about this is to distinguish between smart and dumb components, or presentational and container components.

The idea is simple: Your UI component tree consists of two different kinds of components:

In the context of Angular, this idea is popping up in different places, taking slighly different forms: I've written a bit about it and so has Igor Krivanov. Shai Reznik recently did a talk about the topic at NG-NL, which should be out on Youtube soon. You can also see the two different kinds of components in action in Lukas Ruebbelke's article about ngrx, and Todd Motto has demonstrated how you can create "stateless components" in Angular 1.5.

There are many benefits in organizing your UI code this way, and they have been well described in these articles and talks: Dumb components are easier to understand, easier to test, easier to use, and easier to reuse than container components, since you can easily plug in different inputs and outputs to them in different contexts.

On top of this, there is one additional fantastic use case that is enabled by this pattern, which I want to highlight in this post: You can very easily render presentational components in many different states in parallel, which gives you quick visual feedback during development.

Dumb Component Test Benches

A UI component can have many different states, triggered by different combinations of data and user actions. Our job as developers is to make sure each component looks and behaves appropriately in all possible states. This means that we spend a lot of time simulating different component states while we develop and test our applications. Often this entails clicking around producing different kinds of scenarios in the application's main UI, which the component we're developing is part of. This can be time consuming.

With dumb components we can easily do something better though. Since a dumb component's state is completely driven by its inputs, we can very easily plug in different inputs to produce different component states. The key realization here is that there's nothing forcing us to produce all these different inputs in the context of our main application. We can just as easily construct a "test bench" application for the component, that renders many instances of the component side by side in different states. This means we get rapid feedback about how the component behaves when we make changes to it.

The best description of this idea that I've seen comes from Bruce Hauman, the author of the Devcards tool for ClojureScript:

"We primarily design and iterate on our front end applications inside the main application itself. In other words, our execution environment is constrained by the shape and demands of the application we are working on. This is extremely limiting.

This doesn't seem like a problem, eh?

Well think of it this way: the main application and its many subcomponents can potentially embody a tremendous number of states. But working against a single instance of the application only lets you look at one state at a time. What if you could work on the application or component in several states at the same time? This is a powerful multiplier. You are increasing the bandwidth of the feedback you are getting while working on your code."

Example

We have a dumb "list" component that knows how to render a list of things.

This is an Angular 1.5 component, but everything here applies just as well to Angular 2, or any other component-based UI framework really.

my_list.component.js
angular.module('myList', [])
  .component('myList', {
    bindings: {
      items: '<',
      onDelete: '&'
    },
    templateUrl: 'my_list.template.html'
  });

Most of the meat of the component is in its template. It renders a list with some Material Design Lite styling attached.

my_list.template.html
<ul ng-if="$ctrl.items.length"
    class="mdl-list">
  <li ng-repeat="item in $ctrl.items"
      class="mdl-list__item">
    <span class="mdl-list__item-primary-content">
      <i class="material-icons mdl-list__item-avatar">person
      {{item.name}}
    </span>
    <span class="mdl-list__item-secondary-action">
      <button ng-click="$ctrl.onDelete()"
              class="mdl-button mdl-button--fab mdl-button--mini-fab">
        <i class="material-icons">delete
      </button>
    </span>
  </li>
</ul>
<div ng-if="!$ctrl.items.length"
     class="empty-label">
  Nothing here
</div>

This component is pretty simple, but it has many interesting states that would be useful for us to see: Empty lists, short lists, long lists, lists with terse content, lists with verbose content, and so on.

While we could try to reproduce all these kinds of states in our main application, what if we could just have a UI like this instead:

With a test bench like this, we get immediate visual feedback about how the changes we make to the component's template, styles, or logic affects how the component behaves. More importantly, we get to see these effects in all kinds of "corner case" states, some of which might be cumbersome to reproduce within the constraints of the main application.

This is easy to do when we're working with a dumb component. We can just make a "test bench" wrapper that instantiates the component with different kinds of inputs. No mocking or elaborate setup code is needed - just plain data that's plugged into each instance of the component being "tested":

angular.module('myComponentTestBench', ['myList'])
  .component('myListTestBench', {
    controller: function() {
      this.shortList = [
        {name: 'Jane'},
        {name: 'Joe'},
        {name: 'Mary'}
      ];
      this.longList = [
        {name: 'Jane'},
        {name: 'Joe'},
        {name: 'Mary'},
        {name: 'James'},
        {name: 'John'},
        {name: 'Patricia'},
        {name: 'Linda'},
        {name: 'Michael'},
        {name: 'Barbara'},
        {name: 'William'},
        {name: 'Elizabeth'},
        {name: 'David'},
        {name: 'Jennifer'},
        {name: 'Lisa'},
        {name: 'Joseph'},
        {name: 'Margaret'},
        {name: 'Thomas'}
      ];
      this.verboseList = [
        {name: 'Taumatawhakatangi­hangakoauauotamatea­turipukakapikimaunga­horonukupokaiwhen­uakitanatahu'},
        {name: 'Llanfair­pwllgwyngyll­gogery­chwyrn­drobwll­llan­tysilio­gogo­goch'},
        {name: 'Chargoggagoggmanchauggagoggchaubunagungamaugg'},
        {name: 'Tweebuffelsmeteenskootmorsdoodgeskietfontein'}
      ];
    },
    template: `
      <my-list items="[]"></my-list>
      <my-list items="$ctrl.shortList"></my-list>
      <my-list items="$ctrl.longList"></my-list>
      <my-list items="$ctrl.verboseList"></my-list>
    `
  });

The same idea can be applied to all kinds of dumb components, whether they be simple buttons or more elaborate widgets and forms. The more complex the component, the more useful the technique becomes, because the number of different UI states increases.

Taking the idea further, it is easy to see how developing test benches like these may lead you towards having a kind of living style guide of your component library, and even practices like "style-guide driven development". With a component test bench, developers and designers can collaborate on component UI and behavior based on the actual JavaScript and CSS code rather than mockups. The effects of changes can be verified and iterated upon quickly.

With this approach components also tend to become naturally more reusable, because they're being developed outside of the "main application", which prevents unnecessary coupling from taking place.

If these kinds of techniques interest you, I really recommend looking at what they're up to with Devcards in ClojureScript. There's a lot there to learn from.

Know Your AngularJS Inside Out

Build Your Own AngularJS

Build Your Own AngularJS helps you understand everything there is to understand about AngularJS (1.x). By creating your very own implementation of AngularJS piece by piece, you gain deep insight into what makes this framework tick. Say goodbye to fixing problems by trial and error and hello to reasoning your way through them

eBook Available Now

comments powered by Disqus