Logo

dev-resources.site

for different kinds of informations.

Mastering the Container-Presenter Pattern in Angular: A Deep Dive

Published at
12/19/2024
Categories
angular
patterns
container
presenter
Author
bndf1
Author
5 person written this
bndf1
open
Mastering the Container-Presenter Pattern in Angular: A Deep Dive

In the world of Angular development, structuring your components effectively can make a significant difference in the maintainability and scalability of your application. Today, let's explore a powerful pattern that has become a cornerstone in many Angular projects: the Container-Presenter pattern.

🧩 What is the Container-Presenter Pattern?
The Container-Presenter pattern, also known as Smart and Dumb Components or Stateful and Stateless Components, is a design pattern that promotes a clear separation of concerns in your Angular components. Let's break it down:

  1. Container (Smart) Components: These components are responsible for how things work. They manage state, fetch data, and contain business logic.
  2. Presenter (Dumb) Components: These components are responsible for how things look. They receive data via inputs and emit events via outputs, focusing solely on the presentation.

🌟 Why Use This Pattern?

  • Improved Testability: Presenter components are easier to test as they don't depend on services or state management.
  • Enhanced Reusability: Presenter components can be reused across different parts of your application.
  • Better Separation of Concerns: Each component has a clear, single responsibility.
  • Easier Maintenance: Changes to business logic or data fetching don't affect the presentation layer, and vice versa.

💻 Real-World Example
Let's look at a real-world example from a podcast application I've been working on:

Container Component:

@Component({
  selector: 'app-podcasts-container',
  standalone: true,
  imports: [PodcastItemComponent, SkeletonComponent],
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    @let podcasts = store.podcasts();
    @defer (when podcasts) {
      <h2
        class="text-2xl font-semibold text-center text-gray-800 capitalize lg:text-3xl dark:text-white"
      >
        All Podcasts
      </h2>
      <div class="grid gap-8 mt-8 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
        @for (podcast of podcasts; track podcast.id) {
          @defer (on viewport) {
            <app-podcast-item [podcast]="podcast" />
          } @placeholder {
            <app-skeleton [type]="'CARD'" />
          } @loading {
            <app-skeleton [type]="'CARD'" />
          }
        }
      </div>
    }
  `,
})
export class PodcastsContainerComponent {
  store = inject(PodcastStore);
}
Enter fullscreen mode Exit fullscreen mode

This container component:

  • Injects the PodcastStore to access the application state
  • Uses the @defer directive for lazy loading and better performance
  • Iterates over the podcasts and renders PodcastItemComponent for each

Presenter Component:

@Component({
  selector: 'app-podcast-item',
  standalone: true,
  imports: [NgOptimizedImage],
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    @if (podcast(); as podcast) {
      <div
        class="overflow-hidden bg-white rounded-lg shadow-lg dark:bg-gray-800"
      >
        <div class="px-4 py-2">
          <h1 class="text-xl font-bold text-gray-800 uppercase dark:text-white">
            {{ podcast.title }}
          </h1>
          <p class="mt-1 text-sm text-gray-600 dark:text-gray-400">
            {{ podcast.description }}
          </p>
        </div>
        <img
          class="object-cover w-full h-48 mt-2"
          [src]="podcast.image"
          [alt]="podcast.title"
        />
        <div class="flex items-center justify-between px-4 py-2 bg-gray-900">
          <h1 class="text-lg font-bold text-white">{{ podcast.rating }}</h1>
          <button
            class="px-2 py-1 text-xs font-semibold text-gray-900 uppercase transition-colors duration-300 transform bg-white rounded hover:bg-gray-200 focus:bg-gray-400 focus:outline-none"
          >
            Subscribe
          </button>
        </div>
      </div>
    }
  `,
})
export class PodcastItemComponent {
  podcast = input.required<Podcast>();
}
Enter fullscreen mode Exit fullscreen mode

This presenter component:

  • Receives a podcast input
  • Focuses solely on rendering the podcast information
  • Has no knowledge of where the data comes from or how it's managed

🎯 Benefits in Action

  1. Reusability: The PodcastItemComponent can be easily reused in other parts of the application, such as a "Featured Podcasts" section or a search results page.
  2. Testability: Testing the PodcastItemComponent is straightforward as we can simply pass in mock podcast data and assert on the rendered output.
  3. Separation of Concerns: The PodcastsContainerComponent handles data fetching and state management, while PodcastItemComponent focuses purely on presentation.
  4. Performance: By using ChangeDetectionStrategy.OnPush, we optimize change detection for both components.

🚀 Conclusion
The Container-Presenter pattern is a powerful tool in an Angular developer's arsenal. It promotes clean, maintainable, and scalable code by clearly separating concerns between data management and presentation.

As you structure your Angular applications, consider how this pattern can benefit your project. It may require a bit more initial setup, but the long-term benefits in terms of maintainability, testability, and scalability are well worth it.

💬 Have you used the Container-Presenter pattern in your Angular projects? What benefits or challenges have you encountered? Let's discuss in the comments!

container Article's
30 articles in total
Favicon
How to run a Nginx-web server
Favicon
Docker Basics
Favicon
What is Kubernetes Vs Terraform
Favicon
It is time to express your intention ,before you really code
Favicon
Docker Hands-on: Learn Docker Volume and Bind Mounts with Sample Projects using NGINX
Favicon
Can I start and stop Docker Desktop using CLI?
Favicon
The Power of Containers: Why Docker is Essential in Cloud, AI, Software Engineering and DevOps
Favicon
Docker Tutorial and Easy Guide to Master Dockerfile, Images, Containers, Commands, Volume, Network, and Compose
Favicon
Mastering the Container-Presenter Pattern in Angular: A Deep Dive
Favicon
Terraform: Use Template file for AWS CodeDeploy AppSpec file
Favicon
Building a PSR-11 Compatible Dependency Injection Container with PHP 8.4 Lazy Objects
Favicon
PnR: Configuration-Intention Driven Container Orchestration with Go's Platform Abstraction
Favicon
Why Rootless Containers Matter: A Security Perspective
Favicon
How to Install Tailscale in a Proxmox CE 8.2 LXC Container (AlmaLinux 9)
Favicon
Create a container using the Ubuntu image in Docker.
Favicon
Kubernetes คืออะไร? แบบ Dev เห็นภาพ
Favicon
A brief breakdown of Kubernetes architecture
Favicon
Docker Image Optimization: Reducing Size for Faster Deployments
Favicon
Docker
Favicon
Dockerfile Anti-Patterns: What Not to Do
Favicon
Docker Layer Caching Explained: Tips to Improve Build Times
Favicon
Homemade application firewall for Linux
Favicon
Kubernetes: Introduction
Favicon
Pod Security with K8Studio
Favicon
Docker ARG vs ENV: Understanding Build-time and Runtime Variables
Favicon
Containerize Rust Application in 2 Minutes using Docker Init
Favicon
What is a Container Registry
Favicon
How to Use Docker to Improve Your Development Workflow: A Complete Guide
Favicon
Effortlessly Dockerize Your Vite-React Application
Favicon
Docker Networking every developer should know

Featured ones: