computedFrom properties of objects in list

Hey there, I’m a bit stuck in my brain to find a proper solution. In my application there is a model which got a list of child models. A getter isSelected returns a boolean if all children are selected. Dirty checking is not an option. How could I tell the binding engine to connect to the properties of all child objects?

class Parent {
  children = [];

  @computedFrom('children') // Will not trigger when only the property changed
  get isSelected() {
    if (!this.children.length) {
      return false;
    }
    return this.children.every(child => child.selected);
  }
}

My idea was to create a own computedFrom decorator which connects to changes of the items in the list and then connects to the children but so far it is tricky to understand the whole chain from parsing the binding interpolation to the observer on the getter.

Any idea from you guys?

Thanks in advance

Best
FragSalat

2 Likes

What I typically do is keep track of a counter that gets updated each time a modification takes place. Something like:

class  Parent {
     children = [];
     counter = 0;


     toggleChildSelected(child) {
          child.selected = !child.selected;
          this.counter++;
     }

     @computedFrom('children', 'counter')
     get isSelected() { .... }
}

Hope this helps!

1 Like

If I have to watch a lot of properties on each list element I use the BindingEngine and dispose of all subscriptions in detached() and listChanged() and rebind them in attached() and listChanged() after the dispose.

For the update method I’ll just use a debounced function to wait until all list items have settled and 200ms later update anything dependent on selected.

import { BindingEngine } from 'aurelia-framework'; // Inject into view model

... 
// Rebind Subs
this.subscriptions = [];
this.list.forEach(item => {

   let sub = this.bindingEngine
      .propertyObserver(item, 'selected')
      .subscribe(() => this.delayedListUpdate());

   this.subscriptions.push(sub);

});

...
// Dispose Subs
this.subscriptions.forEach(sub => sub.dispose());
this.subscriptions = [];

I wish there was a more intuitive way to do this, but I haven’t seen it yet.

3 Likes

Hey there, thanks for your suggestions but I don’t wanted to accept those not nice solutions and created my own decorator which allows me to tell that a getter is computed from list of objects.

I wrote a short blog post how it works and can be used https://blog.stagetwo.eu/entry/12-aurelia-computedfrom-array-of-objects/

The result looks something like this

class Child {
  selected: boolean = false;
}

class Parent {
  children: Child = [];

  @computedFromList('children[*].selected') // Binds selected property of each object to this getter
  get isSelected() {
    return this.children.every(child => child.selected);
  }
}

But I just figured out some limitations of Aurelia which requires the list of children not to be more than 100 for now.

2 Likes

@fragsalat that’s creative :heart: and glad to see you solved it. But I think we should emphasize @Sjaak 's solution, as it’s often the most efficient way to handle any case.

1 Like

Definitely it would be the easier and probably better way to go but unfortunately our architecture doesn’t fit to that. The user can select 3 hierarchy levels like Grandparent, Parent, Child. Selecting Grandparents selects all Children of all Parents of those Grandparents. On the other wside when all children of a parent or all parents of a grandparent are selected, the grandparent is considered as selected as well. What I want to say with it is that the selection is not called on the parent or grandparent but always on children for having a generic selection logic which makes this construct not work. The children also doesn’t and shouldn’t have a relation to it’s parent.

Would be open for other architectural suggestions :slight_smile:

1 Like

I was only saying that to encourage folks to try @Sjaak solution before using yours. It seems to be suitable for most cases. But there are situations where yours makes more sense. I didn’t mean yours is not good.

1 Like

I have a similar problem, where I’m doing, for example:

@computedFrom("profileNameSetting")
get profileName() {
    return this.profileNameSetting.value;
}

but because profileNameSetting is an object, this getter will never update.

My question is, why doesn’t @computedFrom take object paths as a param, so that you can do, for example:

@computedFrom("profileNameSetting.value")
get profileName() {
    return this.profileNameSetting.value;
}

?

1 Like

Paths are definitely supported, we have countless cases in our application.
This should work:
@computedFrom("profileNameSetting", "profileNameSetting.value")

1 Like

it’s enough to pass “profileNameSetting.value” to the computedFrom. you don’t need to specify “profileNameSetting”.
but you do need to acount for null inside your getter.
return this.profileNameSetting.value; will throw when this.profileNameSetting is null or undefined.
simply change your code to

@computedFrom("profileNameSetting.value")
get profileName(){
   return this.profileNameSetting && this.profileNameSetting.value;
}
1 Like

@computedFrom("profileNameSetting.value") is enough.

1 Like

I have made this plugin here https://www.npmjs.com/package/aurelia-deep-computed to handle this scenario. Usage would be like this

// observe for changes at property profileNameSetting,
// and also observe the properties inside the value of profileNameSetting
@deepComputedFrom('profileNameSetting')
1 Like

@bigopon, with this plugin do the properties have to be known up front or can properties be added to the object later and expect to be observed?

2 Likes

It needs to be known upfront. But you can add via replacing the entire nested object. Im notnsure how to overcome this limitation for now. Same applies for deleting properties.

I started to build something like an observable object for aurelia using es6 proxy some time back. theres a few issues with it (array prototype methods for instance like splice, updating the reference tree) that I haven’t had a chance to revisit. Here it is if you have any ideas on how to improve it: https://github.com/jbockle/aurelia-stores

1 Like

Thanks. Ill have a look. V2 observation using proxy is done phase 1, you can have a look if you want to see some example with proxying array.

Hi @bigopon,

Does the deepComputeFrom also work for arrays, or is it meant to watch the properties of a single specified object?

2 Likes

it watches deeply, means it will observe everything in the root object, and any object nested within it. Array is object too so yes. For array, it observes differently though, same applies for Map/Set

1 Like