DEV Community

Cover image for From RxJS to Signals: How to Test an Angular Developer's Modern Framework Knowledge
Emma Schmidt
Emma Schmidt

Posted on

From RxJS to Signals: How to Test an Angular Developer's Modern Framework Knowledge

Introduction

The Angular ecosystem has evolved dramatically over the past few years, and knowing how to evaluate a developer's grasp of its modern features is now more important than ever. Whether you are a startup building a product from scratch or an enterprise scaling your frontend team, when you Hire Angular developers, you need a way to separate candidates who truly understand the framework from those who are stuck in 2019 patterns. The shift from RxJS-heavy reactive programming to Angular Signals represents one of the biggest paradigm changes the framework has seen. This blog breaks down exactly what to test, what to ask, and what answers reveal a developer who is genuinely ready for modern Angular development.


Understanding the Shift: RxJS vs Angular Signals

Before jumping into interview questions and test strategies, it helps to understand what this shift actually means.

What is RxJS in Angular?

RxJS (Reactive Extensions for JavaScript) has been the backbone of Angular's reactivity model since the early versions. It uses Observables to handle asynchronous data streams. Developers used it for everything from HTTP requests to form value changes to state management.

A typical RxJS pattern in a component might look like this:

export class UserComponent implements OnInit, OnDestroy {
  private destroy$ = new Subject<void>();
  user$ = this.userService.getUser().pipe(takeUntil(this.destroy$));

  ngOnInit() {
    this.user$.subscribe(user => {
      this.userName = user.name;
    });
  }

  ngOnDestroy() {
    this.destroy$.next();
    this.destroy$.complete();
  }
}
Enter fullscreen mode Exit fullscreen mode

This works, but it requires boilerplate. Developers must manually manage subscriptions, handle memory leaks, and understand reactive operators like switchMap, mergeMap, combineLatest, and more.

What Are Angular Signals?

Introduced as a developer preview in Angular 16 and stabilized in Angular 17, Signals are a simpler, synchronous reactivity primitive. They make state changes explicit and predictable.

The same concept rewritten with Signals:

export class UserComponent {
  userName = signal('');

  constructor(private userService: UserService) {
    effect(() => {
      this.userService.getUser().subscribe(user => {
        this.userName.set(user.name);
      });
    });
  }
}
Enter fullscreen mode Exit fullscreen mode

Signals reduce boilerplate, improve change detection performance with OnPush by default behavior, and make Angular more beginner-friendly without sacrificing power.


Why This Distinction Matters When Hiring

A developer who only knows RxJS will struggle with modern Angular codebases. Conversely, a developer who only knows Signals might not be equipped to work with existing enterprise applications that rely heavily on RxJS. The ideal candidate understands both and knows when to use each.

When you design a technical evaluation, testing this knowledge gap is the most reliable signal (pun intended) of a developer's readiness.


Core Areas to Test

1. RxJS Fundamentals

Even with Signals gaining ground, RxJS is not going away. The Angular HTTP client still returns Observables. NgRx, one of the most popular state management libraries, is built on RxJS. Any developer working with Angular needs to understand these concepts:

Subscription Management

Ask candidates how they handle unsubscribing from Observables. A strong answer will mention multiple strategies:

  • Using takeUntilDestroyed() (available in Angular 16+)
  • Using async pipe in templates (which auto-unsubscribes)
  • Using Subject with takeUntil in older codebases
  • Using DestroyRef injected into a service or component

A weak answer will describe manual unsubscribe() calls without acknowledging memory leak risks.

Operator Knowledge

Ask candidates to explain the difference between switchMap, mergeMap, concatMap, and exhaustMap. This question alone filters out a huge number of mid-level developers who know RxJS exists but do not understand its nuances.

  • switchMap: Cancels the previous inner Observable when a new value arrives. Perfect for search input debouncing.
  • mergeMap: Runs all inner Observables concurrently. Good for parallel requests.
  • concatMap: Queues inner Observables one after another. Good for ordered operations.
  • exhaustMap: Ignores new values while the current inner Observable is active. Perfect for preventing duplicate form submissions.

