Software Architecture

Mastering Angular Reactive Architecture: Combining Angular, NgRx, and DDD

Reactive architecture in Angular leverages NgRx for state management and RxJS for event-driven workflows, ensuring a scalable, maintainable, and high-performance frontend. By following a unidirectional data flow, it eliminates unpredictable state changes and improves efficiency. This architecture consists of three layers: the domain layer for business logic, the feature layer for smart components, and the UI layer for presentation. Optimized state management with selectors, effects, and OnPush change detection reduces unnecessary re-renders and boosts performance. Companies like Netflix and Google use similar principles for real-time applications. Adopting Angular Reactive Architecture helps teams build future-proof applications with clear separation of concerns and efficient state handling.

What is Reactive Architecture?

The core idea behind reactive architecture is that an application seamlessly responds to various events, creating a dynamic and responsive user experience. These events can originate from multiple sources such as user interactions like clicking buttons, typing, or navigating, system events like window resizing or network status changes, and backend responses such as receiving data from APIs or push notifications.

In an Angular Reactive Architecture, these events typically trigger actions in NgRx, which initiate changes in the store. The store acts as the single source of truth for the frontend application, much like a database for the UI. It is divided into multiple slices of state, each representing a distinct part of the application’s data. One slice might manage user authentication, while another handles product data.

State selectors play a critical role in this architecture. Selectors listen to changes in the store and efficiently extract specific slices of state needed by the application. When a change occurs in the store, whether triggered by a user action or a backend event, the relevant selectors emit updates. These updates, in turn, trigger Angular components to refresh only the parts of the UI that display the updated data. This ensures optimal performance by avoiding unnecessary re-renders and keeps the application in sync with the underlying data.

By combining the reactive nature of RxJS, the state management capabilities of NgRx, and Angular’s component-driven design, this approach creates a fluid, event-driven user experience that scales well for complex applications.

Why Choose Reactive Architecture Over Traditional Approaches?

Traditional web applications often follow an imperative approach, where user actions trigger API calls, responses update the UI, and components rely on direct function calls to modify state. This can lead to tightly coupled components, unpredictable state changes, and performance inefficiencies.

Reactive architecture, in contrast, enables a declarative approach where changes flow through a unidirectional data stream. Instead of components actively fetching data or modifying state, they react to state changes emitted by the store. This makes applications more predictable, easier to test, and better suited for handling real-time updates.

Building Blocks of Angular Reactive Architecture

The domain layer serves as the foundation of the application, handling all core logic, state management, and backend communication. It consists of the NgRx store, which acts as the single source of truth, storing both UI-specific information, such as whether a sidebar is open, and remote state that mirrors backend data like user profiles.

Selectors extract and transform raw state into a structure optimized for components. Effects handle asynchronous workflows such as API calls or logging, ensuring that components remain focused on presentation. Services manage backend communication, including API requests and authentication, while facades provide an interface for components, exposing streams through selectors and trigger functions via actions.

By isolating these responsibilities in the domain layer, business logic remains separate from UI concerns.

The feature layer contains smart components, also known as containers, which inject facades to subscribe to streams or dispatch actions. These components handle feature-specific logic, such as filtering a product list, without managing state directly. A product list container might subscribe to a list of products from a facade and trigger actions when users add items to their cart.

The UI layer focuses purely on presentation. Components use @Input to receive data and @Output to emit events. They contain no business logic and are highly reusable. A product card component, for example, simply displays product details and emits an event when the add-to-cart button is clicked.

Code Examples for Key Concepts

NgRx Selector Example

Selectors efficiently extract and transform state to prevent unnecessary re-renders:

import { createSelector } from '@ngrx/store';
import { AppState } from '../reducers';

export const selectProducts = (state: AppState) => state.products;

export const selectAvailableProducts = createSelector(
  selectProducts,
  (products) => products.filter(product => product.available)
);

Components that subscribe to this selector will automatically update when the product state changes.

Using RxJS to Handle API Calls with Effects

Effects handle API requests without blocking the UI thread:

import { Actions, createEffect, ofType } from '@ngrx/effects';
import { ProductService } from '../services/product.service';
import { fetchProducts, fetchProductsSuccess } from '../actions/product.actions';
import { mergeMap, map, catchError } from 'rxjs/operators';
import { of } from 'rxjs';

export class ProductEffects {
  loadProducts$ = createEffect(() =>
    this.actions$.pipe(
      ofType(fetchProducts),
      mergeMap(() =>
        this.productService.getProducts().pipe(
          map(products => fetchProductsSuccess({ products })),
          catchError(() => of({ type: '[Product] Load Failed' }))
        )
      )
    )
  );

  constructor(private actions$: Actions, private productService: ProductService) {}
}

This ensures that API calls are properly handled, preventing unnecessary duplicate requests.

Performance Optimization with Change Detection

Reactive architecture works best when paired with Angular’s OnPush change detection strategy. By default, Angular checks the entire component tree for updates, which can be inefficient in large applications.

@Component({
  selector: 'app-product-list',
  templateUrl: './product-list.component.html',
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class ProductListComponent {
  @Input() products: Product[];
}

With ChangeDetectionStrategy.OnPush, Angular only updates the component when the input reference changes, reducing unnecessary re-renders.

How Other Industries Leverage Reactive Architecture

Netflix uses reactive architecture to handle massive amounts of streaming data, ensuring smooth playback and adaptive bitrate streaming. Google’s Gmail and Google Docs leverage RxJS and reactive state management to provide real-time updates and collaboration features. Banks and financial institutions use reactive principles to process transactions and display live stock market data efficiently.

Embracing Angular Reactive Architecture

Angular Reactive Architecture, powered by Angular, NgRx, and Domain-Driven Design, offers a clean, scalable, and maintainable approach to building modern web applications. With a clear separation of concerns, well-defined layers, and a focus on collaboration, this architecture provides a strong foundation for scalable frontend development.

Teams adopting this approach should start small, leverage tools like Nx for modularization, and fully embrace reactive principles to create applications that are future-proof and adaptable to business needs.

Blog

Recent Posts