DEV Community

Cristian Sifuentes
Cristian Sifuentes

Posted on

Angular Signals Explained Like a Senior Developer (Angular 21 Perspective)

Angular Signals Explained Like a Senior Developer (Angular 21 Perspective)

Angular Signals Explained Like a Senior Developer (Angular 21 Perspective)

TL;DR --- Signals were introduced to eliminate accidental complexity
in UI state.\
They are not a replacement for RxJS. They are a refinement of Angular's
reactivity model.


The Production Pain That Forced Signals

In enterprise Angular systems, I've seen teams debug:

  • Nested switchMap chains\
  • BehaviorSubject misuse\
  • Subscription leaks\
  • Race conditions in UI state

RxJS was solving problems it was never meant to solve.

Simple UI state became streams.\
Streams became orchestration logic.\
Orchestration logic became unreadable.

Unreadable code becomes production risk.

Signals target a specific class of pain:

Local, synchronous, UI-driven state that never required a stream.


What Signals Actually Are

Signals are:

  • Reactive value containers\
  • Read synchronously\
  • Updated explicitly\
  • Automatically dependency-tracked

Core API:

import { signal, computed, effect } from '@angular/core';

const count = signal(0);

count.set(1);
count.update(v => v + 1);

const double = computed(() => count() * 2);

effect(() => {
  console.log('Count changed:', count());
});
Enter fullscreen mode Exit fullscreen mode

No subscriptions.\
No teardown logic.\
No async by default.

Signals are deterministic state containers.


The Architectural Shift

Old model:

Who subscribed to what?

Signal model:

Which value depends on which signal?

Angular tracks dependency reads at runtime.

This enables:

  • Fine-grained updates\
  • Predictable recomputation\
  • Reduced unnecessary change detection\
  • Easier reasoning

Signals are not syntactic sugar.\
They are a reactivity shift.


Real Enterprise Example: Feature Store

Requirements

  • Load user
  • Show loading
  • Control permissions
  • Derive UI flags

Signal-Based Store

// user.store.ts
import { signal, computed, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';

export interface User {
  id: string;
  name: string;
  permissions: string[];
}

export class UserStore {
  private http = inject(HttpClient);

  readonly user = signal<User | null>(null);
  readonly loading = signal(false);

  readonly canEdit = computed(() =>
    this.user()?.permissions.includes('EDIT_PROFILE') ?? false
  );

  loadUser() {
    this.loading.set(true);

    this.http.get<User>('/api/user').subscribe({
      next: user => this.user.set(user),
      complete: () => this.loading.set(false)
    });
  }
}
Enter fullscreen mode Exit fullscreen mode

Absent:

  • BehaviorSubject
  • Manual unsubscribe
  • Async pipe complexity

Derived state uses computed.


Angular 21 Template Integration

import { Component } from '@angular/core';
import { UserStore } from './user.store';

@Component({
  standalone: true,
  selector: 'app-profile',
  providers: [UserStore],
  template: `
    @if (store.loading()) {
      <app-loader />
    }

    @if (store.user()) {
      <h2>{{ store.user()!.name }}</h2>

      @if (store.canEdit()) {
        <button>Edit Profile</button>
      }
    }
  `
})
export class ProfileComponent {
  constructor(public store: UserStore) {
    this.store.loadUser();
  }
}
Enter fullscreen mode Exit fullscreen mode

No async pipe.\
No subscription management.\
Clear dependency graph.


Why This Scales in Enterprise

Signals shine when state is:

  • Local to a feature\
  • UI-driven\
  • Synchronous\
  • Derived from other local state

Benefits:

  • No memory leaks\
  • Easy refactoring\
  • Smaller cognitive load\
  • Cleaner tests

Test example:

it('should compute permission correctly', () => {
  const store = new UserStore();
  store.user.set({ id: '1', name: 'A', permissions: ['EDIT_PROFILE'] });

  expect(store.canEdit()).toBe(true);
});
Enter fullscreen mode Exit fullscreen mode

Pure state test. No TestBed needed.


What Signals Are NOT For

Signals are not ideal for:

  • WebSocket streams\
  • Event orchestration\
  • Complex async flows\
  • Cancellation logic

That remains RxJS territory.

Correct separation:

Signals → UI & local state\
RxJS → async streams & orchestration


Common Senior Mistakes

Replacing RxJS Everywhere

Signals are not an async engine.

Global Signal Services Without Ownership

Hidden shared state increases coupling.

Localize signals first.

Overusing effect()

effect(() => {
  if (store.user()) {
    analytics.track('user_loaded');
  }
});
Enter fullscreen mode Exit fullscreen mode

Effects should handle side effects only.\
Prefer computed for derivation.


Angular 16--21 Evolution

Angular 16: - Introduced signals

Angular 17--18: - Template integration - Control flow blocks

Angular 20--21: - Stable mental model - Works seamlessly with
standalone - Zoneless architecture realistic - Measurable performance
gains

Signals are now architectural primitives.


Performance Insight

Signals allow Angular to:

  • Track exact dependencies
  • Avoid tree-wide dirty checking
  • Minimize recomputation boundaries

This enables fine-grained reactivity.

In zoneless apps, this becomes transformative.


Interview-Level Answer

Why were Signals introduced?

To reduce accidental complexity in UI state and enable deterministic
fine-grained reactivity without subscription overhead.

When to choose RxJS?

When modeling asynchronous event streams or complex orchestration.

Are Signals mutable?

Explicitly updated, but dependency tracking is reactive and
deterministic.

Precision signals seniority.


Production Guidelines

  • One store per feature
  • Signals for UI/local state
  • RxJS for async orchestration
  • Prefer computed over manual state wiring
  • Keep signals near usage
  • Avoid logic-heavy effects

Balance creates maintainability.


Final Takeaway

Signals didn't arrive because RxJS failed.

They arrived because Angular teams were over-engineering simple state.

Signals restore clarity.

Use them where they fit.\
Respect RxJS where it belongs.

And Angular 21 will feel predictable again.


Cristian Sifuentes\
Angular Architect · Systems Thinker

Top comments (0)