A strong candidate can also give real-world scenarios for each.

Cold vs Hot Observables

This is a conceptually tricky area. Cold Observables start producing values only when subscribed (like HTTP calls). Hot Observables produce values regardless of subscriptions (like DOM events or WebSockets).

Ask: "If two components subscribe to the same HTTP Observable, how many HTTP requests are made?" A strong developer will say two, and then explain how to convert it to a hot Observable using shareReplay(1).


2. Angular Signals Knowledge

Testing Signals knowledge reveals how current a developer is with the Angular roadmap.

Signal Primitives

There are three core primitives:

  • signal(): Creates a writable reactive value
  • computed(): Creates a derived value that updates automatically when its dependencies change
  • effect(): Runs a side effect whenever its Signal dependencies change

Ask candidates to explain the difference between computed() and effect(). Many developers confuse them.

computed() is for deriving values:

const fullName = computed(() => `${firstName()} ${lastName()}`);
Enter fullscreen mode Exit fullscreen mode

effect() is for side effects like logging, DOM manipulation, or syncing with external state:

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

Signal-Based Inputs and Outputs

Angular 17.1 introduced input() as a signal-based alternative to @Input(). Angular 17.3 introduced output() as a signal-based alternative to @Output().

Ask candidates to rewrite a traditional Input/Output-based parent-child component interaction using the new signal-based API. This tests whether they have been paying attention to Angular's evolution.

Traditional approach:

@Input() title: string = '';
@Output() titleChange = new EventEmitter<string>();
Enter fullscreen mode Exit fullscreen mode

Modern approach:

title = input<string>('');
titleChange = output<string>();
Enter fullscreen mode Exit fullscreen mode

toSignal and toObservable

Since most Angular apps use a mix of Signals and Observables, the interop utilities are important. Ask candidates to explain:

  • toSignal(): Converts an Observable into a Signal so it can be used in templates or computed()
  • toObservable(): Converts a Signal into an Observable so it can be used with RxJS operators

A senior developer will also mention that toSignal() requires an injection context and should be called during component construction.


3. Change Detection Understanding

One of the biggest performance improvements in modern Angular comes from combining Signals with ChangeDetectionStrategy.OnPush. Test whether candidates understand this relationship.

Ask: "How do Signals improve change detection performance in Angular?"

A strong answer will include:

  • Traditional default change detection triggers for every browser event and async operation across the entire component tree
  • OnPush limits change detection to when an Input reference changes or an Observable emits through the async pipe
  • Signals make components "signal-aware," meaning Angular knows exactly which components need to re-render when a specific Signal changes
  • The upcoming Zoneless Angular (removing Zone.js entirely) relies heavily on Signals for reactivity

4. State Management Patterns

Modern Angular applications use various state management strategies. Test whether a candidate can evaluate the tradeoffs.

Component-Level State with Signals

For simpler applications, local state managed with Signals is often the cleanest solution. Ask candidates when they would choose this over a global store.

A strong answer: Local Signal-based state is ideal when data is not shared across many components. It avoids unnecessary complexity and keeps state close to where it is used.

Service-Based State with Signals

A common modern pattern is using Angular services as lightweight stores with Signals:

@Injectable({ providedIn: 'root' })
export class CartService {
  private items = signal<CartItem[]>([]);
  readonly cartItems = this.items.asReadonly();
  readonly totalCount = computed(() => this.items().length);

  addItem(item: CartItem) {
    this.items.update(current => [...current, item]);
  }
}
Enter fullscreen mode Exit fullscreen mode

Ask candidates to critique this pattern and compare it to NgRx. A strong developer will note that this pattern works well for small to medium apps but NgRx offers better DevTools, time-travel debugging, and a more structured approach for large teams.

NgRx Signal Store

NgRx 17 introduced @ngrx/signals, a Signal-based alternative to the traditional Redux-style store. Asking candidates about this tests whether they are tracking cutting-edge Angular ecosystem changes.


5. Practical Coding Challenges

