Refactoring Angular Apps to Component Style

Or, how to modernize your crufty AngularJS codebase one simple step at a time

Posted on by Tero Parviainen (@teropa)

Component-based directives are becoming increasingly popular in the Angular community. One reason for this is that Angular 2 will be all about components and people are preparing their 1.x code for the upgrade. Another reason is that componentization just makes apps easier to work with.

The thing is, even though people are talking about components a lot, most of the existing Angular code in the world is still not component-based. Components didn't become widespread until quite recently, and many of us are working with apps that have several years of history behind them. The foundations for these codebases were laid way before components were added to our collective toolbox.

In this article I present a case for refactoring existing Angular 1.x applications, whether large or small, towards component-based architectures. This is both to improve those apps in general and to prepare them for Angular 2.

I'll describe a catalog of specific refactorings that have been working for me when moving apps towards components. True to the spirit of refactoring, every change is broken down into small systematic steps. That is a requirement for being able to do this without breaking existing functionality, and without setting aside large amounts of time.

Table of Contents

Components

Pretty much all of the major JavaScript frameworks are converging towards an explicit concept of Components as the primary building blocks of user interface code. Brian Ford, in his talk in TXJS 2015, put it quite nicely:

"The main primitive is this idea of a 'component'. I think everyone has some notion of what a component is. The idea is that it should be an atomic UI piece that is composable and reusable, and should work with other pieces in a way that's performant."

Components are not a new invention. They've been around in some form for as long as people have been writing computer user interfaces. But I don't think they've ever been explicitly in the front and center in JavaScript across the board, the way they are now in all of React, Ember, and Angular.

Components in Angular 2

So, Angular 2 will be a component-based framework. Components are what you will construct user interfaces out of. There will be no controllers. There will be no standalone templates. There will also be no directives - not in the same form as we know them. It's all just components.

Here's how you use a component in Angular 2:

<my-confirmation
  [message]="'Launch missiles?'"
  (ok)="launchMissiles()">
</my-confirmation>

A component is basically just a custom HTML element that matches a component name we have defined somewhere else. A component usually has certain inputs and outpus, which we supply as properties on the element.

Technically components can be matched to any CSS selectors, not just element names.

Here's how you define a component like the one above:

@Component({
  selector: 'my-confirmation',
  inputs: ['message'],
  outputs: ['ok']
})
@View({
  template: `
    <div>
      {{message}}
      <button (click)="ok()">OK</button>
    </div>
  `
})
class MyConfirmation {
  okEvents = new EventEmitter();
  ok() {
    this.okEvents.next();
  }
}

I won't go into the details of the Angular 2 component syntax and API, but what I will say is that there are really three distinct parts in this component definition, and in all components in general:

Approximating Components in Angular 1.x

Most of the discussion around the Angular 2 migration path revolves around how different the two versions are: APIs are changing and Angular 1 concepts are disappearing.

When it comes to components, however, there is a true commonality between the two Angular versions. In Angular 2 we have components, and in Angular 1 we can approximate components using the existing directive system. Though we lack an actual component API, we can use conventions that result in something almost identical.

Here's what an Angular 1.x component looks like:

<my-confirmation
  message="'Launch missiles?'"
  on-ok="launchMissiles()">
</my-confirmation>

And here's how you can define such a component as a directive:

module.directive('myConfirmation', function() {
  return {
    scope: {},
    bindToController: {
      message: '=',
      onOk: '&'
    },
    controller: function() { },
    controllerAs: 'ctrl',
    template: `
      <div>
        {{ctrl.message}}
        <button ng-click=“ctrl.onOk()">
          OK
        </button>
      </div>
    `
  }
});

It is true that there's not a single line of code here that's the same as in the Angular 2 example. Apart from the closing curly braces, that is. (Those you can migrate directly.) But I think it is important to notice that all the same key parts are here:

If you write your Angular UIs in this kind of component style, your app will be architecturally aligned with Angular 2. Porting it will be much easier: It'll be more about changing the APIs and syntax, and less about a complete overhaul of your application architecture. In terms of workload, those are two very different propositions.

In these examples, all component code is contained in a single file. However, this is not required and it's not necessarily what I'm advocating.

In Angular 2, the interface boundary and the logic will naturally go in the same file, as the former is defined as decorators on the latter. It also seems to be common practice to define Angular 2 component templates inline using ES6 template strings. But when componentizing Angular 1 code, I still usually split these up into two to three files. I just make sure those files are co-located and consistently named:

  • components/my_confirmation/
    • my_confirmation_directive.js
    • my_confirmation_controller.js
    • my_confirmation.html

The Reality of Most Angular 1.x Apps: Scope Soup

A problem many of us are facing is that as much as we'd like it to be the case, most of our code isn't following the component pattern. That pattern hasn't been widely accepted for very long, and many of the apps we're building and maintaining were started years ago.

These non-component applications are characterized by a few typical architectural patterns. Together they could be described as the Scope Soup architecture:

I've discussed the problems with Scope Soup architectures at length in a previous article. What I'll add here is that on top of everything, these kinds of apps are architecturally pretty far removed from Angular 2. A component style is not only superior by itself, but will also make your path to Angular 2 easier.

Refactoring to The Rescue

So, if you have a Scope Soup architecture and buy the idea that a Component architecture would be superior, what is there to do? The differences between the two are fairly fundamental, so it seems like a daunting task to go from one to the other.

