The Problem: Business Logic Sprawl in Enterprise Angular
Enterprise Angular applications often begin with a straightforward pattern: fetch raw data from a REST API and store it directly in the application state. This approach works well initially. However, as the application scales and complexity increases, treating state as mere passive data containers leads to a significant problem: business logic begins to scatter. It seeps across components, pipes, and utility files, making the codebase difficult to maintain, debug, and extend. This fragmentation creates what many developers experience as "logic sprawl," where a single business rule might be implemented in multiple, disconnected places.
Consider a user profile. Raw data might include `firstName`, `lastName`, and `email`. But what about derived properties like `fullName` or methods to `updateEmail` or `sendVerificationEmail`? Initially, these might live in a component. Later, as they are needed elsewhere, they get copied or refactored into a shared utility function. Soon, it becomes unclear where the single source of truth for user-related logic resides. This is a common pain point in large applications, leading to inconsistencies and increased development time.
The Solution: Introducing "Smart Objects" with NgRx SignalStore
The article proposes a pattern called Smart Objects to combat this logic sprawl. Instead of treating API response objects as inert data, we transform them into active entities that encapsulate their own behavior and derived properties. This means that a `User` object, for instance, would not only hold `firstName` and `lastName` but also possess a `getFullName()` method and potentially methods to manage its own state updates.
The chosen implementation leverages NgRx SignalStore, a modern state management solution for Angular that embraces Signals. SignalStore provides a flexible and reactive way to manage application state, and it pairs well with the concept of Smart Objects. The core idea is to enrich the raw data objects fetched from the API with methods and computed properties directly within the store. This keeps related logic colocated with the data it operates on, creating a more organized and maintainable structure.
Object Enrichment: Adding Behavior to Data
Object Enrichment is the process of augmenting raw data objects with additional methods and properties. In the context of an Angular application and NgRx SignalStore, this typically involves creating a class or factory function that takes the raw API data as input and returns an object with the desired enhancements.
For example, a raw user object might look like this:
{
id: 1,
firstName: "John",
lastName: "Doe",
email: "john.doe@example.com"
}
A "Smart Object" version of this user could be:
class UserSmartObject {
constructor(
public id: number,
public firstName: string,
public lastName: string,
public email: string
) {}
getFullName(): string {
return `${this.firstName} ${this.lastName}`;
}
// Potentially other methods like updateEmail, sendVerification, etc.
}
The challenge then becomes how to instantiate these Smart Objects within the Angular ecosystem, especially when these objects themselves might need access to Angular's dependency injection system (e.g., to call an `AuthService` or `HttpClient`).
Overcoming DI Constraints with runInInjectionContext
Angular's Dependency Injection (DI) system is powerful but has constraints. Services can inject other services, and components can inject services. However, instantiating a plain JavaScript class or object outside of an Angular-provided context (like a component constructor or a service provider) means it doesn't automatically have access to the injector. This is where runInInjectionContext becomes crucial.
runInInjectionContext is a utility function provided by Angular that allows you to execute a piece of code within the context of a specific injector. This means that any services or dependencies available to that injector can be accessed within the executed code block.
In the context of NgRx SignalStore, you can use runInInjectionContext when defining your store's actions or reducers. This allows your Smart Objects to be instantiated and their methods to be called, with access to Angular's DI. The process typically involves:
- Defining your raw API data model.
- Defining your
SmartObjectclass with its methods. - When fetching data and updating the store, use
runInInjectionContextto instantiate theSmartObject, passing in the raw data and any necessary services obtained from the injection context.
This pattern ensures that the logic for transforming raw data into enriched, behavior-rich objects happens at the earliest possible point in the state management pipeline, keeping it centralized within the store.

Benefits of the Smart Object Pattern
Adopting the Smart Object pattern with NgRx SignalStore and runInInjectionContext offers several key advantages:
- Improved Maintainability: Business logic is colocated with the data it pertains to, making it easier to find, understand, and modify.
- Reduced Logic Duplication: By encapsulating behavior, the need to copy-paste or refactor logic into shared utilities diminishes significantly.
- Enhanced Readability: Components become cleaner, focusing on presentation and delegating complex operations to the Smart Objects.
- Testability: Smart Objects, especially when designed carefully, can be easier to unit test in isolation.
- Clearer State Management: The state is no longer just data; it's a collection of active entities with defined behaviors.
This approach transforms how developers think about data in their applications. Instead of merely consuming data, they are building interactive, behavior-rich entities that drive the application's functionality. The explicit use of runInInjectionContext provides a clean and Angular-idiomatic way to bridge the gap between plain objects and the framework's powerful DI system, ensuring that even enriched objects can leverage framework services when needed.
Interactive Playground & Code Access
For developers eager to implement this pattern immediately, the article points to a GitHub Repository containing the full, working implementation. Additionally, a live reactivity playground is available on StackBlitz, allowing immediate experimentation with the concepts discussed. This hands-on access is invaluable for understanding how the SignalStore, Smart Objects, and runInInjectionContext interact in a live environment.
