State

Components demystified

These documents are the beginning of a book (or a collection of documents) that will explain SPA-principles in depth. SPA’s (single-page-apps) offer us the opportunity to create responsive userfriendly webapplications that have a native look-and-feel. However, with great power comes great responsabi

Components demystified — State article by brechtbilliet on Angular In Depth
Components demystified — State article by brechtbilliet on Angular In Depth
On this page · 16 sections

Table of Contents

  1. Foreword
  2. Base concept
  3. The hierarchical component-tree
  4. Smart vs dumb components
  5. Best practices

Foreword

This series of documents marks the start of a book—or possibly a set of documents—that will break down the inner workings of single-page applications in depth. SPAs give us the ability to craft responsive, user-friendly web apps with a native feel. But that capability comes with a catch: managing these applications as they grow—and they almost always do—is no trivial task. By following the ideas laid out in 'spa concepts demystified', you should find building large-scale applications to be remarkably straightforward.

The focus here is on components: what they are, and the reasoning behind the decisions that shape their design.

Base concept

These days, "components" is a term you hear constantly in the world of SPAs. The idea itself has been around for a while, but it truly gained traction once React (Facebook's SPA framework) entered the scene, and since then, most modern SPA frameworks have adopted it as their core building block.

At its heart, a component is a small piece of a larger whole—like a single part of an application. The word gets thrown around loosely, but in the context of SPA technology, it has a precise meaning.

It’s the marriage of a snippet of HTML with a snippet of logic, working together toward a specific purpose.

Every component has a goal, a responsibility, and deciding what that responsibility should be is crucial. This is essentially the separation-of-concerns-principle in action. Components are small, focused units that come together to form the presentation layer of your SPA.

On a technical level: It’s a custom DOM tag. This custom tag can carry its own behavior or functionality and have a specific look. Modern SPA frameworks give us the tools to define these custom DOM tags. And just like any native DOM element, a component can accept custom attributes, which serve as a way to pass data in.

Note: Additionally, components can include their own custom stylesheets.

Example: The native anchor-tag

Consider the standard anchor-tag:

<a href="http://brecht.io">my link</a>
Functionality
Clicking it causes the browser to navigate to the specified URL.
Presentation
The anchor tag’s content is styled in a distinct way, complete with native hover effects.

Example: A custom rating-tag

<rating value="5"></rating>
Functionality
This generates five stars, with the number of selected stars determined by the value passed in. Clicking any star updates the value of the provided parameter.
Presentation
Hovering over a star highlights it, signaling to the user that it’s interactive.

In a single-page application, a custom component usually expands into additional HTML. The rating component above might render something like this:

<rating>
	<i class="fa fa-star rating fa-2x starred"></i>
        <i class="fa fa-star rating fa-2x starred"></i>
        <i class="fa fa-star rating fa-2x starred"></i>
        <i class="fa fa-star rating fa-2x starred"></i>
        <i class="fa fa-star rating fa-2x starred"></i>
</rating>

The hierarchical component-tree

Most contemporary frameworks push for a component-tree structure. Put simply, your whole application is a tree of components, layered hierarchically. This means your pages become individual components, and it doesn’t stop there—the application as a whole ends up being a component itself. Yes! Every visual piece of your application is a component. There is no room for standalone templates or controllers on their own.

Structuring your app as a single hierarchical tree comes with a clear set of benefits:

  • You can easily sketch out a component-tree
  • The syntax remains declarative
  • Debugging is simpler, both in the browser and in your code editor
  • It’s easier to reason about how your application fits together
  • It establishes a clear structure, which lessens complexity when done correctly
  • No more guessing which controller pairs with which view
  • Since each parent-child relationship is explicit, more efficient change detection is achievable (covered later)
  • It sets the stage for Shadow DOM usage
  • You get a consistent language to use within your team, which helps with task distribution

Smart vs dumb components

When you’re organizing your components, drawing a line between dumb and smart ones is a smart move in itself. Dumb components—sometimes labeled presentational—are just that: they don’t have any awareness of the wider app. Smart components, also known as containers, usually act as the interface to the rest of the application. If you’re using a unidirectional dataflow setup, like in React or Angular2, this split can make structuring your code much more manageable.

Let’s lay out some ground rules.

Dumb components

  • They have no knowledge of the application’s inner workings
  • They won’t redirect users to other sections of the app
  • Their only means of communication is with their immediate parent
  • Typically, they have no injected dependencies, though it’s not ruled out
  • They can nest other components within them
  • They do not fetch data
  • The logic they contain is limited to their own concerns
  • Their state and data arrive via attributes
  • They don’t directly alter the parameters they receive
  • They are built to be reused

Here’s what a dumb version of our rating component would look like:

<rating value="5" on-set-value="setRating(newRating)"></rating>

And the corresponding Angular2 implementation might be:

<rating [value]="5" (setValue)="setRating($event)"></rating>

Dumb components are straightforward to understand and can often be set aside when you’re thinking through your application’s overall flow. So keep this in mind as the primary guideline: Favor dumb components whenever you can! They cut down on complexity and make your application easier to get your head around.

Smart components

  • They are aware of the application’s data and state, but aren’t concerned with how it’s stored or managed
  • They commonly have an interface designed for communicating with the wider app
  • They pass state and data down to their child components
  • Even they avoid changing state directly

Best practices

Sketch them out and assign responsibilities early

Since components make up the entire presentation layer, mapping out their structure is worth doing before you dive into code. You should also decide which components will be smart and which will be dumb in advance. My advice: grab a pen and paper or head to the whiteboard, and sketch a component tree for each page you plan to build. This helps you sort out which component should own which piece of state.

Aim for small components

Keeping your components compact gives you a number of distinct advantages:

  • Smaller components are simpler to manage—the less code, the clearer it is what the component does
  • They’re less of a hassle when it comes to maintenance
  • They align well with the single-responsibility principle, where each component has a focused task
  • That focused responsibility makes them easier to test
  • Troubleshooting is straightforward
  • In some frameworks, you get finer control over change detection, re-rendering only the parts that need it
  • You can hand out individual tasks to developers, who can work in isolation on their own components
  • Component names become a sort of shared jargon within the development team

Isolate components from the rest of the app

If you’ve been following along, you’ll know this advice doesn’t target dumb components—they don’t interact with the rest of the application beyond their parent and children. Containers, the smart components, are aware of the app, but that doesn’t mean they should get free rein to access anything they want.

A better approach is to build an abstraction layer that shields your containers. This way, the component tree—your presentation layer—stays decoupled from everything else. Consider it: containers shouldn’t need to know how state management is wired up or how data is fetched and processed. And in software, a component that knows less is a component that’s easier to manage.

One way to do this is with a sandbox that exposes only the specific properties of your state that the container needs, along with the functions it relies on to alter state or talk to the backend. This abstraction should stay thin, keeping the actual logic where it belongs—inside the container itself. That’s its job, after all.

Communication rules are a must

A dumb component should only talk to its parent and its own children. The same applies to smart components, with one caveat: smart ones also have an API that reaches out to the rest of the application. That’s precisely what makes them smart.

The key win here is that you never have to worry about tracking who notifies whom. The structure dictates it.

Stay dumb whenever you can

The more dumb components in your application, the easier it becomes to keep it running smoothly. Having a large share of dumb components brings several benefits:

  • Fewer smart components means fewer abstractions, since each container needs its own API to the app
  • Dumb components are significantly easier to reason about
  • Since they avoid modifying state or data, they reduce complexity
  • Assigning a clear responsibility to a dumb component is a no-brainer
  • Testing them is simpler, as they have fewer dependencies

Keep templates inline

Put the HTML for a component in the same file as its JavaScript. Wait, what? What about separation of concerns? Well, the concern here is that the component fulfills its purpose, and it does so by combining its HTML and JS. Keeping them together has its perks:

  • You avoid switching files back and forth while working on a component
  • No need for brittle absolute paths pointing to template files
  • Fewer HTTP requests for templates—they’re already loaded when the app starts

Note: This works only if the component isn’t too large—but, of course, you’re keeping them small anyway.

B
brechtbilliet

Writes about RxJS, Components, State. Active 2016–2022.

All 22 articles →