Best way to Handle Throttled Computed Values?


#1

I am currently encountering Durandal to Aurelia Conversion issues:

The way Aurelia handles computed properties, throttling and subscriptions is very different from the way knockout handled them and I am struggling to convert some code without having to do a large amount of refactoring.

Here is a snippet from Knockout’s Documentation Page that I am struggling to find a proper Aurelia solution for:

this.instantaneousValue = ko.observable();
    this.throttledValue = ko.computed(this.instantaneousValue)
                            .extend({ throttle: 400 });
 
    // Keep a log of the throttled values
    this.loggedValues = ko.observableArray([]);
    this.throttledValue.subscribe(function (val) {
        if (val !== '')
            this.loggedValues.push(val);
    }, this);

I would expect to be able to write something like this in Aurelia:

    //This was the pattern I assumed would work: 
    this.instantaneousValue = null;
    // Keep a log of the throttled values
    this.loggedValues = [];
    this.bindingEngine
          .expressionObserver(this, 'instantaneousValue & throttle:400')
          .subscribe((newValue, oldValue) => { 
            if (newValue !== '') {
              this.loggedValues.push(newValue);
              this.throttledValue = newValue;
            }
          });

The above code did not work so I thought perhaps Aurelia Would Support this:

    this.instantaneousValue = null;
    // Keep a log of the throttled values
    this.loggedValues = [];
    this.bindingEngine
          .propertyObserver(this, 'throttledValue')
          .subscribe((newValue, oldValue) => { 
            if (newValue !== '')
              this.loggedValues.push(newValue);
          });

    @computedFrom('instantaneousValue & throttle:400')
    get throttledValue() {
      return this.instantaneousValue;
    }

But that did not work either.

It seems that there is no supported one-to-one solution. After debugging through the binding engine I discovered that this was possible but I had to write it like this:

    this.instantaneousValue = null;
    // Keep a log of the throttled values
    this.loggedValues = [];

    this.throttledValueBinding = this.bindingEngine
                                    .createBindingExpression('throttledValue', 'throttledValue & debounce:1500', 2, this.resources.lookupFunctions)
                                    .createBinding(this);
    this.throttledValueBinding.bind(view); //assume this is in the created method
    this.throttledValueObserver = subscriptionBinding.observerLocator.getObserver(this, 'throttledValue');
    this.throttledValueSubscription = this.bindingEngine
            .propertyObserver(this, 'instantaneousValue')
            .subscribe((newValue, oldValue) => { 
              this.subscriptionObserver.callSubscribers(newValue, oldValue);
            });
    this.instantaneousValueSubscription = this.bindingEngine
            .propertyObserver(this, 'instantaneousValue')
            .subscribe((newValue, oldValue) => { 
              if (newValue !== '')
                this.loggedValues.push(newValue);
            });

I do not like this solution. It only works because this example does not require us to observe multiple values. Is there a better way of doing this?


#2

You may want to checkout this from @huochunpeng https://github.com/aurelia-contrib/aurelia-getter-throttle


#3

Thanks @bigopon , while that plugin would work well for throttling it doesn’t look like it supports other binding behaviors like ‘debounce’ which knockout supports with the notifyWhenChangesStop method.


#4

That plugin is designed mainly to help dirty check.

To utilize binding behavior for computed property, you can do it in view template (html file) instead of view model (js file)

@bigopon you forgot how versatile your let tag is.

<let throttled-value.bind="instantaneousValue & throttle:400"></let>

#5

Awesome! Thanks @huochunpeng. Is this feature ready for production use? Also, how do you subscribe to a property computed in a let tag?


#6

Yes it is ready for production.
You can ask the let tag to assign the value to view model, and act on change:

<let
  to-binding-context
  throttled-value.bind="instantaneousValue & throttle:400"></let>
export class Bla {
  @observable throttledValue

  throttledValueChanged(value) {
    // do stuff
  }
}

@huochunpeng Lol thanks. I completely forgot about it. Been sucked into router :stuck_out_tongue:


#7

Been trying to get this to work. Looks like I can manually subscribe using a property observer and that will get called however the throttledValueChanged is not being called.

*edit
I am sorry this works perfectly. I put together a simple gist to link here and it worked. I had a typo in my code :frowning: sorry for the preemptive post.


#8

Glad that it worked for you. It’d be nice if you could help with this issue, based on what you experienced using <let/>https://github.com/aurelia/templating/issues/648