You could always rewrite your application, of course. But that path is fraught with peril - it always ends up being more work than you expect, for example. In any case most of us just don't have the option of pausing development for a month or two just to "fix our code".

The thing is, I don't think that's necessary. There is a way to change things without disrupting development. And it is not a new idea. It's called refactoring.

Refactoring, as defined by Martin Fowler, is a very specific technique for improving existing code. Fowler described it in the preface of his seminal book as follows:

"Refactoring is the process of changing a software system in such a way that it does not alter the external behavior of code yet improves its internal structure. It is a disciplined way to clean up code that minimizes the chances of introducing bugs.

With refactoring you can take a bad design, chaos even, and rework it into well-designed code. Each step is simple, even simplistic. [...] Yet the cumulative effect of these small changes can radically improve the design. It is the exact reverse of the normal notion of software decay."

If we can find a way to componentize our Scope Soup in small and systematic steps, that will significantly increase our chances of success. We are less likely to break things if each change is small enough and if we can test that everything still works after every step.

Also, if we are able to make this work part of our daily routine, we don't need a separate refactoring sprint or project. That's great, because selling such a thing to management is difficult (as it should be in my opinion). Instead, we can make one refactoring today, the next one tomorrow, the next one next week, and stretch the work over a period of time.

The remainder of this article describes a catalog of refactorings that I've found to be useful when componentizing Scope Soup applications. Each refactoring is fairly independent and applies to a particular situation. After each one, the code functions just like it did before, but is a little bit closer to a component style. Applying all of these refactorings across your codebase will over time transform it significantly.

I've found most of these refactorings while working on one particular application. Depending on how similar your app is to that one, you may find these refactorings more or less relevant and easy to apply. I do feel they are fairly universal, but your mileage may vary.

Prerequisite: Controller Aliasing

Before you do anything else, it is a good idea to introduce controller aliasing to each one of the controllers you have in your app. This means using the 'Controller as ctrl' syntax in every ng-controller and the controllerAs attribute in every route and directive that has a controller.

Applying controller aliasing is a good idea regardless of whether you choose to go ahead with these refactorings or not. It just makes it that much easier to see where each piece of data or functionality is coming from. As you then start componentizing your code, you can focus on that instead of spending your time figuring out which controller owns a given attribute.



Component Creation Refactorings

The first group of refactorings is all about finding places where you might want to introduce components into your application, and then introducing them.

As you apply the five refactorings that follow, your code organization is going to start shifting. You'll end up with many small component directories. In each one will be a template, a directive, and a controller. Together they form a component. It becomes easier to find things, and it becomes a lot easier to see what exactly the structure of your application is.

Replace ng-include with Component Directive

You have an ng-include somewhere in your HTML templates. At some point in the past you've decided that this is a separate part of the UI that needs a separate template, and thus it is exactly the kind of part that could easily become its own component.

Motivation

Above all else, this refactoring is a necessary first step toward having a component, and the real payoff comes in the later refactorings.

It does, however, have value in itself too. As Igor Minar put it in a Twitter conversation with Ben Nadel:

ng-include is a relatively low-level feature that pollutes your view with information about template file locations. A component tag is a cleaner way to include something onto a template.

A possible challenge with this refactoring is that ng-include lets you load templates dynamically, since the template URL is an expression and evaluated at runtime. However, I've been able to substitute such cases with things like ng-if/ng-switch, as described in the Ben Nadel article. The resulting code tends to be easier to follow too.

Mechanics
Example

We have a template where another template, foobar.html, is included with ng-include:

index.html
<div ng-include="'foobar.html'">
</div>

To replace this, we create a foobar directive. It uses an inherited scope and the foobar.html template:

foobar_directive.js
module.directive('foobar', function() {
  return {
    scope: true,
    templateUrl: 'foobar.html'
  };
});

We make sure that the directive and the template are co-located and that their names match:

Then we can replace the original ng-include with an instance of our new directive:

index.html
<div ng-include="'foobar.html'">
</div>
<foobar></foobar>


Adopt Template Root Controller in Component Directive

You have a component directive, whose template has an ng-controller at or very near the root. This often follows Replace ng-include with Component Directive.

It is very likely that this controller should be considered the component controller instead. Adopt it in the component directive.

Motivation

Templates and controllers often go in pairs, and it is a common pattern to have a template, and on the top of that template an ng-controller. Putting both of these things in the component directive makes the pairing explicit. It solidifies the relationship between the controller and the template.

Mechanics
Example

After a foobar component directive has been introduced, its template looks like this:

foobar.html
<div ng-controller="FoobarCtrl as ctrl">
  <p>{{ctrl.getText()}}</p>
</div>

We move and rename the file that contains FoobarCtrl so that it is co-located with the component directive:

We then use that controller in the directive, and use the same ctrl alias that the ng-controller was using earlier:

foobar_directive.js
module.directive('foobar', function() {
  return {
    scope: true,
    controller: 'FoobarCtrl',
    controllerAs: 'ctrl',
    templateUrl: 'foobar.html'
  };
});

We can now remove the whole ng-controller wrapper from the template:

foobar.html
<div ng-controller="FooBarCtrl as ctrl">
  <p>{{ctrl.getText()}}</p>
</div>

Alternatively, you could keep the element for styling purposes and just remove the ng-controller attribute.



Replace ng-controller with Component Directive

You have an ng-controller somewhere other than the root of a template. This is a good sign that there might be a component hiding here.

