5

Repro

https://stackblitz.com/edit/angular-rlqkyb

app.service

Simple service creating a BehaviorSubject with some default values, a function to return this subject as an observable and finally a function to emit a new value on the subject.

app.component

This component simply gets a reference to the observable returned by the service and subscribe to it via the async pipe. Note that the observable is piped in order to display the emitted value.

This component also displays the second component.

app2.component

This component has an input that subscribes to the observable returned by the service that emits a new value and that's it.

The problem

The problem is that even though I see the new value emitted in the console, the view of app.component is not updated.

At first, I thought that it was because the observable returned by the service was not in the zone, so I tried wrapping some part of this function into a zone.run (the whole content of the observable and just the next/complete calls) but it still didn't work.

One thing that works is the commented line in app2.component that subscribes to the observable using a setTimeout.

Another thing that works is to set the change detection strategy to default instead of OnPush, however, I do not wish to do so.

In my mind, the app.component gets the observable, subscribes to it via the async pipe (so it should refresh the value as soon as it receives a new value, that's the goal of the observable, indicating to Angular that something has changed). At the same time, app2.component gets loaded and via its input, it updates the observable, so the async pipe of app.component should get the new value and update its view as it is doing without the OnPush strategy.

I'm pretty confident it's not an angular issue but rather a misunderstanding from me, but then I'd appreciate if someone could explain me where I'm wrong :-)

Note that I also checked the other thread on StackOverflow but they all talk about zone.run or using detectChanges (which I wouldn't know where to use it here)

4
  • Why the complicated setData..? You can simply next the observable there..? Commented Jun 15, 2022 at 16:50
  • 1
    I could indeed but this is an oversimplification of are reactive store that I am implementing and in it, the setData does way more than simply doing a next. Commented Jun 15, 2022 at 16:52
  • That could be the case. However, you’re seeing an issue now, so I would start by simplifying stuff first? Commented Jun 15, 2022 at 16:53
  • 1
    The code in itself is not complicated. I prefer the approach where I understand what fails in order to be able to improve the code accordingly. However, I'll still have a look to see if this can be improved. Commented Jun 15, 2022 at 17:02

2 Answers 2

4

The emission of a new value from the observable marks the OnPush component to be checked in the next change detection cycle. But I think in your case, there is simply nothing happening that triggers a CD cycle in the application. That explains why the line with the setTimeout fixes it: setTimeout triggers a CD cycle and because the component is marked to be checked, it is updated.

Sign up to request clarification or add additional context in comments.

6 Comments

I think you probably nailed it.. that must be it. Not a real-life scenario this it seems?
You're right, il from Mars and I'm trying to find weird scénario not happening in the real life to make expert think a lot ;-) thanks for the answer, it would actually make sense.
@ssougnez 😂… let’s just say, I’ve never encountered this specific scenario. I wonder if the result will be the same if you’d use something like NgRx. Wouldn’t surprise me If it also won’t update the dom.
Hm, I guess if you bind the loading property via an async pipe, it should still work: Clicking a button triggers CD -> because the input changed, the component gets checked, the setter is executed, loading$ emits true, view is updated. A finished http call also triggers CD afaik, loading$ emits false and thus marks the component to be checked in this CD -> the view is updated.
It doesn't seem to matter if the value is a primitive or Observable. If next is called outside of the zone, Angular doesn't care. I thought using observables in templates are automatically subscribed and UI updated regardless if it's change outside the zone. I wish there was an easy way to tell Angular to run everything in the template in zone. Especially for observables. Or a pipe to wrap/run in zone. Currently I have to do this manually in ugliest way in ts.
|
1

Add .slice(), angular will refresh.

let objects = this.state.objects.slice();
this.state = {...this.state, objects};

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.