Angular Icon Get 73% off the Angular Master bundle

See the bundle then add to cart and your discount is applied.

0 days
00 hours
00 mins
00 secs

Write Angular like a pro. Angular Icon

Follow the ultimate Angular roadmap.

Stateful and stateless components, the missing manual

The goals of this article are to define what stateful and stateless components are, otherwise known as smart and dumb - or container and presentational components. For the purposes of the article, we’ll be using Angular 2 Components to explain the stateful/stateless concepts. Bear in mind these concepts are not at all limited to Angular, and live in other libs/frameworks such as React.

Terminology

Before we begin, let’s clarify what “stateful” and “stateless” really mean in programming terms.

Stateful

When something is “stateful”, it is a central point that stores information in memory about the app/component’s state. It also has the ability to change it. It is essentially a “living” thing that has knowledge of past, current and potential future state changes.

Stateless

When something is “stateless”, it calculates its internal state but it never directly mutates it. This allows for complete referential transparency meaning that given the same inputs, it will always produce the same output. These are not essentially “living” as they are merely passed information. This means it has no knowledge of the past, current or future state changes.

Components

When we talk about stateful and stateless in web application development, we can begin to apply these concepts to component paradigms. So what is a component? A component is an isolated piece of behaviour or functionality that allows us to divide behaviour into roles, much like we would with JavaScript functions.

Impure versus Pure functions

When we think about stateful and stateless components, we can ignore any framework entirely until it comes to implementation and think about JavaScript functions. Let’s first consider pure versus impure functions, and somewhat compare them to stateful and stateless. I really like comparing component types to functions to better understand UI composition.

In my eyes, after reading this you’ll match up:

Check out my Pure versus Impure functions article for more depth, but we’ll cover the basics here.

Impure functions (stateful)

Let’s consider the following code from the above article, which parses the user’s weight and height values into Number types, then calculates the bmi (Body Mass Index) based on the formula.

const weight = parseInt(form.querySelector('input[name=weight]').value, 10);
const height = parseInt(form.querySelector('input[name=height]').value, 10);

const bmi = (weight / (height /100 * height / 100)).toFixed(1);

This is great in terms of the fact that it works, however this doesn’t create a reusable function that allows us to calculate the BMI elsewhere, test the formula easily, and relies on very procedural code. Let’s look at how we can make it “pure”. The important piece here is that this chunk of code is the driver of changes, and we can rely on pure functions to essentially create small isolated pieces of logic that accept data and return new data without relying on external variables.

Pure functions (stateless)

When we think about pure functions, we would be able to expect the same result every time, without the knowledge of lexical constant values such as weight or height. Let’s refactor the formula to be a pure function:

const weight = form.querySelector('input[name=weight]').value;
const height = form.querySelector('input[name=height]').value;

const getBMI = (weight, height) => {
  let newWeight = parseInt(weight, 10);
  let newHeight = parseInt(height, 10);
  return (newWeight / (newHeight /100 * newHeight / 100)).toFixed(1);
};

const bmi = getBMI(weight, height);

The getBMI function can easily live elsewhere, not necessarily in the same function (as this example is inside the onSubmit) function if you check the other article. Now the function is pure, it can be defined better in terms of “why”. The function has several attributes:

Here’s the thing: all four of the above concepts directly map across to thinking about stateless components.

Let’s now take the “impure” functions and look at the stateful component equivalent, followed by the “pure” functions that can be mapped across to stateless components.

Stateful components

Much like an impure JavaScript function, a stateful component is the driver of what happens, and it therefore utilises any stateless components at our disposal.

Here are some attributes that a stateful component has:

This list, and the one further in the article, was inspired by Dan Abramov’s Presentational and Container components article.

Stateful Todo component

In this article, we’re going to build a small todo application demonstrating these concepts, followed by their stateless counterparts.

