Use Ember.PromiseProxyMixin in a theme

AI-generated summary

The Ember.PromiseProxyMixin has been added to Ember modules in the Discourse core, enabling easier work with Promises inside Ember components. zcuric provides an example of using this mixin in a Discourse theme to style a poster’s avatar if they are a staff member. The example involves creating a PromiseObject by extending EmberObject with PromiseProxyMixin, then using a computed property to wrap a promise with PromiseObject. This allows for easy handling of the promise’s state in the template. joffreyjaffeux appreciates the write-up and looks forward to exploring the mixin’s potential in plugins, themes, and the Discourse codebase.

Recently the Ember.PromiseProxyMixin was added to ember modules in the discourse core.

https://github.com/discourse/discourse/pull/9312

It’s very useful Ember mixin which allows you to make async requests and to work easily with Promises inside Ember components. Think of it as React Suspense but for Ember. The potential is huge for theme and plugin development in discourse.

Here is the official Ember documentation for Ember.PromiseProxyMixin.

And here you can find a detailed non-discourse example how to use Ember.PromiseProxyMixin.

I’ll give a simple example how can this mixin be used inside discourse theme. Let’s say for some reason in topic-list-item component you want to style the poster avatar if the poster is staff member, the avatar has to have a different border.

Somewhere in topic-list-item.js(.es6) you will have to:

  • Import EmberObject and PromiseProxyMixin and extend EmberObject with PromiseProxyMixin
import EmberObject from "@ember/object";
import PromiseProxyMixin from "@ember/object/promise-proxy-mixin";

const PromiseObject = EmberObject.extend(PromiseProxyMixin);

// You can use a custom memoize function to prevent multiple request for same users
const getUser = memoize(username => ajax(`/u/${username}`).then(data => data));
  • Then you will have make these computed properties
  // Computed Promise
  @discourseComputed("topic")
  posterPromise(topic) {
    const { user } = topic.posters[0];
    return getUser(user.username);
  },

 // This CP wraps the promise with with `PromiseObject`
  @discourseComputed("posterPromise")
  posterProxy() {
    const promise = this.get("posterPromise");
    return promise && PromiseObject.create({ promise });
  },

  posterData: reads("posterProxy.content.user"),

  @discourseComputed("posterData")
  isStaff() {
    const posterData = this.get("posterData");
    if (!posterData) return false;
    const { groups = [] } = posterData;
    // To see if the user is a staff member we have to check
    // if the use belongs to a staff group
    return groups.some(({ name }) => name === "staff");
  }

And in the template topic-list-item.hb(r|s) you would have something like this:

  {{#if posterProxy.isFulfilled}}
    {{#if isStaff}}
      // do your customisation
    {{/if}}
  {{/if}}

You can utilise isPending to show a loader maybe.

I hope this not-so-useful example can help you figure out how the mixin works and maybe it helps you find a way you could use it in your theme/plugin development.

:wave:

4 Likes

Hi @zcuric,

thanks for this very nice write up and the initial PR, there’s definitely things to explore here, in plugins/themes but also in the Discourse codebase. We will see how to apply this pattern and maybe make it simpler in the future :+1:

5 Likes