Trying to get rid of observers

Hello,

I have a fundamental question about the architecture of my ember app. Currently I use a ton of observers, and when I updated to the latest version of ember, I now get hundreds of warnings about that (I already disabled them so I can continue developing, but that’s probably not a permanent solution). I wonder whether there is any way to get rid of them.

My application is special in that it uses WebGL for rendering. This API is different than traditional web rendering in that it’s imperative rather than declarative. My observers trigger whenever anything in my model changes, so I can rerender my viewport. I don’t think I can switch to a computed property, because a WebGL canvas isn’t something you compute.

One of the side effects of trying to integrate ember with this concept is that under some conditions, my observer is triggered multiple times for fundamentally the same change, which leads to many unnecessary redraws.

Can somebody give me some hints on how to improve the architecture there? Moving away from WebGL is not an option (until WebGPU is available, but I’m not sure whether that even solves the problem).

Can you share the warnings that you’re getting? As far as I understood they were heavily discouraged but not necessarily going away (or at least anytime soon, see this guides page). I know there were certain observer creation patterns that were discouraged and/or deprecated, like the prototype extensions version (but that was just introduced in 3.11, is that what you mean by “latest version”?). If it’s something like that it should be pretty easy to change to the newer syntax and keep all your old observers.

That said it’s probably replace them where you can (if it makes sense), and there is at least one alternative in the component lifecycle hooks: didReceiveAttrs/didUpdateAttrs. But depending on how your app is written right now that could theoretically change your architecture significantly.

Thank you for the response!

I get this warning. It doesn’t look like it’s a deprecation warning, but I still don’t take warnings lightly.

I meant ember-3.10. I was on 3.7.1 before that, which didn’t have that warning. I’m not using the function prototype version anyways, though.

didReceiveAttrs looks interesting, but since it’s not single attributes on a component, but a whole ember-data store I’m rendering (including relationships), I don’t think that this would be prudent. I’d have to recreate a whole component hierarchy that just emits empty DOM nodes to trigger a redraw.

Ah ok yeah in that case I’d just disable it an not worry about it. As it says in the lint rule you linked: “Usage of observers is very easy BUT it leads to hard to reason about consequences. Unless observers are necessary, it’s better to avoid them.”

I’d argue in this case that they seem necessary.

The reason it showed up when you upgraded is probably because the eslint rules got bumped during the upgrade. If you’re not super familiar with ESLint or linters in general they’re basically just rules to help you write “good practice” code. Ember comes with a baked in set and you can install stricter ones to really nitpick your code for stuff like whitespace and dangling commas. Sticking with the lint rules is generally good, but if you just don’t like a rule or have special cases (like your app) there’s no harm at all in disabling a lint rule. I wouldn’t worry about it.

All right, thank you! Then I’m just going to disable the warning and not worry about it.

I agree that an observer is probably appropriate for tying together Ember and a WebGL canvas. That kind of use case is why observers themselves are not removed or deprecated in Ember. The lint rule is more of a guideline, and it’s right more often than it’s wrong.

For your use case, I would suggest making a single component that handles the boundary between all your ember stuff and your webGL stuff. It would have an observer. You can disable the lint rule for only that one spot.

This will also help get the timing correct:

because you can fix it once in your webgl-manager component and use that everywhere else.

An example of what I mean is something like:

{{my-webgl-canvas inputs=(hash flavor=this.flavor) draw=this.draw}}

this.draw is a function that gets the raw WebGL element and the user-provided inputs (like { flavor: "chocolate" }) as its arguments, and is responsible for doing the webgl drawing.

my-webgl-canvas is a component that actually creates the canvas element, observes the inputs for changes and schedules a call to the draw function.

You can do the scheduling using once. Your observer calls once, and once calls your real drawing code.

1 Like

Oh, I remembered there is a newer alternative to observers for this. It’s @ember/render-modifiers.

Assuming you have a component with a draw function that needs to run at initial render and rerun whenever this.flavor changes, you can say:

<canvas {{did-insert this.draw this.flavor}} {{did-update this.draw this.flavor}} />
// in the component JS:
draw(element, [flavor]) {
  // use the element and the arguments to do the actual webgl calls here
}
2 Likes