Skip to main content

NgRx vs Service-Based State Management Analysis

Application Context

This analysis evaluates whether NgRx (Redux-style state management) or simple service-based data fetching is more appropriate for this Angular healthcare application.

Current Architecture Overview

  • Framework: Angular 19 with standalone components
  • Data Sources: Mock data (transitioning to Firebase/API)
  • Services: ~15+ injectable services with BehaviorSubjects
  • Components: ~40+ components across pages and shared
  • State Patterns: In-memory arrays, localStorage, BehaviorSubject observables

Option 1: NgRx State Management

Pros

BenefitDescription
Predictable StateSingle source of truth with immutable state updates
Time-Travel DebuggingRedux DevTools enable state inspection and replay
Centralized LogicAll state mutations in reducers, easier to audit
TestabilityPure functions (reducers, selectors) are easy to unit test
Caching Built-inEntity adapter provides normalized caching
Optimistic UpdatesEffects pattern handles async operations cleanly
Team ScalabilityEnforced patterns reduce code style variations
Complex State DerivationSelectors with memoization for computed state

Cons

DrawbackDescription
Boilerplate HeavyActions, reducers, effects, selectors for each feature
Learning CurveTeam needs Redux/RxJS expertise
Overkill for Simple CRUDPatient list doesn’t need Redux complexity
Bundle SizeAdds ~50-100KB to production bundle
Development SpeedSlower initial development for simple features
IndirectionAction → Reducer → Selector flow harder to trace
Over-engineering RiskTemptation to put everything in store

When NgRx Makes Sense

  • Real-time collaborative editing
  • Complex undo/redo requirements
  • Offline-first with sync queues
  • Shared state across 10+ components
  • Complex derived state calculations

Option 2: Service-Based Data Fetching (Current Approach)

Pros

BenefitDescription
SimplicityDirect service calls, easy to understand
Fast DevelopmentNo boilerplate, quick feature iteration
Angular NativeUses built-in DI and RxJS patterns
Smaller BundleNo additional state library overhead
FlexibleMix patterns as needed per feature
Lower Learning CurveStandard Angular knowledge sufficient
Direct Data FlowComponent → Service → API is traceable

Cons

DrawbackDescription
State DuplicationSame data fetched multiple times
Inconsistent PatternsEach service may handle state differently
No Built-in CachingManual cache implementation needed
Debugging HarderNo centralized state inspection
Race ConditionsManual handling of concurrent updates
Component CouplingComponents may hold too much state logic

Current Implementation Strengths

// Your services already use BehaviorSubjects effectively
private alertsSubject = new BehaviorSubject<ThresholdAlert[]>([]);
public alerts$ = this.alertsSubject.asObservable();

// Reactive patterns without NgRx overhead
private unreadCountSubject = new BehaviorSubject<number>(0);
unreadCount$ = this.unreadCountSubject.asObservable();

Application-Specific Analysis

Data Flow Patterns in This App

FeatureState ComplexityShared AcrossRecommendation
Patient ListLow3-4 componentsService ✓
Settings/ThemeLowGlobalService ✓
Chat MessagesMedium2-3 componentsService ✓
AlertsMedium3-4 componentsService ✓
QuestionnairesMedium5+ componentsService ✓
CarepathsMedium4-5 componentsService ✓
Dashboard MetricsLow1 componentService ✓

Key Observations

  1. No Complex Cross-Cutting State: Features are relatively isolated
  2. CRUD-Dominant Operations: Most features are simple create/read/update
  3. Limited Real-Time Needs: No collaborative editing or live sync
  4. Moderate Component Tree: State doesn’t need to traverse deep hierarchies
  5. Firebase Integration: Firebase SDK handles real-time sync natively

Instead of full NgRx, enhance current services with:

1. Signal-Based State (Angular 17+)

// Modern Angular signals for reactive state
@Injectable({ providedIn: 'root' })
export class PatientService {
  private patientsSignal = signal<Patient[]>([]);
  readonly patients = this.patientsSignal.asReadonly();
  
  // Computed derived state
  readonly activePatients = computed(() => 
    this.patientsSignal().filter(p => p.status === 'In Progress')
  );
}

2. Simple Caching Layer

// Add caching to services without NgRx
private cache = new Map<string, { data: any; timestamp: number }>();
private CACHE_TTL = 5 * 60 * 1000; // 5 minutes

async getPatient(id: string): Promise<Patient> {
  const cached = this.cache.get(id);
  if (cached && Date.now() - cached.timestamp < this.CACHE_TTL) {
    return cached.data;
  }
  const data = await this.fetchPatient(id);
  this.cache.set(id, { data, timestamp: Date.now() });
  return data;
}

3. Lightweight State Container (Optional)

Consider @ngrx/component-store for complex features only:
// Per-component store without global NgRx
@Injectable()
export class QuestionnaireStore extends ComponentStore<QuestionnaireState> {
  readonly questionnaires$ = this.select(state => state.items);
  
  readonly addQuestionnaire = this.updater((state, item: Questionnaire) => ({
    ...state,
    items: [...state.items, item]
  }));
}

Decision Matrix

CriteriaWeightNgRx ScoreService Score
Development Speed25%2/55/5
Maintainability20%4/53/5
Team Familiarity15%2/55/5
Bundle Size10%2/55/5
Debugging10%5/53/5
Scalability10%5/53/5
Feature Complexity Match10%2/54/5
Weighted Total100%2.85/54.05/5

Conclusion

Recommendation: Continue with Service-Based Approach

For this healthcare application, NgRx is not recommended because:
  1. Feature Complexity Doesn’t Justify It: CRUD operations on patients, questionnaires, and carepaths don’t require Redux patterns
  2. Current Architecture Works Well: Your BehaviorSubject-based services already provide:
    • Reactive data streams
    • Centralized business logic
    • Observable-based component updates
  3. Firebase Handles Real-Time: When you integrate Firebase, its SDK provides real-time sync without needing NgRx
  4. Development Velocity: Adding NgRx would slow down feature development without proportional benefits
  5. Team Productivity: Simpler patterns mean faster onboarding and fewer bugs

Suggested Enhancements

Instead of NgRx, consider these improvements:
EnhancementEffortImpact
Migrate to Angular SignalsMediumHigh
Add service-level cachingLowMedium
Standardize error handlingLowMedium
Use @ngrx/component-store for complex formsLowMedium
Add loading/error states to servicesLowHigh

When to Reconsider NgRx

Revisit this decision if:
  • Real-time collaborative editing is added
  • Offline-first with complex sync is required
  • State needs to be shared across 15+ components
  • Complex undo/redo functionality is needed
  • Team grows to 10+ developers on same codebase

Summary

AspectVerdict
Current ApproachService-based with BehaviorSubjects
RecommendationKeep current approach, enhance with Signals
NgRx Needed?No - overkill for this application
Alternative@ngrx/component-store for complex features only
PriorityFocus on Firebase integration, not state library