Presentational & Container Components in Angular

In organizing increasingly complex software systems, one normally breaks the complexity down into smaller, more manageable and maintainable units. Within the object-oriented programming paradigm, large classes and methods are refactored into smaller methods and classes to reveal intention to developers, making it easier to understand and make changes. Within a functional paradigm, larger functions can be broken down into smaller functions that 'do one thing'.

In a similar fashion, Angular components can be broken down into smaller components that 'do one thing'. An approach developers sometimes take is for one component to represent a 'page', and to extract common pieces into their own components in instances where one is avoiding duplication. An alternative approach for component separation from the React community is to separate components into presentational components and container components:

  • Container Components (sometimes called smart of fat components) handle the application logic, and the interaction and orchestration with other services which fulfill the logic requirements of the application. They will typically have a constructor with the dependencies that allow for the fulfillment of application logic requirements (e.g. services, etc).
  • Presentational Components (sometimes called dumb or skinny components) handle how information is displayed to the user, and how information is received from the user. They typically have no state besides the information they display, which is received through @Input() properties. They also have no dependencies, and send information to the parent components through the @Output() event emitters. This means that they typically have no visible constructor, as all inputs are passed in from the parent component.

How does this look in practice?

Imagine the very common example of a task-list. A full component where one can add tasks, and have the tasks displayed may look like this:

// app.component.html

<div>  
  <input type="text" (change)="titleChanged($event.target.value)" />
  <input type="text" (change)="descriptionChanged($event.target.value)" />
  <button (click)="addTaskItem()">Add New Item</button>
</div>

<div>  
  <div *ngFor="let task of taskList">
    <h4>{{ task.title }}</h4>
    <p>{{ task.description }}</p>
  </div>
</div>

// app.component.ts

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {  
  title;
  description;
  taskList = [];


  titleChanged(newTitle) {
    this.title = newTitle;
  }

  descriptionChanged(newDescription) {
    this.description = newDescription;
  }

  addTaskItem() {
    this.taskList.push({ title: this.title, description: this.description });
  }
}

This looks simple enough, but the UI can be separated into smaller presentational components, while keeping the above components. Firstly, one can have a separate component for the input form:

@Component({
  selector: 'app-task-input',
  template: `
  <div>
    <input type="text" (change)="titleChanged($event.target.value)" />
    <input type="text" (change)="descriptionChanged($event.target.value)" />
    <button (click)="addTaskItem()">Add New Item</button>
  </div>
  `
})
export class TaskInputComponent {  
  title;
  description;
  @Output() taskAdded = new EventEmitter();

  titleChanged(newTitle) {
    this.title = newTitle;
  }

  descriptionChanged(newDescription) {
    this.description = newDescription;
  }

  addTaskItem() {
    this.taskAdded.emit({ title: this.title, description: this.description })
  }
}

The task items can also be made into separate presentational components as follows:

@Component({
  selector: 'app-task-item',
  template: `
    <div>
      <h4>{{ task.title }}</h4>
      <p>{{ task.description }}</p>
    </div>
  `
})
export class TaskItemComponent {  
  @Input() task;
}

In the end, the main orchestrating container component looks like this:

@Component({
  selector: 'app-root',
  template: `
    <app-task-input (taskAdded)="addTaskItem($event)"></app-task-input>
    <app-task-item *ngFor="let task of taskList" [task]="task"></app-task-item>
  `,
  styleUrls: ['./app.component.css']
})
export class AppComponent {  
  taskList = [];

  addTaskItem(task) {
    this.taskList.push(task);
  }
}

The purpose of the container component is not how things look, or how data is received from the user, but orchestration. The purpose of the presentational component is not any business or application logic, but presenting data to, and collecting data from the user. This gives each component only one reason to change (single responsibility principle), i.e. when the way data is presented to the user changes (in the case of presentational components), and when the application and orchestration logic changes (in the case of container components).

Patrick Kayongo

I create and maintain software. Pan-African.

Johannesburg, South Africa