Theory alone is not enough. Include at least one live or take-home coding challenge.

Challenge 1: Build a Search Component

Requirements:

  • Use an input field that triggers an API search
  • Debounce input by 300ms
  • Cancel in-flight requests if the user types again
  • Display results using the async pipe
  • Handle loading and error states

This tests RxJS skills (debounceTime, switchMap, catchError), template binding, and the async pipe.

Challenge 2: Refactor with Signals

Give candidates a component written with BehaviorSubject and ask them to refactor it using Signals, computed(), and toSignal(). This tests both old and new knowledge simultaneously.

Challenge 3: Build a Cart Feature

Requirements:

  • Use a Signal-based service to manage cart state
  • Expose a computed Signal for the total price
  • Use effect() to persist cart state to localStorage
  • Display cart item count in a header component using input() Signals

This tests end-to-end Signal usage in a realistic feature.


Red Flags to Watch For

When evaluating candidates, certain patterns indicate shallow knowledge:

Overusing Observables for Simple State

A developer who wraps every piece of state in a BehaviorSubject without considering Signals is likely not up to date with Angular best practices.

Ignoring Subscription Cleanup

Any candidate who subscribes without mentioning unsubscription strategies immediately reveals a potential source of memory leaks in production.

Treating effect() Like computed()

Candidates who use effect() to derive values instead of computed() are misusing the API. computed() is a pure reactive derivation. effect() is for side effects. This distinction matters for correctness and performance.

Not Knowing Zone.js

Angular has historically relied on Zone.js to trigger change detection. Candidates should at least know what it does, why the community wants to remove it, and how Signals fit into the Zoneless Angular roadmap.

Memorized Answers Without Context

Watch out for candidates who can recite definitions but stumble when asked to apply them to a real scenario. Push with follow-up questions like "When would you NOT use this?" or "What would break if you changed this?"


Green Flags That Reveal Mastery

Alongside red flags, certain behaviors indicate a genuinely strong Angular developer:

  • They mention Angular 18 and 19 features like deferred loading (@defer), the new control flow syntax (@if, @for, @switch), and partial hydration for Server-Side Rendering
  • They can articulate a clear migration path from RxJS-heavy code to a Signal-first architecture
  • They understand that Signals and RxJS are complementary, not competing, and know how to combine them using toSignal() and toObservable()
  • They bring up performance considerations like avoiding unnecessary effect() registrations and keeping computed() dependencies minimal
  • They have opinions on tooling, testing strategies, and architecture, and can defend those opinions with reasoning

Structuring a Full Technical Interview

Here is a suggested structure for a 90-minute technical interview covering these areas:

0 to 15 minutes: Conceptual Warm-Up
Ask about the evolution of Angular reactivity. Let the candidate talk. Listen for whether they frame Signals as a replacement or a complement to RxJS.

15 to 40 minutes: RxJS Deep Dive
Cover operator differences, subscription management, and at least one scenario-based question like the search debounce use case.

40 to 65 minutes: Signals and Modern Angular
Cover the three primitives, input/output signals, change detection, and the interop utilities.

65 to 85 minutes: Live Coding or Code Review
Use one of the practical challenges mentioned above. Code review of a buggy component works well for senior roles.

85 to 90 minutes: Culture and Architecture Discussion
Ask how they would introduce Signals into an existing codebase without breaking RxJS-based logic. This reveals collaboration and migration mindset.


Conclusion

The Angular framework is in a fascinating transition period. RxJS is still deeply embedded in the ecosystem, but Signals represent the future of reactive state management in Angular. A developer who understands both paradigms, knows when to use each, and can articulate a migration strategy is genuinely rare and extremely valuable.

When building your evaluation process, do not just test syntax recall. Test judgment, adaptability, and depth of understanding. The best Angular developers are the ones who can look at a BehaviorSubject-heavy codebase and know exactly how to improve it without breaking everything in the process.

The questions and challenges in this guide are designed to surface that exact depth. Use them well, and you will build a team that can handle both the Angular of today and the Angular of tomorrow.

Top comments (0)