I have a service which maintains some state using a signal. The service also exposes an rxjs observable which emits when a certain event has taken place. This event takes place after the signal value has been updated.
I have a smart component which has a direct dependency on the service, and passes along the signal and the event to a child dumb component which reacts to the event by taking an action which is dependent on the current state value.
My issue is that because the signal from the service is passed down into the child component as an input()
, it gets wrapped in a second signal, which delays the change detection, because it must propagate from the service to the smart component, and then from the smart component to the child component. So when the event fires in the child component, the signal input() hasn't updated its value yet - so it has a stale value.
This can be seen in my stackBlitz here. I have created 3 different iterations of the child component (a, b, and c).
- a: Follows the standard Angular
input()
pattern, and you can see its state value is always 1 update behind at the time of the event firing. - b: Utilizes an
Input()
for the value, so the actual signal is passed down, rather than the signal's value. - c: Utilizes an
input()
, but it too is given the signal, rather than the value from the parent.
Component B and C both get the correct values, but none of these patterns feel good to me. B is definitely the least offensive, but I don't think it will be apparent to most devs why this one particular input is using a decorator, when every other input in our codebase will be using an input()
signal. Is there a better pattern that will address this issue?
Additional relevant notes: The stackblitz demo included here is significantly simplified from my actual use-case. In my actual codebase, there are 3 different ways for the state value to be changed, which is determined by receiving certain messages in a websocket connection. I am using an observable to notify children of these changes, because then I know which of the 3 methods modified the state. I react differently depending on which of the 3 methods caused the reaction. I cannot rely on the current state value alone to determine how to trigger the reaction in the child component. And I would need to track the previous state value and do a somewhat complicated evaluation in order to determine how the state was modified and then how to respond on the child side.
Additionally, in my actual codebase, when the observable emits its event, I am calling into a util function which has multiple side effects, which are based off of some slightly intensive calculations based on the current value of many different state objects. For the sake of showing the issue I was running into, in the stackblitz demo I am just displaying the value, but in reality the side-effect is far more complicated.
I have a service which maintains some state using a signal. The service also exposes an rxjs observable which emits when a certain event has taken place. This event takes place after the signal value has been updated.
I have a smart component which has a direct dependency on the service, and passes along the signal and the event to a child dumb component which reacts to the event by taking an action which is dependent on the current state value.
My issue is that because the signal from the service is passed down into the child component as an input()
, it gets wrapped in a second signal, which delays the change detection, because it must propagate from the service to the smart component, and then from the smart component to the child component. So when the event fires in the child component, the signal input() hasn't updated its value yet - so it has a stale value.
This can be seen in my stackBlitz here. I have created 3 different iterations of the child component (a, b, and c).
- a: Follows the standard Angular
input()
pattern, and you can see its state value is always 1 update behind at the time of the event firing. - b: Utilizes an
Input()
for the value, so the actual signal is passed down, rather than the signal's value. - c: Utilizes an
input()
, but it too is given the signal, rather than the value from the parent.
Component B and C both get the correct values, but none of these patterns feel good to me. B is definitely the least offensive, but I don't think it will be apparent to most devs why this one particular input is using a decorator, when every other input in our codebase will be using an input()
signal. Is there a better pattern that will address this issue?
Additional relevant notes: The stackblitz demo included here is significantly simplified from my actual use-case. In my actual codebase, there are 3 different ways for the state value to be changed, which is determined by receiving certain messages in a websocket connection. I am using an observable to notify children of these changes, because then I know which of the 3 methods modified the state. I react differently depending on which of the 3 methods caused the reaction. I cannot rely on the current state value alone to determine how to trigger the reaction in the child component. And I would need to track the previous state value and do a somewhat complicated evaluation in order to determine how the state was modified and then how to respond on the child side.
Additionally, in my actual codebase, when the observable emits its event, I am calling into a util function which has multiple side effects, which are based off of some slightly intensive calculations based on the current value of many different state objects. For the sake of showing the issue I was running into, in the stackblitz demo I am just displaying the value, but in reality the side-effect is far more complicated.
Share Improve this question edited Mar 9 at 9:54 ThePuzzleMaster asked Mar 9 at 9:04 ThePuzzleMasterThePuzzleMaster 1,0012 gold badges12 silver badges18 bronze badges1 Answer
Reset to default 0There is no need for an observable in this scenario, whatever can be done through observables can be reactively achieved with signals. I would suggest the below approach.
Convert the service observable to a signal and set the value of $thingEvents
using computed
because the property is a state derived from the source signal.
export class MyStateService {
public $myThing = signal<number>(0);
public $thingEvents = computed(() =>
this.$myThing() >= 0 ? 'grow' : 'shrink'
);
public doSomething(delta: number) {
this.$myThing.update((myThing) => myThing + delta);
}
}
Then just pass in the signal values as input to the child component by executing them in the HTML.
<h2>Showcasing input value not being accurate</h2>
<app-component-a [$myValue]="$myValue()" [$myEvent$]="myEvent$()" />
<button (click)="updateState()">Update state</button>
The TS for the root component will be:
export class SignalProblem {
protected $myValue = this.myState.$myThing;
protected $myEvent = this.myState.$thingEvents;
constructor(private myState: MyStateService) {}
protected updateState() {
this.myState.doSomething(1);
}
}
Then in the child component, if you are going to change the value (user/function changes it within the component) $valueWhenEventFires
then use a linked signal, if the value will not change, then use a computed
.
Or we can directly refer the computed
from the service itself.
@Component({
selector: 'app-component-a',
template: `
<div>I am the component with an input()</div>
<div>My value when event fires: {{this.$valueWhenEventFires()}}</div>
`,
})
export class ComponentA implements OnInit {
public $myValue = input<number>(0);
public $myEvent = input<ThingEvent>();
// use this when `$valueWhenEventFires` is purely a computed field.
protected $valueWhenEventFires = computed(() => this.$myEvent());
// use this, when `$valueWhenEventFires` is modified locally and local state is to be preserved
// protected $valueWhenEventFires = computed(() => this.$myEvent());
ngOnInit() {}
}