Just like ng-include, ng-controller often demarcates an area of the template that's somehow independent, or separate from its parents and siblings. If it is, it should be a component.

Motivation

Like Replace ng-include with Component Directive, this is a first step towards a component more than anything else. In later refactorings, the transformation will be completed.

You do immediately get a slightly cleaner template, since you don't have to encode the name and alias of a controller in the HTML. You can just apply a component element.

Mechanics
Example

We have an element with an ng-controller somewhere in a template. It is instantiating the FoobarCtrl controller and aliasing it as ctrl:

index.html
<div ng-controller="FoobarCtrl as ctrl"
     class="my-foo-bar">
  <p>{{ctrl.getText()}}</p>
</div>

We create a new directive called foobar, which creates an inherited scope and uses the FoobarCtrl, also aliasing it as ctrl:

foobar_directive.js
module.directive('foobar', function() {
  return {
    scope: true,
    controller: 'FoobarCtrl',
    controllerAs: 'ctrl'
  };
});

The new directive and the existing controller are moved to the same location. We also make sure they have the same name prefix:

And finally, in our original template we can replace the ng-controller with an instance of the foobar component:

foobar.html
<div ng-controller="FoobarCtrl as ctrl"
     class="my-foo-bar">
<foobar class="my-foo-bar">
  <p>{{ctrl.getText()}}</p>
</foobar>
</div>


Move Markup to Component Template

You have a component directive with a controller. In the child nodes of the component element there are expressions that use the component controller. This is most often the case right after you've applied Replace ng-controller with Component Directive:

<foobar>
  <p>{{ctrl.getText()}}</p>
</foobar>

Although this works just fine, it looks a bit odd since ctrl is not defined in this template. It comes from the foobar component, but you can't see that by looking at the template.

This is a sign that the child elements here really belong to the component itself, and should be moved inside the component's template.

Motivation

When a component directive has no template, but the component element does have children, it is often the case that these children really belong to the component template.

This may not always be the case. But if the child elements have expressions that refer to the component controller, that's a good sign that they should be in the component template.

Sometimes the child elements are partially part of the component, but other parts are not. In this case, consider using transclusion.

Mechanics

Occasionally part of the HTML content would still be logically a better fit for the original template. In that case, instead of the second step above, do the following:

Basic Example

Continuing from the previous example, we're in a situation with markup inside a component element that doesn't really belong there:

index.html
<foobar>
  <p>{{ctrl.getText()}}</p>
</foobar>

We'll create a template file for the component directive:

We should also reference this new template from the directive:

foobar_directive.js
module.directive('foobar', function() {
  return {
    scope: true,
    controller: 'FoobarCtrl',
    controllerAs: 'ctrl',
    templateUrl: 'foobar.html'
  };
});

The template file is populated with what was previously inside the directive element:

