When we start building apps with UI frameworks like Angular, the most frequent questions that we are faced with right at the beginning is: how do we structure our application?

  • what types of components are there?
  • how should components interact?
  • should I inject services into any component?
  • how do I make my components reusable across views?

A very common concept is the splitting of Angular Components into Smart and Dumb Components ( also know sometimes as container components and  Presentation Components).

Smart VS Dumb Component

Let’s find out the differences between these two types of components, and when should we use each and why!

  • a smart component can have external dependencies (Dependencies Injection) and may trigger side effects (e.g. API calls)
  • a Dumb Component has no external dependencies and produces no side effects. Communication with the parent component should only be made trough @Input() and @Output() decorators.

Example

In order to understand the difference between the two types of components, let’s start with a simple application, where the separation is not yet present.

We started building the Home screen of an application and we have added multiple features to a single template:

//home.ts

@Component({
  selector: 'app-home',
  template: `
    <h2>All Lessons</h2>
    <h4>Total Lessons: {{lessons?.length}}</h4>
    <div>
        <table class="table lessons-list card card-strong">
            <tbody>
            <tr *ngFor="let lesson of lessons" (click)="selectLesson(lesson)">
                <td class="lesson-title"> {{lesson.description}} </td>
                <td class="duration">
                    <i class="md-icon duration-icon">access_time</i>
                    <span>{{lesson.duration}}</span>
                </td>
            </tr>
            </tbody>
        </table>
    </div>
`,
  styleUrls: ['./home.component.css']
})
export class HomeComponent implements OnInit {

    lessons: Lesson[];

  constructor(private lessonsService: LessonsService) {
  }

  ngOnInit() {
      this.lessonsService.findAllLessons()
          .pipe(
              tap(console.log)
           )
          .subscribe(
              lessons => this.lessons = lessons
          );
  }

  selectLesson(lesson) {
    ...
  }

}

Understanding the Problem

Although this Homepage component is still very simple, it’s already starting to have a significant size. For example, we have implemented a table containing a list of lessons.

But there will likely be other parts of the application where this functionality is also needed, for example let’s say that we have another screen which presents the table of contents of a given course.

In that screen we also want to display a list of lessons, but only the lessons that belong to that course. In that case, we would need something very similar to what we have implemented in the Home screen.

And we shouldn’t just copy-paste this across components, we should create a reusable component, right?

Let’s create a Presentation Component

What we will want to do in this situation is to extract the table part of the screen into a separate component, let’s call it the LessonsListComponent:

//lesson-list.ts
import {Component, OnInit, Input, EventEmitter, Output} from '@angular/core';
import {Lesson} from "../shared/model/lesson";

@Component({
  selector: 'lessons-list',
  template: `
      <table class="table lessons-list card card-strong">
          <tbody>
          <tr *ngFor="let lesson of lessons" (click)="selectLesson(lesson)">
              <td class="lesson-title"> {{lesson.description}} </td>
              <td class="duration">
                  <i class="md-icon duration-icon">access_time</i>
                  <span>{{lesson.duration}}</span>
              </td>
          </tr>
          </tbody>
      </table>  
  `,
  styleUrls: ['./lessons-list.component.css']
})
export class LessonsListComponent {

  @Input()
  lessons: Lesson[];

  @Output('lesson')
  lessonEmitter = new EventEmitter<Lesson>();

    selectLesson(lesson:Lesson) {
        this.lessonEmitter.emit(lesson);
    }

}

Now let’s take a closer look at this component: it does not have the lessons service injected into it via its constructor. Instead, it receives the lessons in an input property via @Input.

This means that the component itself does not know where the lessons come from:

  • the lessons might be a list of all lessons available
  • or the lessons might be a list of all the lessons of a given course
  • or even the lessons might be a page in any given list of a search

We could reuse this component in all of these scenarios, because the lessons list component does not know where the data comes from. The responsibility of the component is purely to present the data to the user and not to fetch it from a particular location.

This is why we usually call to this type of component a Presentation or Dumb Component.

Let’s create a Smart Component

what happened to our Home Component? This is what it will look like after refactoring:

//home.ts

import { Component, OnInit } from '@angular/core';
import {LessonsService} from "../shared/model/lessons.service";
import {Lesson} from "../shared/model/lesson";

@Component({
  selector: 'app-home',
  template: `
      <h2>All Lessons</h2>
      <h4>Total Lessons: {{lessons?.length}}</h4>
      <div>
          <lessons-list [lessons]="lessons" (lesson)="selectLesson($event)"></lessons-list>
      </div>
`,
  styleUrls: ['./home.component.css']
})
export class HomeComponent implements OnInit {

    lessons: Lesson[];

  constructor(private lessonsService: LessonsService) {
  }

  ngOnInit() {
     ...
  }

  selectLesson(lesson) {
    ...
  }

}

As we can see, we have replaced the list part of the Home screen with our new reusable lessons list component. The home component still knows how to retrieve the lessons list from a service, and what type of list this is (if these lessons are the lessons of a course, etc.).

But the Home component does not know how to present/display the lessons to the user.

This type of component is inherently tied to the application itself, so as we can see it receives in the constructor some dependencies that are application-specific, like the LessonsService.

PS: It would be very hard to use this component in another application.

Summary

Smart vs Presentation Components is more of a mindset to adopt where we ask ourselves:

  • would this presentation logic be useful elsewhere in the application?
  • would it be useful to split things up further?
  • are we creating unintended tight couplings in the application?
Reference:

https://blog.angular-university.io/

By Shabazz

Software Engineer, MCSD, Web developer & Angular specialist

Leave a Reply

Your email address will not be published. Required fields are marked *