Component driven development in AngularJS

Wednesday, December 23, 2015

Lately you've probably been hearing a lot about components. All the popular frameworks are using the term (React, Angular, Polymer, ...). But why and what does it mean exactly? And are we able to use this concept in Angular 1 already? Answers to all these questions can be found in this article.

Components

Components can be the main building blocks of your application. They should only have one functionality. The main reason of using this approach is reusability, and that's important. This makes sure that we don't repeat ourselves. Another benefit is that our UI can be way more flexible with components. It's easier to reuse, maintain or replace certain parts.

Traditionally we would use basic controllers to contain the logic of mapping functions and variables to our view. But the major problem with this is that controllers are not reusable at all. They don't bring structure to your application and can soon become quite the mess.

We can replace the traditional controllers by using directives (or components in Angular 1.5). This can be quite a mindshift but it's well worth the effort.

Directives, your building blocks

How would we go and build this though? Bear with me and observe this diagram of an application that we are trying to build.

Our application is a dashboard that will contain different lanes such as 'ready', 'in progress' or 'done'.
In these lanes we will be able to view the teams that are currently in this state.

So basically quite a simple application. Normally we would probably create something like 'dashboardController', which would contact an Angular service that fetches some teams.
We would then set them on the scope (or the this context, depending on your experience with Angular) and iterate over them using a ng-repeat in the view.
But as already stated this is not the way to go for reusability. We probably can't reuse any JavaScript or HTML code.

What we will do is create components (directives) for each different object in our diagram. So that means a directive for dashboard, lane and team.

The directives will need to meet certain requirements to be considered reusable components

Isolated scope is necessary for reusability

Restrict: 'E', we only want to use the element syntax

Smart and dumb

The problem that should have crossed your minds is, which of the three components is going to contain the logic? We could have lane inject the Angular service and fetch some teams, but what if we replace the vertical lanes with another component that contains horizontal lanes. Should this other directive also inject the service?

What we want is to keep lane and team dumb, so we can reuse or replace these directives without rewriting the whole page. This means that we probably don't want any logic in lane or team.

A solution to this problem is to introduce a container component or smart component, namely 'dashboard'. This smart component will inject the Angular service and process the fetched teams as well as set them on the context.
Dashboard will transfer the teams to the contained lane directive, which will in its turn iterate over teams and transfer each team to a contained team directive.

The dataflow will be uni-directional like this:

Below you can find what this looks like in code.
The dashboard directive will inject a teamService and fetch the teams as well as set them on the context.

Communication through events

What if we want to have interaction between these components. For example, clicking on a team opens up a detail view of that team.

We certainly can't implement the logic necessary to switch to a certain detail state in the team component itself, then it wouldn't be 'dumb' anymore.

We will solve this by using the event system of Angular. Most of you will probably be familiar on how to broadcast or emit events and listen to them, but lets go through on how this works as a communication system between components.
We are still able to use emit even if the component has an isolated scope, this is because the isolated scope is still part of the scope hierarchy. As such, it can emit events to the parent scope.

So the team component will need to emit for example a 'dashboard-team-clicked' event. I try to make it a habit to start the event name with the smart component that will listen to the event.
The dashboard component will listen to this event and perform the necessary logic to open up a detail view of the specified team.

We will create a link function in the team component to be able to emit the event.

Project structure

Transforming your basic controllers to components is all nice and well, but without a proper project structure this will not yield you the maximum benefit. That is why I've reorganised my structure for new projects.

As you might see, page components are always prefixed with page-. This has been done to clearly differentiate page and normal components.

Final remarks

As mentioned applying a component driven approach in Angular 1 has many benefits.
It adds more structure to your application and your UI becomes more flexible.
As you might know, in Angular 2 you will only be able to work with components. This means it's highly valuable to already get acquainted with this way of thinking.

Also special thanks to Arne Van Raepenbusch for providing helpful feedback on the project structure and editing.