Almost exclusively if you search for examples of cross-component communication via services the examples are using RxJS inside the service (could be signals now too). However it doesn't seem like this is necessary. For example, even in the case of the below service, both components will be able to see changes to counter without using RxJS or Signals.
Is there a downside to not using RxJS or Signals in this case? I recognize that using RxJS or Signals is kind of the prescribed way to do things in Angular and that's a good reason to do so, but I'm wondering if there's an actual reason beyond that. Would this cause any sort of issue or bug?
Sample service below and here is a sample Stackblitz.
Service:
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root',
})
export class MyService {
private counter: number = 0;
increment() {
this.counter += 1;
}
getValue() {
return this.counter;
}
}
Component1:
import { Component } from '@angular/core';
import { MyService } from './MyService';
@Component({
selector: 'component1',
standalone: true,
template: `
<h1>Component 1</h1>
Current Value: {{ valueFromService }}
<button (click)="increment()">Click to increment</button>
`,
})
export class Component1 {
constructor(private myService: MyService) {}
get valueFromService() {
return this.myService.getValue();
}
increment() {
this.myService.increment();
}
}
Component 2:
import { Component } from '@angular/core';
import { MyService } from './MyService';
@Component({
selector: 'component2',
standalone: true,
template: `
<h1>Component 2</h1>
Current Value: {{ valueFromService }}
`,
})
export class Component2 {
constructor(private myService: MyService) {}
get valueFromService() {
return this.myService.getValue();
}
}
Using Observables plays well with OnPush ChangeDetectionStrategy that usually has a positive impact on runtime performance. It also makes it simplier to work with derived states, handle multiple simultanious updates, and running side-effects as a reaction to state updates.
Your example works as long as components use a default ChangeDetectionStrategy. Let's take a closer look at a very simplified and not very precise explanation of what happens when a user clicks the increment button:
So the service value update and rendered value update are kind of separated. If the value updates as a result of something that Zone.js cannot intercept (e.g. awync/await) the view won't reflect changes. It is worth to note that Events may happen quite often and cheking every single component on each event may become quite a heavy task. That is why it is considered a better practice to use OnPush ChangeDetectionStrategy.
Not going too deep into details, when OnPush is used, Angular skips checking this component and all its children unless some of the inputs are changed, or the component is marked for checking via e.g. calling ChangeDetectorRef.markForCheck()
. It is a common pattern to taking an observable from a service and bind to it in template via async
pipe and the pipe calls markForCheck
each time a the Observable emits a value. So while Change Detection cycle is still needs to be triggered, it is an Observable value update makes a component checked, and those components that have no changes won't be checked at all.
As for derived states and side effects, let's imagine we have two services A and B, and when a value updates in A need to do something in B. Without Observables that would require injecting B to A and what if we have dozens of such connections? It gets messy pretty fast. The same is fair fo the components, it is pretty common situation that before rendering the value it should be transformed, combined with another one, and only then rendered. While this can be done inside a getter it is a waste of resources to run them on each CD cycle.
The last thing is that some of Angular's APIs come as Observables, for example, reactive forms, router events, HttpClient so it is simplier to fit them into your own stuff when it is also Observable-based.