First, let’s start off by rendering our base component, the ` to kick things off:

import { Component } from '@angular/core';

@Component({
  selector: 'my-app',
  template: `
    <todos></todos>
  `
})
export class AppComponent { }

Inside here, we’re rendering a <todos> component. This component will be stateful, let us continue! We’re not going to teach how to build a todo app, we all know this, so we’re going to look at how we can apply stateful and stateless paradigms to Angular 2 components and observe the ideas.

Angular Directives In-Depth eBook Cover

Free eBook

Directives, simple right? Wrong! On the outside they look simple, but even skilled Angular devs haven’t grasped every concept in this eBook.

  • Green Tick Icon Observables and Async Pipe
  • Green Tick Icon Identity Checking and Performance
  • Green Tick Icon Web Components <ng-template> syntax
  • Green Tick Icon <ng-container> and Observable Composition
  • Green Tick Icon Advanced Rendering Patterns
  • Green Tick Icon Setters and Getters for Styles and Class Bindings

Let’s look at the component composition through ASCII art as we progress, so far we have an app component:

          ┌─────────────────┐          
          │      <app>      │          
          └─────────────────┘          

Now onto the <todos> component:

import { Component, OnInit } from '@angular/core';
import { TodoService } from './todo.service';

@Component({
  selector: 'todos',
  template: `
  <div>
    <todo-form
      (onAdd)="addTodo($event)">
    </todo-form>
    <todo-list
      [todos]="todos"
      (onComplete)="completeTodo($event)"
      (onDelete)="removeTodo($event)">
    </todo-list>
  </div>  
  `
})
export class TodosComponent implements OnInit {
  todos: any[];
  constructor(private todoService: TodoService) {}
  ngOnInit() {
    this.todos = this.todoService.getTodos();
  }
  addTodo({label}) {
    this.todos = [{label, id: this.todos.length + 1}, ...this.todos];
  }
  completeTodo({todo}) {
    this.todos = this.todos.map(
      item =&gt; item.id === todo.id ? Object.assign({}, item, {complete: true}) : item
    );
  }
  removeTodo({todo}) {
    this.todos = this.todos.filter(({id}) =&gt; id !== todo.id);
  }
}

You can see from the above that all we have is a container <div> wrapping two further child (stateless) components. There is no other logic in the template other than that. The <todo-form> component receives no input, but expects to bind an output called onAdd. Next up, the <todo-list> component receives the todos data from the [todos] input binding, and two outputs (onComplete) and (onDelete), delegating the respective functions to the stateless counterparts.

The rest of the component class are the methods that make up the functionality of the todo component. Immutable operations are taking place inside each callback, and each callback is exposed to a stateless component so that it can run with it. All these functions are expecting is a notification that something has changed, for instance “Oh hey! Here’s a new todo label, go ahead and do your thing with it oh-mighty stateful component”. Note how the functions are only called from the child, stateless, level.

And that’s literally it on stateful. We cover some of the potential concepts that stateful components may contain. Let’s move onto the more frequently used stateless components.

ASCII (the TodoService represents the injected Service):

          ┌─────────────────┐          
          │      <app>      │          
          └────────┬────────┘          
                   ▼                   
     ┌─────────────────────────────┐    
     │           <todo>            │    
     │     ┌─────────────────┐     │    
     │     │   TodoService   │     │   
     └─────┴─────────────────┴─────┘

Stateless components

Much like a pure JavaScript function, a stateless component isn’t aware of “lexical” variables, in the fact that it receives data via property binding (equivalent to function arguments) and emits changes via an event (equivalent to a return block).

So what does this mean? Well, based on how function scope chains work, this means that stateless components have no knowledge of any part of the application they’re apart of. Which means they can be reused, easily tested and moved around very easily.

Here are some attributes that a stateless component has:

Stateless TodoForm component

Before we begin with this component, we need to understand that it’s a special kind of stateless component in the fact that it retrieves user input, and therefore contains UI state only:

import { Component, Output, EventEmitter } from '@angular/core';

@Component({
  selector: 'todo-form',
  template: `
  <form (ngSubmit)="submit()">
    <input name="label" [(ngModel)]="label">
    <button type="submit">Add todo</button>
  </form>
  `
})
export class TodoFormComponent {
  label: string;
  @Output() onAdd = new EventEmitter();
  submit() {
    if (!this.label) return;
    this.onAdd.emit({label: this.label});
    this.label = '';
  };
}

This component also doesn’t receive any data via property binding, and that’s perfectly acceptable. The role this component plays is to capture the label of a new todo item, which is illustrated inside the submit function. This is a special use case for stateless components that have a function inside to capture UI state and do something with it.

Stateless TodoList component

Let’s look at the second stateless component we have, a direct child of <todos>:

import { Component, Input, Output, EventEmitter } from '@angular/core';

@Component({
  selector: 'todo-list',
  template: `
  <ul>
    <li *ngFor="let todo of todos">
      <todo
        [item]="todo"
        (onChange)="onComplete.emit($event)"
        (onRemove)="onDelete.emit($event)">
      </todo>
    </li>
  </ul>
  `
})
export class TodoListComponent {
  @Input() todos;
  @Output() onComplete = new EventEmitter();
  @Output() onDelete = new EventEmitter();
}

Our @Input and @Output is well defined here, and as you can see, nothing else exists on this component class. We’re actually creating an EventEmitter instance for each output, and also delegating this down into further stateless components, in this case the single <todo> component, which will render each todo in our collection. We also delegate the onComplete and onDelete methods here, which are also bound to the parent, creating a basic chain. Let’s look inside <todo> and we’re done:

import { Component, Input, Output, EventEmitter } from '@angular/core';

@Component({
  selector: 'todo',
  styles: [`
    .complete { text-decoration: line-through; }
  `],
  template: `
  <div>
    <span [ngClass]="{ complete: item.complete }">{{ item.label }}</span>
    <button
      type="button"
      (click)="onChange.emit({ todo: item });">Done</button>
    <button
      type="button"
      (click)="onRemove.emit({ todo: item });">Delete</button>
  </div>
  `
})
export class TodoComponent {
  @Input() item;
  @Output() onChange = new EventEmitter();
  @Output() onRemove = new EventEmitter();
}

Hopefully you can see a pattern emerging here! Again, we have some inputs and outputs that can send event information up to the parent, then up again (if needed). All of the above Angular 2 components are stateless. They have no knowledge of their surroundings, but are passed data via property bindings and emit changes via event callbacks.

Here’s the final ASCII render of the component tree that we’ve talked through:

          ┌─────────────────┐          
          │      <app>      │          
          └────────┬────────┘          
                   ▼                   
     ┌─────────────────────────────┐    
     │           <todos>           │    
     │     ┌─────────────────┐     │    
    ┌┤     │   TodoService   │     ├┐   
    │└─────┴─────────────────┴─────┘│   
┌──▼──────────────┐  ┌──────────────▼──┐
│   <todo-form>    │ │   <todo-list>   │
└──────────────────┘ └──────────────┬──┘
                     ┌──────────────▼──┐
                     │     <todo>      │
                     └─────────────────┘

Final code

Altogether now:

AngularJS 1.x version?

Oh why not…

Full 1.x implementation

Here’s the full source code for the AngularJS 1.x version (obviously in a real app we’d use ES6 import and export statements etc):

const todos = {
  template: `
    <div>
      <todo-form
        new-todo="$ctrl.newTodo"
        on-add="$ctrl.addTodo($event);">
      </todo-form>
      <todo-list
        todos="$ctrl.todos"
        on-complete="$ctrl.completeTodo($event);"
        on-delete="$ctrl.removeTodo($event);">
      </todo-list>
    </div>
  `,
  controller: class TodoController {
    constructor(TodoService) {
      this.todoService = TodoService;
    }
    $onInit() {
      this.todos = this.todoService.getTodos();
    }
    addTodo({ label }) {
      this.todos = [{ label, id: this.todos.length + 1 }, ...this.todos];
    }
    completeTodo({ todo }) {
      this.todos = this.todos.map(
        item =&gt; item.id === todo.id ? Object.assign({}, item, { complete: true }) : item
      );
    }
    removeTodo({ todo }) {
      this.todos = this.todos.filter(({ id }) =&gt; id !== todo.id);
    }
  }
};

const todoForm = {
  bindings: {
    onAdd: '&amp;'
  },
  template: `
   <form ng-submit="$ctrl.submit();">
      <input ng-model="$ctrl.label">
      <button type="submit">Add todo</button>
    </form>
  `,
  controller: class TodoFormController {
    constructor() {}
    submit() {
      if (!this.label) return;
        this.onAdd({
        $event: { label: this.label }
      });
      this.label = '';
    };
  }
};

const todoList = {
  bindings: {
    todos: '&lt;&#039;,
    onComplete: &#039;&amp;&#039;,
    onDelete: &#039;&amp;&#039;
  },
  template: `
     <ul>
      <li ng-repeat="todo in $ctrl.todos">
        <todo
          item="todo"
          on-change="$ctrl.onComplete($locals);"
          on-remove="$ctrl.onDelete($locals);">
        </todo>
      </li>
    </ul>
  `
};

const todo = {
  bindings: {
    item: '&lt;&#039;,
    onChange: &#039;&amp;&#039;,
    onRemove: &#039;&amp;&#039;
  },
  template: `
    <div>
      <span ng-class="{ complete: $ctrl.item.complete }">{{ $ctrl.item.label }}</span>
      <button
        type="button"
        ng-click="$ctrl.onChange({ $event: { todo: $ctrl.item } });">Done</button>
      <button
        type="button"
        ng-click="$ctrl.onRemove({ $event: { todo: $ctrl.item } });">Delete</button>
    </div>
  `
};

class TodoService {
  constructor() {}
  getTodos() {
    return [{
      label: 'Eat pizza',
      id: 0,
      complete: true
    },{
      label: 'Do some coding',
      id: 1,
      complete: true
    },{
      label: 'Sleep',
      id: 2,
      complete: false
    },{
      label: 'Print tickets',
      id: 3,
      complete: true
    }];
  }
}

angular
  .module('app', [])
  .component('todos', todos)
  .component('todo', todo)
  .component('todoForm', todoForm)
  .component('todoList', todoList)
  .service('TodoService', TodoService);

Further reading

If you need some further heads up on @Input, @Output and EventEmitter work, check my @Input and @Output and EventEmitter articles.

Learn Angular the right way.

The most complete guide to learning Angular ever built.
Trusted by 82,951 students.

Todd Motto

with Todd Motto

Google Developer Expert icon Google Developer Expert

Related blogs 🚀

Free eBooks:

Angular Directives In-Depth eBook Cover

JavaScript Array Methods eBook Cover

NestJS Build a RESTful CRUD API eBook Cover