最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

angular - Updated signal value is delayed in child component, if the value is passed down as an `input()` - Stack Overflow

programmeradmin5浏览0评论

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 badges
Add a comment  | 

1 Answer 1

Reset to default 0

There 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() {}
}

Stackblitz Demo

发布评论

评论列表(0)

  1. 暂无评论