Observe property on an array of objects for any changes

前端 未结 4 517
再見小時候
再見小時候 2020-12-24 14:48

I am using Aurelia and I have an array of items bound to a grid and they have a selected property on them. I want to bind a button to be enabled when any one of the items is

相关标签:
4条回答
  • 2020-12-24 14:56

    In addition to the Jeremy's examples, you can create a custom setter, for example:

    class Item {
       // this is your ~private~ field
      _isSelected = false;
    
      // in our constructor, we pass the view model and the property name
      constructor(vm, prop, name) {
        this.vm = vm;
        this.prop = prop;
        this.name = name;
      }
    
      get isSelected() {
        return this._isSelected;
      }
      // when you set the value, you increase the vm's property
      set isSelected(value) {
        if (value !== this._isSelected) {
          this.vm[this.prop] += value ? 1 : -1;
          this._isSelected = value;
        }
      }
    }
    
    export class MyViewModel
    {
      items = [];
      itemsSelected = 0; // that's the property we'll pass to the class we've created
    
      constructor() 
      {
        for (let i = 0; i < 50; i++) {
          // instead of adding a annonymous {} here, we add an instance of our class
          this.items.push(new Item(this, 'itemsSelected', `Item ${i+1}`));
        }
      }
    
      toggleIsSelected(item) {
        item.isSelected = !item.isSelected;
      }
    }
    

    I've created a plunker for you: http://plnkr.co/edit/OTb2RDLZHf5Fy1bVdCB1?p=preview


    Doing that, you'll never be looping to see if some item has changed.

    0 讨论(0)
  • 2020-12-24 14:57

    I think you can also leverage EventAggregator as shown here. In that way there is no need to perform dirty checking all the time and instead handle the item selection event in its own VM and publish the eventdata; the subscriber on the other side will listen to the same and perform the gymnastic needed.

    However, I have never used it, so I am not sure about the deeper details of it. But from the documentation it looks pretty easy.

    0 讨论(0)
  • 2020-12-24 15:03

    Jeremy got me thinking about this in this bug. So it looks like you can also get the binding refreshing via a custom Binding Behaviors. Hopefully Jeremy can confirm I'm not doing anything too silly here.

    Used like this:

    repeat.for="item of items | filter & array:'propertyName'"

    It overrides the standard observe behaviour and observes on the array and the property you define on each item. It can probably be improved to be more generic...

    function observeProperty(obj, property) {
      this.standardObserveProperty(obj, property);
      let value = obj[property];
      if (Array.isArray(value)) {
        this.observeArray(value); 
        for(let each of value){   
          this.standardObserveProperty(each, this.propertyName); 
        }
      }
    }
    
    export class ArrayBindingBehavior {
      bind(binding, source, property) {
        binding.propertyName = property;
        binding.standardObserveProperty = binding.observeProperty;
        binding.observeProperty = observeProperty;
      }
    
      unbind(binding, source) {
        binding.observeProperty = binding.standardObserveProperty;
        binding.standardObserveProperty = null;
        delete binding.propertyName;
      }
    }
    
    0 讨论(0)
  • 2020-12-24 15:08

    Few things you could do- assuming I have your use case right:

    dirty-checking (it's only one property- not a big deal)

    export class Item {
      selected = false;
    }
    
    export class ViewModel {
      items = [new Item(), new Item(), new Item()];
    
      get anySelected() {
        var items = this.items, i = items.length;
        while(i--) {
          if (items[i].selected) {
            return true; 
          }
        }
        return false;
      }
    }
    

    observe the items

    import {BindingEngine, inject} from 'aurelia-framework';
    
    export class Item {
      selected = false;
    }
    
    @inject(BindingEngine)
    export class ViewModel {
      items = [new Item(), new Item(), new Item()];    
      anySelected = false;
      subscriptions = [];
    
      constructor(locator) {
        this.bindingEngine = bindingEngine;
      }
    
      updateAnySelected() {
        var items = this.items, i = items.length;
        while(i--) {
          if (items[i].selected) {
            this.anySelected = true;
            return;
          }
        }
        this.anySelected = false;
      }
    
      activate() {
        var items = this.items, i = items.length, observer;
        while(i--) {
          observer = this.bindingEngine.propertyObserver(items[i], 'selected');
          subscriptions.push(observer.subscribe(() => this.updateAnySelected());
        }
        this.updateAnySelected();
      }
    
      deactivate() {
        let dispose;
        while(subscription = subscriptions.pop()) {
          subscription.dispose();
        }
      }
    }
    

    use a collection class

    import {computedFrom} from 'aurelia-framework';
    
    export class Item {
      _selected = false;
    
      constructor(parent) {
        this.parent = parent;
      }
    
      @computedFrom('_selected')
      get selected() {
        return this._selected;
      }
      set selected(newValue) {
        newValue = !!newValue;
        if (newValue === _selected) {
          return;
        }
        _selected = newValue;
        this.parent.itemChanged(newValue);
      }
    }
    
    export class Items {
      items = [];
      selectedCount = 0;
      anySelected = false;
    
      createItem() {
        let item = new Item(this);
        this.items.push(item);
        return item;
      }
    
      itemChanged(selected) {
        this.selectedCount += (selected ? 1 : -1);
        this.anySelected = this.selectCount > 0;    
      }
    }
    
    export class ViewModel {
      items = new Items();
    
      constructor() {
        let item = this.items.createItem();
        item = this.items.createItem();
        item = this.items.createItem();
      }
    }
    

    use a selectedItems array instead of a selected boolean prop

    export class ViewModel {
      items = [{}, {}, {}];
      selectedItems = [];
    
      selectItem(item) {
        this.items.push(item);
      }
    
      deselectItem(item) {
        this.items.splice(this.items.indexOf(item), 1);
      }
    }
    

    for binding purposes, use selectedItems.length as your "any selected" property

    0 讨论(0)
提交回复
热议问题