# Brace expension in computed with unknownProperty

**URL:** <https://discuss.emberjs.com/t/brace-expension-in-computed-with-unknownproperty/20612>\
**Category:** General\
**Created:** [September 2, 2024, 4:01pm UTC](https://discuss.emberjs.com/t/brace-expension-in-computed-with-unknownproperty/20612 "2024-09-02T16:01:39Z")\
**Posts on this page:** 4\
**Page:** 1

<div class="post-metadata">

**Author:** ![grunk](https://sea1.discourse-cdn.com/flex019/user_avatar/discuss.emberjs.com/grunk/32/17985_2.png) [@grunk](https://discuss.emberjs.com/u/grunk)\
**Post date:** [September 2, 2024, 4:01pm UTC](https://discuss.emberjs.com/t/brace-expension-in-computed-with-unknownproperty/20612/1 "2024-09-02T16:01:39Z")

</div>

Hi, I recently refactored a service from a PromiseProxyMixin to an Octane Service. This new service use unknownProperty to replicate the previous behaviour :

`this.myService.someRandomProperty`

```auto
class myService extends Service {
	#get(property) {
		if (this.data) {
			return this.data[property];
		}
		else {
			return this.myPromise.then(() => {
				return this.data[property];
			});
		}
	}

	unknownProperty(key) {
		return this.#get(key);
	}
}

```

(simplified snippet)

That part works perfectly.

I have problem when brace expension is used in @computed like so :

```auto
@computed('myService.{prop1,prop2,prop3}')
get myGetter() {
}

```

In this case the computed does not works as attended i have to get the “data” propertie of the service like so

`@computed('myService.data.{prop1,prop2,prop3}')`

Is this a known limitation of unknownProperty ? What happen behind the scene when brace expansion is used ? If i can i 'd like to avoid having to use my service differently in computed

Note : We are running ember 3.24 et slowly migrating to full octane code and more recent ember version , but for now we still have to deal with some computed.

Thanks 🙂

---

<div class="post-metadata">

**Author:** ![sukima](https://sea1.discourse-cdn.com/flex019/user_avatar/discuss.emberjs.com/sukima/32/5773_2.png) [@sukima](https://discuss.emberjs.com/u/sukima)\
**Post date:** [October 10, 2024, 1:15am UTC](https://discuss.emberjs.com/t/brace-expension-in-computed-with-unknownproperty/20612/2 "2024-10-10T01:15:30Z")

</div>

First, `unknownProperty` is a feature available to pre-octane ember. Granted it still likely works in later versions it is a big foot-gun and I’m pretty sure I seen talks about removing it.

Good news is that since we now focus on `@tracked` in native classes this kind of thing can be achieved with native JavaScript’s Proxy—with a little redesign.

To prepare yourself for that eventual upgrade I might approach this by splitting the responsibilities up. One of the advantage of the _now deprecated_ PromiseProxyMixin is that it had a consistent type. It didn’t matter if it proxy’ed a Promise or a raw value it always offered the same interface (`.content`, `.isPending`, `.isFulfilled`, `.isRejected`, and `.reason`). In more modern Ember we would either make our own wrapper for this or use an addon like [ember-concurrency](https://ember-concurrency.com).

If this were octane and I did not have ember-concurrency I think I would consider faking updates by using a counter system. The down side to this is that reactivity is at the top most level. I’d have to pull-in [another addon](https://github.com/tracked-tools/tracked-built-ins) to facilitate deep reactivity.

Based on the code above It looks like the reactive aspects is likely just the promise resolution. Using that assumption we can probably simplify things by moving the hard work to another class and offering a Proxy to consumers of the service. It does introduce one level of properties but that does keep things clean and less magic going on plus avoid the confusing `unknownProperty` mechanics.

```js
class AsyncThing {
  status = 'pending';

  get isPending() { return this.status === 'pending'; }
  get isFulfilled() { return this.status === 'fulfilled'; }
  get isRejected() { return this.status === 'rejected'; }

  constructor(maybePromise) {
    this.promise = Promise.resolve(maybePromise);
    this.promise.then(
      (value) => {
        this.status = 'fulfilled';
        this.value = value;
      },
      (reason) => {
        this.status = 'rejected';
        this.reason = reason;
      },
    );
  }
}

export default class MyService extends Service {
  #cache = new Map();

  data = new Proxy({}, {
    get: (_, prop) => this.#cache.get(prop) ?? this[prop],
    set: (_, prop, value) => {
      const notifyChanged = () => this.notifyPropertyChanged(`data.${prop}`);
      const wrapper = new AsyncThing(value);
      wrapper.promise.finally(notifyChanged);
      this.#cache.set(prop, wrapper);
      notifyChanged();
      return true;
    },
  });
}

```

By contrast in Octane it would look more like this:

```js
class AsyncThing {
  @tracked value;
  @tracked reason;
  @tracked status = 'pending';

  get isPending() { return this.status === 'pending'; }
  get isFulfilled() { return this.status === 'fulfilled'; }
  get isRejected() { return this.status === 'rejected'; }

  constructor(maybePromise) {
    this.promise = Promise.resolve(maybePromise);
    this.promise.then(
      (value) => {
        this.status = 'fulfilled';
        this.value = value;
      },
      (reason) => {
        this.status = 'rejected';
        this.reason = reason;
      },
    );
  }
}

export default class MyService extends Service {
  @tracked _counter = 0;
  #cache = new Map();

  data = new Proxy({}, {
    get: (_, prop) => {
      this._counter;
      return this.#cache.get(prop) ?? this[prop];
    },
    set: (_, prop, value) => {
      this.#cache.set(prop, new AsyncThing(value));
      this._counter++;
      return true;
    },
  });
}

```

Not sure how useful this all is. It isn’t an exact answer but it does offer an opportunity to rethink you “Data Down; Actions Up” design.

Also, I haven’t used this addon in years but it might offer something for you? [ember-tracked-polyfill](https://github.com/sukima/ember-tracked-polyfill)

---

<div class="post-metadata">

**Author:** ![sukima](https://sea1.discourse-cdn.com/flex019/user_avatar/discuss.emberjs.com/sukima/32/5773_2.png) [@sukima](https://discuss.emberjs.com/u/sukima)\
**Post date:** [October 10, 2024, 1:19am UTC](https://discuss.emberjs.com/t/brace-expension-in-computed-with-unknownproperty/20612/3 "2024-10-10T01:19:46Z")

</div>

Whoa whoa wait! `@tracked` was available in 3.24!

Use the `@tracked` version above. Better yet consider managing this with [ember-concurrency](https://ember-concurrency.com/) and/or [tracked-built-ins](https://github.com/tracked-tools/tracked-built-ins)

I made that previous post way too complicated!

Also, once you get past 3.26 you’ll get access to [ember-resources](https://github.com/NullVoxPopuli/ember-resources) which would simplify all of this into almost a one liner. ember-resources is awesome and makes this so much easier.

@NullVoxPopuli mind checking my accuracy on this thread?

---

<div class="post-metadata">

**Author:** ![NullVoxPopuli](https://sea1.discourse-cdn.com/flex019/user_avatar/discuss.emberjs.com/nullvoxpopuli/32/18691_2.png) [@NullVoxPopuli](https://discuss.emberjs.com/u/NullVoxPopuli)\
**Post date:** [October 16, 2024, 3:36pm UTC](https://discuss.emberjs.com/t/brace-expension-in-computed-with-unknownproperty/20612/4 "2024-10-16T15:36:52Z")

</div>

> [@sukima](#):
>
> @NullVoxPopuli mind checking my accuracy on this thread?

ofc 🎉

> [@sukima](#):
>
> Whoa whoa wait! `@tracked` was available in 3.24!

tracked is available in 3.13+

> [@sukima](#):
>
> once you get past 3.26 you’ll get access to [ember-resources](https://github.com/NullVoxPopuli/ember-resources)

yeah, minimum possible working version is 3.25, but 3.28 is preferred.

> [@sukima](#):
>
> ember-resources is awesome and makes this so much easier.

thanks!! \<3

> [@sukima](#):
>
> I’d have to pull-in [another addon](https://github.com/tracked-tools/tracked-built-ins) to facilitate deep reactivity.

it’s still shallow reactivity, but allowing dynamic reactive entries within the flat structures (Object, Array, Map, Set, WeakMap, WeakSet). For deep reactivity, there is

> **[GitHub - NullVoxPopuli/ember-deep-tracked: Deep auto-tracking for when you just...](https://github.com/NullVoxPopuli/ember-deep-tracked/tree/main)**
>
> main

> [@sukima](#):
>
> ```auto
> export default class MyService extends Service {
> @tracked _counter = 0;
> 
> ```

I’m against counters being exposed as public API. Instead, if you’re wanting to just “retry” a failed a promise / function, I’d implement a retry method that calls the async-function again (but maybe I’m missing something – not sure what the purpose is exactly in my skimming).

> [@sukima](#):
>
> an opportunity to rethink you “Data Down; Actions Up” design.

aka “one way data flow”

hope this helps!
