Ember Octane: A Brief Reactivity Overview

Tags

Chevrolet Corvette Speedometer

Ember.js has entered a new paradigm with Ember Octane, including the introduction of sleek new pull-based reactivity primitives. With Ember Octane, developers can easily build impactful web applications and have fun while doing it. Although many developers are eager for additional Ember advancements, I believe this change to a pull-based reactivity system will pay dividends heartily for the community. However, it is a new shift in how we think about data flowing and reacting throughout an application.

Let’s take a look at what this transition means and understanding potential challenges.

What is a pull-based reactivity system?

Chris Garrett (@pzuraq) has brilliantly explained reactivity and autotracking in detail, so I highly recommend you read his blog for an in-depth source of truth.

Here is my short and sweet version: A pull-based reactivity system seeks to react to changes in state by causing consumers of said state to re-run. This is fundamentally different than many concepts you may have touched in the past. If you’ve built an Evented system before, it requires “pushing” a new change to something listening. When another part of the system receives a “message,” it will re-run. See this example of one I built for ember-infinity.

In Ember, to declare a property as trackable, we decorate it with @tracked. When this state is updated, dependent parts of your system will re-run. Dynamic state, such as arguments passed to components on this.args, are also auto-tracked. You don’t have to do anything! If a component consuming some piece of state off of this.args is updated, dependent getters will be invalidated and will re-run!

get isEmberAwesome() {
 // this getter will re-run if this.args.yes ever changes
 return this.args.yes;
}

I would also highly recommend debugging through tracked-redux. This helps drive home a “bare metal” version of a “pull” based system with caches and tags. Caches and tags are the essence of a program that implements pull based reactivity.

Ok, what are some of the potential challenges associated with moving from a push to a pull based system in your Ember application?

Challenges

When it comes to switching to pull-based reactivity in an Ember.js application, there are some potential caveats to be aware of.

  1. Most of our applications will live between a “push” and “pull” based world for quite some time. As such, the most prominent source of errors has to do with migrating logic that does not have tests asserting reactivity changes across components. Often, after converting a component to a @glimmer/component or a service/utility with tracked state, I remove all the computed decorators or observers. This better aligns our code with native JS (no Ember-isms) and opts-in to the “pull” based reactivity system. However, I may not catch a getter in a legacy component consuming this state. As a result, @dependentKeyCompat is often necessary to ensure your getters and dependent state interop with existing computed properties. This helpful “gotcha” guide offers four important points on computed properties and how they function when interopting tracked properties with existing code, including:

    • Can use “regular” properties in its DK list, that are updated using Ember.set(…). Inside the CP’s function, you can access them with or without Ember.get(…). This is the part you already know.

    • Can use @tracked properties in its DK list.

    • Can use “getters” that are marked as @dependentKeysCompat in its DK list.

    • Cannot use unmarked getters or regular properties that are not updated with Ember.set(…) in its DK list. (You won’t get an error, they just won’t cause the CP to recompute as you may expect.)

  2. Computed and observer based utilities may not play nice with @tracked. Let’s take a computed macro like sort, as an example. These computed macros rely on the same “push” based mechanisms we have talked about. The source of the error may vary, so it can be difficult to pinpoint “why” one of these computed macros might not work. When I have come across these situations in my own code base, I migrate the logic to pure JavaScript. However, there is an alternative option. Luckily, Chris Garrett created macro-decorators. This will install a getter and/or setter without relying on Ember’s computed logistics.

  3. I often see state that doesn’t need to be @tracked. It is cheap to add; however, we may not document why it needs to be @tracked. I’ve tried my best to document why each and every property needs to be @tracked. Why? Because documenting tracked properties can help to communicate intention. When the system is refactored, you will give the next person a clear idea of how this piece of state interacts with the rest of the system.

  4. Lastly—and I have yet to see a standard solution established for this problem—issues will arise if you try to read and write a tracked property in the same run loop or return an unstable value from a getter. This often presents itself as an error such as “used previously in the same computation.” I’m certain this will be solved through improved glimmer internals. But, for now, it is best to be aware of this issue and know other people are experiencing it as well. Often, this might be resolved through improved data flow. For example, if you have a component that renders data off a service at the top of your component tree but you modify/add to that data lower in your component tree, this error may surface during the render phase of your application. A solution would be to ensure your data flows “down,” potentially requiring a more explicit contract between where you modify data and how the data is subsequently rendered.

Wrapping Up

Ember.js has once again shown its ability to innovate and bring new, more developer-friendly experiences to the community. Although moving your application to a completely different state paradigm is no easy task, I’m extremely happy with the escape hatches and documentation put forth.

Finally, a huge thanks to those in the community who have pushed this through while maintaining backwards compatibility with Ember’s old reactivity system.

Helpful Resources

DockYard is a digital product agency offering custom software, mobile, and web application development consulting. We provide exceptional professional services in strategy, user experience, design, and full stack engineering using Ember.js, React.js, Ruby, and Elixir. With a nationwide staff, we’ve got consultants in key markets across the United States, including San Francisco, Los Angeles, Denver, Chicago, Austin, New York, and Boston.