foobar.html
<p>{{ctrl.getText()}</p>

The directive element itself becomes empty:

index.html
<foobar>
  <p>{{ctrl.getText()}}</p>
</foobar>
Example with Transclusion

After applying Replace ng-controller with Component Directive, we're left with the following markup in the original template:

index.html
<foobar>
  <h2>{{ctrl.getTitle()}}</h2>
  <p>{{mainCtrl.getText()}}</p>
</foobar>

The component has content that logically belongs to its own template (ctrl.getTitle()), but also content that comes from the outer scope, where we have some other controller (mainCtrl.getText()).

We can use transclusion to support both cases. Again, we'll first create a template file for the component directive:

We reference this new template from the directive. While doing that, we also enable transclusion:

foobar_directive.js
module.directive('foobar', function() {
  return {
    transclude: true,
    scope: true,
    controller: 'FoobarCtrl',
    controllerAs: 'ctrl',
    templateUrl: 'foobar.html'
  };
});

The template file is populated with the part of the original template that we want to put inside the component. For the other part, we add an ng-transclude placeholder:

foobar.html
<h2>{{ctrl.getTitle()}}</h2>
<p ng-transclude></p>

In the directive element itself we leave just the part that will be transcluded:

index.html
<foobar>
  <h2>{{ctrl.getTitle()}}</h2>
  <p>{{mainCtrl.getText()}}</p>
</foobar>


Wrap Markup in Component Directive

You have a big template, with no ng-controllers or ng-includes inside it to demarcate places where Replace ng-include with Component Directive or Replace ng-controller with Component Directive would apply.

There are likely to be parts of such templates that would make sense to extract into separate templates. Instead of using ng-include, you can introduce components in these cases.

Motivation

You want your templates to be small enough to be manageable, and to form cohesive units. Whereas in the past you may have used ng-include to extract smaller templates from bigger ones, using a component is not only a higher-level abstraction, but also makes it easier to later make the separated parts full-blown components with their own controllers and interface boundaries.

Mechanics

If there's some part inside the extracted HTML that you'd rather leave in place, you can use transclusion. Instead of the last two steps above, do the following:

Basic Example

We have, as part of some bigger template, a <ul> element that lists some data:

index.html
<div>
  <h1>Foobars</h1>
  <ul>
     <li ng-repeat="foo in ctrl.bars">
        <img ng-src="foo.imageUrl"/>
        {{foo.text}}
     </li>
  </ul>
</div>

That listing is something we'd like to extract into its own component. We'll create a directive and a template file for that component. We'll call it the foobarList directive:

The directive creates an inherited scope, and references the newly created template file:

foobar_list_directive.js
module.directive('foobarList', function() {
  return {
    scope: true,
    templateUrl: 'foobar_list.html'
  };
});

The template is populated by simply copying the extracted HTML from the original template:

foobar_list.html
<ul>
  <li ng-repeat="foo in ctrl.bars">
    <img ng-src="foo.imageUrl"/>
    {{foo.text}}
  </li>
</ul>

In the original template, we may now use an instance of this new component. It replaces the extracted markup:

index.html
<div>
  <h1>Foobars</h1>
  <ul>
    <li ng-repeat="foo in ctrl.bars">
      <img ng-src="foo.imageUrl"/>
      {{foo.text}}
    </li>
  </ul>
  <foobar-list></foobar-list>
</div>
Example with Transclusion

If the <ul> element from the previous example has some content that comes from outside, transclusion can be used to support both. In this case, we have an item header that's provided by an external mainCtrl:

index.html
<div>
  <h1>Foobars</h1>
  <ul>
     <li ng-repeat="foo in ctrl.bars">
        <h3>
          {{mainCtrl.itemHeader}}
        </h3>
        <img ng-src="foo.imageUrl"/>
        {{foo.text}}
     </li>
  </ul>
</div>

When we extract the listing into its own component, we again create a directive and template file for that component:

The directive creates an inherited scope, and references the newly created template file. It also enables transclusion:

foobar_list_directive.js
module.directive('foobarList', function() {
  return {
    transclude: true,
    scope: true,
    templateUrl: 'foobar_list.html'
  };
});

The template is populated by copying the extracted HTML from the original template. The item header part is replaced with an ng-transclude element:

foobar_list.html
<ul>
  <li ng-repeat="foo in ctrl.bars">
    <ng-transclude></ng-transclude>
    <img ng-src="foo.imageUrl"/>
    {{foo.text}}
  </li>
</ul>

In the original template, we may now use an instance of this new component. It replaces the extracted markup, but leaves in place the item header, which will then be transcluded to each item:

index.html
<div>
  <h1>Foobars</h1>
  <ul>
    <li ng-repeat="foo in ctrl.bars">
      <h3>
        {{mainCtrl.itemHeader}}
      </h3>
      <img ng-src="foo.imageUrl"/>
      {{foo.text}}
    </li>
  </ul>
  <foobar-list>
    <h3>
      {{mainCtrl.itemHeader}}
    </h3>
  </foobar-list>
</div>

Data Flow Refactorings

As you apply the component creation refactorings listed above, you'll see your codebase begin to transform. This will be most evident on the file system level: You start to have component directories (or files, depending on how you organize things), each of which have two to three things: A directive definition, a controller, and/or a template.

However, what we haven't done in any way so far, is to change the way data is shared between these components. They're still using scope inheritance and mutation of shared data - the hallmarks of Scope Soup.

On the one hand, it is great that we are able to first change the structure of the code without looking at the data flow, because that allows us to follow the crucial rule of refactoring: Making small changes without breaking the code.

But on the other hand, this is not enough. Once we have some components, we should start to look at how they are sharing data and functionality, and how we could isolate them into proper components in which this sharing is explicit.

Replace External Reference with Bound Input

The most important refactoring in this category is all about finding references to external data or functions from inside components, and getting rid of them by replacing them with explicit, bound inputs.

These kinds of references are usually plentiful in Scope Soup, so this is a refactoring you'll apply many times over, as you find external references from your components.

Motivation

Components should not refer to external scope data. All data that they use from the surrounding context should be explicitly passed in as scope bindings.

This is because otherwise your components are hard to reuse: When you want to reapply a component in a new context, you have to read through all the internal component code and find places where it might reference scope data. That's not how components should work.

Using explicit data sharing through scope bindings also makes your application easier to figure out in general. For example, when you can always see which components are getting a certain piece of data, you'll know which components may need to be revisited if you change the shape of that data.

Mechanics
Example with Template Expressions

We have a component template. In it we can see two external references to a mainCtrl controller, which is not the controller of this component, but some other controller defined on an upper level in the scope hierarchy:

foobar.html
<div ng-show="mainCtrl.isFooBarVisible()">
 <p>
   {{ctrl.getText(mainCtrl.getLanguage())}}
 </p>
</div>

We should remove these external references, and we should do so one by one, while keeping the code working at all times.

We choose the mainCtrl.isFooBarVisible() expression first. It is calling a method on the parent controller, and using the result to control whether the element is visible or not. To replace the external reference, we introduce a binding in the component directive, called visible:

foobar_directive.js
module.directive('foobar', function() {
  return {
    scope: true,
    controller: 'FoobarCtrl',
    controllerAs: 'ctrl',
    bindToController: {
      visible: '='
    },
    templateUrl: 'foobar.html'
  };
});

This says that the component has an input called visible, and the component user should supply a value for it.

Note that we have introduced bindToController while keeping the scope: true attribute unchanged. This is not an isolate scope directive - not yet. We don't want to isolate it because there are still other external references inside it, which we don't want to break. Since Angular 1.4, it is possible to use this combination of scope: true and bindToController to introduce bindings to non-isolated scopes.

For the new visible binding, we now need to plug in a value wherever we use the foobar component. Here we actually have legitimate access to the mainCtrl, and can use the expression that was originally inside the component template:

index.html
<foobar
  visible="mainCtrl.isFooBarVisible()">
</foobar>

Inside the component we can now just refer to the scope binding, thus breaking the dependency to the external world:

foobar.html
<div ng-show="ctrl.visible">
 <p>
  {{ctrl.getText(mainCtrl.getLanguage())}}
 </p>
</div>

Having fixed the first external reference, we can repeat the same refactoring for the second one, which is the mainCtrl.getLanguage() call:

foobar.html
<div ng-show="ctrl.visible">
 <p>
  {{ctrl.getText(mainCtrl.getLanguage())}}
 </p>
</div>

For this, we introduce another binding to the directive definition, called language:

foobar_directive.js
module.directive('foobar', function() {
  return {
    scope: true,
    controller: 'FoobarCtrl',
    controllerAs: 'ctrl',
    bindToController: {
      visible: '=',
      language: '='
    },
    templateUrl: 'foobar.html'
  };
});

We then plug in a value for it where the component is used:

index.html
<foobar
  visible="mainCtrl.isFooBarVisible()"
  language="mainCtrl.getLanguage()">
</foobar>

Inside the component template we again just use the binding:

foobar.html
<div ng-show="ctrl.visible">
  <p>{{ctrl.getText(ctrl.language)}}</p>
</div>

The template is now free of external references. All of the data it needs is explicitly specified at the interface boundary.

Example with $watch Expression

The component template is not the only place where external references can be found. You may also have $watch, $watchCollection, or $watchGroup expressions in your component controller (or the link functions if you have any). Here is one that, just like the template expressions in the previous example, should be replaced with a bound input:

foobar_controller.js
function FoobarCtrl($scope) {
  var ctrl = this;

  $scope.$watch('mainCtrl.foo', function(newFoo) {
    if (newFoo.active) {
      ctrl.activeFoo = newFoo;
    } else {
      ctrl.activeFoo = null;
    }
  });
}

The watch expression is referencing the foo attribute of mainCtrl. What we should have for this instead is a bound input:

foobar_directive.js
module.directive('foobar', function() {
  return {
    scope: true,
    controller: 'FoobarCtrl',
    controllerAs: 'ctrl',
    bindToController: {
      foo: '='
    },
    templateUrl: 'foobar.html'
  };
});

When we use the component, we plug in the actual value:

index.html
<foobar foo="mainCtrl.foo">
</foobar>

Inside the controller's watch expression we then just use the bound input:

foobar_controller.js
function FoobarCtrl($scope) {
  var ctrl = this;

  $scope.$watch('ctrl.foo', function(newFoo) {
    if (newFoo.active) {
      ctrl.activeFoo = newFoo;
    } else {
      ctrl.activeFoo = null;
    }
  });
}
Example with Scope Reference

The external reference may actually not be in a watch expression at all. Here we have some controller code that is simply referencing an inherited Scope attribute in plain JavaScript:

foobar_controller.js
function FoobarCtrl($scope) {
  var ctrl = this;

  ctrl.getActiveFoo = function() {
    if ($scope.mainCtrl.foo.active) {
      return $scope.mainCtrl.foo;
    } else {
      return null;
    }
  };
}

This, just like the previous example, needs a bound input instead:

foobar_directive.js
module.directive('foobar', function() {
  return {
    scope: true,
    controller: 'FoobarCtrl',
    controllerAs: 'ctrl',
    bindToController: {
      foo: '='
    },
    templateUrl: 'foobar.html'
  };
});

The value for foo is provided when the component is used:

index.html
<foobar foo="mainCtrl.foo">
</foobar>

The controller code can then just use the bound input:

foobar_controller.js
function FoobarCtrl($scope) {
  var ctrl = this;

  ctrl.getActiveFoo = function() {
    if (ctrl.foo.active) {
      return ctrl.foo;
    } else {
      return null;
    }
  };
}


Replace External Effect with Bound Output

Not all external references inside components are logical inputs. Some of them are logical outputs: Ways in which the component code notifies the world that something has happened.

This is most often the case with event handlers, such as ng-click, ng-change, et al. But you may find outputs anywhere in your component template or controller where you are calling functions inherited from outside, with the purpose of causing a change somewhere else.

Motivation

The reasons for doing this are the same as in Replace External Reference with Bound Input: We do not want our component internals to rely on arbitrary functions attached on parent scopes. That just makes it much harder to reuse those components, and much harder to see what changes a given component may introduce.

Mechanics
Example with Event Handler

We have an ng-click inside a component template, which is invoking a method on an inherited mainCtrl reference:

foobar.html
<div ng-show="ctrl.visible">
  <p>{{ctrl.getText(ctrl.language)}}</p>
  <button ng-click="mainCtrl.deleteFoobar()">
    Delete
  </button>
</div>

In order to break this dependency, we'll introduce an expression binding called onDelete on the directive definition:

foobar_directive.js
module.directive('foobar', function() {
  return {
    scope: true,
    controller: 'FoobarCtrl',
    controllerAs: 'ctrl',
    bindToController: {
      visible: '=',
      language: '=',
      onDelete: '&'
    },
    templateUrl: 'foobar.html'
  };
});

Angular 2 distinguishes between inputs and outputs by using the [] attribute syntax for inputs and the () syntax for outputs. In Angular 1.x there is no such distinction, and there's no built-in way to distinguish between inputs and outputs when you're using a component. For this reason I like to use the on prefix for outputs, as this encodes the distinction.

When the component is used, an expression is plugged in for this new binding. It is the component user that decides what "delete" actually means, which in this case is an invocation of the deleteFoobar method of the main controller:

index.html
<foobar
  visible="mainCtrl.isFooBarVisible()"
  language="mainCtrl.getLanguage()"
  onDelete="mainCtrl.deleteFoobar()">
</foobar>

From inside the component template, we simply invoke the expression binding:

foobar.html
<div ng-show="ctrl.visible">
  <p>{{ctrl.getText(ctrl.language)}}</p>
  <button ng-click="ctrl.onDelete()">
    Delete
  </button>
</div>
Example from Controller Code, with Arguments

The external function call may not always be in an event handler in a template. It may also be in the controller - exactly like we saw with inputs. Here we have a controller method that calls a function on a parent scope:

foobar_controller.js
function FoobarCtrl($scope) {
  var ctrl = this;

  ctrl.onFooClick = function() {
    mainCtrl.setActiveFoo(ctrl.foo);
  };
}

Again, what we should do is introduce an expression binding for this purpose on the directive definition. We can call it onActivateFoo:

foobar_directive.js
module.directive('foobar', function() {
  return {
    scope: true,
    controller: 'FoobarCtrl',
    controllerAs: 'ctrl',
    bindToController: {
      onActivateFoo: '&'
    },
    templateUrl: 'foobar.html'
  };
});

When the template is used, we do the mainCtrl.setActiveFoo invocation that was originally inside the component controller. It has one named argument: foo

index.html
<foobar
  on-activate-foo="mainCtrl.setActiveFoo(foo)">
</foobar>

In the controller we then invoke the expression binding instead of the external, inherited controller. We need to provide an object argument so that Angular will know what to use as the value of foo in the bound expression:

foobar_controller.js
function FoobarCtrl($scope) {
  var ctrl = this;

  ctrl.onFooClick = function() {
    ctrl.onActivateFoo({foo: ctrl.foo});
  };
}

Isolate Component

Once you have replaced all of the inputs and outputs of a component with bindings, using Replace External Reference with Bound Input, and Replace External Effect with Bound Output, the component should no longer have any dependencies to the outside context, other than the ones defined at the interface boundary. At this point, you can switch its scope to an isolate scope.

Motivation

When a component has an isolate scope, you can have much more certainty about how it behaves: You know that it has no inputs and outputs that are not explicitly mentioned in the bindings. That's because there cannot be any, since the scope inheritance chain has been cut.

This is great for reusability, as you quickly see what you need to provide for a given component. It also helps you and others not to introduce any further external references by accident.

Mechanics
Example

We have a foobar directive for which all inputs and outputs have been defined at the interface boundary:

foobar_directive.js
module.directive('foobar', function() {
  return {
    scope: true,
    controller: 'FoobarCtrl',
    controllerAs: 'ctrl',
    bindToController: {
      visible: '=',
      language: '=',
      onDelete: '&'
    },
    templateUrl: 'foobar.html'
  };
});

We can simply switch the value of the scope attribute to an object literal, causing the scope to become an isolate scope:

foobar_directive.js
module.directive('foobar', function() {
  return {
    scope: {},
    controller: 'FoobarCtrl',
    controllerAs: 'ctrl',
    bindToController: {
      visible: '=',
      language: '=',
      onDelete: '&'
    },
    templateUrl: 'foobar.html'
  };
});


Replace State Mutation with Bound Output

Some effects that components make are more easy to spot than others. Some are actually outputs disguised as inputs, in the form of state mutation.

This happens when we are changing data that we actually received as an input. We should be explicit about such changes and encode them as component outputs instead.

Motivation

Mutating data is a common way to get things done in Angular apps, and also just in JavaScript in general: We set attributes on objects, remove attributes on objects, add items to arrays, remove items from arrays, etc.

This is usually not a problem until we start mutating state that we do not own. When we make changes to some data structure we got from some other part of the application, that's where trouble starts to seep in.

The problem is that over time, as the application grows, this kind of state mutation destroys our ability to figure out what's going on. We get into situations where data is changing and we don't know why. The change could be made by some code we wrote last year, four scopes down, but there is nothing in the code that we're looking at to remind us about it.

It helps if we already have Isolated Components, because then we see who has access to the data that's changing. But what we can't see is which one of those components is in fact changing it.

Mechanics

These steps assume you have completed Replace External Reference with Bound Input, and Replace External Effect with Bound Output for all references within the component.

Example with Array Mutation

We have a foobarList component that looks like a proper component: It has an isolate scope, a controller, and a template. It takes one input, which is a collection of foobars:

foobar_list_directive.js
module.directive('foobarList', function() {
  return {
    scope: {},
    controller: 'FoobarListCtrl',
    controllerAs: 'ctrl',
    bindToController: {
      foobars: '='
    },
    templateUrl: 'foobar_list.html'
  };
});

When the component is used, a collection of foobars is provided to it as an input:

index.html
<foobar-list
  foobars="mainCtrl.foobars">
</foobar-list>

Inside the component template, there is a repeater that displays each item in the collection. For each item, some information is shown. A Delete button is also shown for each item. When clicked, an internal component controller method called deleteFoobar is invoked:

foobar_list.html
<ul>
 <li ng-repeat="fb in ctrl.foobars">
  {{fb.name}}
  <button on-click="ctrl.deleteFoobar(fb)">
   Delete
  </button>
 </li>
</ul>

In the deleteFoobar method of the component controller we have the logic for deleting an item. It goes into the foobars array and removes the item from it:

foobar_list_controller.js
function FoobarListController() {
  var ctrl = this;

  ctrl.deleteFoobar = function(foobar) {
    var idx = ctrl.foobars.indexOf(foobar);
    if (idx >= 0) {
      ctrl.foobars.splice(idx, 1);
    }
  };

}

This is where we have the problem: We are mutating the foobars collection, because we have it at hand and it is very easy to do. But we do not own that collection: It was given to us as an input. So inside the component we really have no business mutating it.

What we should do instead is invoke a callback saying "the user wants to delete this foobar". In the controller, we'll invoke a bound output called onDelete, and give it the foobar that is being deleted:

foobar_list_controller.js
function FoobarListController() {
  var ctrl = this;

  ctrl.deleteFoobar = function(foobar) {
    var idx = ctrl.foobars.indexOf(foobar);
    if (idx >= 0) {
      ctrl.foobars.splice(idx, 1);
    }
    ctrl.onDelete({foobar: foobar});
  };

}

We specify a bound output with this name at the component boundary:

foobar_list_directive.js
module.directive('foobarList', function() {
  return {
    scope: {},
    controller: 'FoobarListCtrl',
    controllerAs: 'ctrl',
    bindToController: {
      foobars: '=',
      onDelete: '&'
    },
    templateUrl: 'foobar_list.html'
  };
});

It is now obvious from looking at the component interface definition that it may want to delete an item. It becomes the responsibility of the component user to specify what that actually means.

In this case, we should probably have a method in the mainCtrl, which owns the foobars collection, that deletes an item from it:

index.html
<foobar-list
  foobars="mainCtrl.foobars"
  on-delete="mainCtrl.deleteFoobar(foobar)">
</foobar-list>

The code that owns the collection goes together with the code that mutates it. We no longer have the problem of "action at a distance", where other code somewhere is mutating our collection. They just notify us that it should be mutated, and we actually do the mutation in mainCtrl.

Example with Reassignment

A simple attribute reassignment is another form of state mutation. Let's say we have a foobarEditor that takes an object to edit, and a callback that is invoked when the user is done with the editing:

foobar_editor_directive.js
module.directive('foobarEditor', function() {
  return {
    scope: {},
    controller: 'FoobarEditorCtrl',
    controllerAs: 'ctrl',
    bindToController: {
      foobar: '=',
      onDone: '&'
    },
    templateUrl: 'foobar_editor.html'
  };
});

The object to edit is passed into the component, along with the action to invoke when the onDone output is called:

index.html
<foobar-editor
  foobar="mainCtrl.foobar"
  onDone="mainCtrl.saveFoobar()">
</foobar-editor>

Inside the component, there are some form fields bound to the object using ng-model:

foobar_editor.html
<input type="text"
       ng-model="ctrl.foobar.firstName"
       placeholder="First name" />
<input type="text"
       ng-model="ctrl.foobar.lastName"
       placeholder="Last name" />
<button ng-click="ctrl.onDone()">OK</button>

The controller for this component doesn't actually need to do anything:

foobar_editor_controller.js
function FoobarEditorController() {
}

The problem with this component is that, like in the previous example, it does not own the object it is given. It should not be mutating its contents.

We can refactor this component by, first of all, changing its signature so that it gives out the new version of the object when the user is done, instead of just mutating the input object in place:

index.html
<foobar-editor
  foobar="mainCtrl.foobar"
  onDone="mainCtrl.saveFoobar(newFoobar)">
</foobar-editor>

In the directive, we should change the internal name of the incoming object, so that it explicitly says it's an input:

foobar_editor_directive.js
module.directive('foobarEditor', function() {
  return {
    scope: {},
    controller: 'FoobarEditorCtrl',
    controllerAs: 'ctrl',
    bindToController: {
      inputFoobar: '=foobar',
      onDone: '&'
    },
    templateUrl: 'foobar_editor.html'
  };
});

In the controller we then make a copy of that object for editing. The copy is what the ng-models will be bound to. The input object remains unchanged:

foobar_editor_controller.js
function FoobarEditorController() {
  var ctrl = this;

  ctrl.foobar = angular.copy(ctrl.inputFoobar);
}

In the view we'll change the click handler of the OK button, so that it doesn't call the bound output action directly, but instead calls an internal controller method which we'll introduce next:

foobar_editor.html
<input type="text"
       ng-model="ctrl.foobar.firstName"
       placeholder="First name" />
<input type="text"
       ng-model="ctrl.foobar.lastName"
       placeholder="Last name" />
<button ng-click="ctrl.onOKClick()">OK</button>

This method will call the bound output, handing it the edited copy of the object with the name newFoobar, matching what we expected when we were using the component:

foobar_editor_controller.js
function FoobarEditorController() {
  var ctrl = this;

  ctrl.foobar = angular.copy(ctrl.inputFoobar);

  ctrl.onOKClick = function() {
    ctrl.onDone({newFoobar: ctrl.foobar});
  };
}

Here, again, we are treating the input object as if it was immutable. It might as well be!

If you wanted to propagate out the changes immediately when the user changes the text field values. You could invoke the bound output using ng-change handlers on those inputs.

Replace Two-way Binding with One-way Binding

One notable fact about the '=' bindings we have been creating for inputs is that they are actually two-way bindings. That means you may occasionally get code that treats them as outputs as well as inputs - either by accident or on purpose.

If you want to make sure that data flows only in one direction through these bindings, you can replace them with one-way bindings. The expression binding syntax '&' can be used here. We've only used it for outputs so far, but it can also be used for one-way inputs.

Motivation

Like other kinds of state mutation, two-way bindings may make it unclear when and how things change in your application. If you reassign a controller attribute inside a component, it may not occur to you that this may have external effects outside the component. But it may indeed have external effects if that attribute is bound with a two-way binding.

Using one-way data bindings may offer an easy way to make sure such reassignments don't happen by accident. If the component indeed does reassign the value on purpose, an explicit output binding should be used instead of piggybacking on the two-way input.

Note that unlike a two-way binding, a one-way binding does not have a watch attached to it, so it is not automatically synced to the parent value. To get the latest value, you always have to call the expression function again.

If you only need the value once, this won't be an issue. Also, if you call the binding function from an {{expression}}, the watcher built into that expression will take care of always using the latest value. But in some cases, you'll need to $watch the expression yourself.

Mechanics
Example

Let's say we have a component that we use whenever we want to render a search field. It takes one combined input and output, which is the search term the user has entered:

index.html
<search-field
  search-term="mainCtrl.searchTerm">
</search-field>

The component uses a two-way binding for the search term:

search_field_directive.js
module.directive('searchField', function() {
  return {
    scope: {},
    controller: 'SearchFieldCtrl',
    controllerAs: 'ctrl',
    bindToController: {
      searchTerm: '='
    },
    templateUrl: 'search_field.html'
  };
});

Inside the component template, an input field with an ng-model is bound to this search term:

search_field.html
<input type="search" ng-model="ctrl.searchTerm"/>

The effect of this is that through the two two-way bindings (the one in the ng-model and the one on the component bindings), the mainCtrl.searchTerm field is automatically updated whenever the user types something into the search input. The component controller doesn't really need to do anything:

search_field_controller.js
function SearchFieldController() {
}

It would arguably be more clear if, like in the second example of Replace State Mutation with Bound Output, the change made to the search term by this component was made explicit:

index.html
<search-field
  search-term="mainCtrl.searchTerm"
  on-search-term-change="mainCtrl.setSearchTerm(newSearchTerm)">
</search-field>

The component should not cause mainCtrl.searchTerm to be mutated. Instead it should call this bound output whenever it wants to affect the update to the outside world.

Like in the previous example, we'll explicitly name the input search term as an input on the scope bindings. We'll also change it from a two-way binding to an expression binding, so that it is in fact impossible to reassign it from within the component code. In addition to this, we'll introduce the change output handler:

search_field_directive.js
module.directive('searchField', function() {
  return {
    scope: {},
    controller: 'SearchFieldCtrl',
    controllerAs: 'ctrl',
    bindToController: {
      inputSearchTerm: '&',
      onSearchTermChange: '&'
    },
    templateUrl: 'search_field.html'
  };
});

In the controller we'll populate the search term that the ng-model is bound to, by calling the input expression binding:

search_field_controller.js
function SearchFieldController() {
  var ctrl = this;

  ctrl.searchTerm = ctrl.inputSearchTerm();
}

In the template we'll add an ng-change handler and hook it up to an internal controller method. It'll be invoked whenever the user changes the search term:

search_field.html
<input type="search"
       ng-model="ctrl.searchTerm"
       ng-change="ctrl.onChange()" />

The controller method invokes the bound output from this method:

search_field_controller.js
function SearchFieldController() {
  var ctrl = this;

  ctrl.searchTerm = ctrl.inputSearchTerm();

  ctrl.onChange = function() {
    ctrl.onSearchTermChange({newSearchTerm: ctrl.searchTerm});
  };
}


Toward Smart And Dumb Components

I'll conclude with a short discussion on a architectural direction you may want to take your app toward, once you have some components: A separation between Smart and Dumb components.

I haven't yet explored this idea fully, so I'll only descibe it on a general level.

As you apply the data flow refactorings above, the way state is managed in your application becomes visible: Looking at component boundaries you can see what data goes into them, and what changes come out of them.

Of course, not every component in your application is going to be fully defined by its inputs and outputs. Many components also talk to services: They get data from services, usually using Promises returned from asynchronous calls. They also write changes to services, also usually with asynchronous calls.

It can be useful to distinguish between components that read and write data from services and components that don't. We can adapt Dan Abramov's categorization of components into "smart" and "dumb" components for this purpose:

Dumb components are much simpler things than smart components: There's nothing asynchronous in them, and there are no service calls that need to be made. A dumb component is very easy to test since you can just instantiate one and pass in the data and functions it needs. No mocks or other setup is usually needed. A dumb component is often also easier to reuse than a smart one, since it isn't responsible for loading its own data and thus can be easily repurposed to different kinds of data sources.

This is why it is a good idea to use a dumb component whenever you can. Try coming up with a component structure with few smart components at the root, and many dumb components downward from there. Small applications may only need one smart component. Bigger ones usually have one per each route. Very complex UIs may need several smart components to keep each one from having too many responsibilities. But it is still usually a good idea to keep the ratio of smart components to dumb components small.

What you'll end up with if you follow this path to its conclusion is a one-way data flow architecture, somewhat akin to Redux and React: There are a few smart components at the root, but for the most part, your component tree is all about data flowing down, and actions flowing up. The data changes that may happen in your app are all encoded in the relatively few smart components. The paths that the data and the actions take in the component tree are all explicit in the code.

Further Reading

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