1

As the title suggests, I am trying to bind an elements source to a computed array which is populated by results from a http.fetch call. Here is the code (the interesting parts for this question):

The view:

<tr>
    <td repeat.for="boardItemFilter of boardItemFilters">
        <input if.bind="boardItemFilter.isStringFilter()" type="text" value.bind="boardItemFilter.Value & debounce:400" />
        <div if.bind="boardItemFilter.isCheckboxFilter()" repeat.for="sourceValue of boardItemFilter.SourceValues">
            <input type="checkbox" value.bind="sourceValue" checked.bind="boardItemFilter.SelectedValues" />
            <span textcontent.bind="sourceValue"></span>
        </div>
    </td>
</tr>

The viewmodel:

@computedFrom('selectedUnit')
get boardItemFilters(){
    let unitName = "";
    if(this.selectedUnit != null){
        unitName = this.selectedUnit.Name;
    }
    return this.datacontext.fetchBoardItemFiltersByUnit(unitName)
    .then(result => console.log(result);
}

Where the fetchBoardItemFiltersByUnit() function is:

fetchBoardItemFiltersByUnit(unitName){
    let self = this;
    let request = {'unitName': unitName};
    return this.http.fetch('BoardFilters',{
        method: 'post',
        body: json(request),
    }).then(response => response.json())
      .then(response => this.ExtendFilters(response));
}

The boardItemFilters() computed triggers on selectedUnit change properly. The datacontext call is made, that part works just fine. The problem happens when aurelia tries to bind the property before the http.fetch is done. It then thinks that the property is not repeatable, and breaks. After those errors, the console.log(result) displays the expected array of objects that the server returned, but the result is not assigned to the computed property. I have tried to manually re trigger the binding using the signal behavior, with no luck.

The question is naturally, what am i doing wrong? Are the results not being stored into the computed property, or am binding it in a way that is not expected? What would be the proper solution to this problem?

I have tried a lot of things, and the only thing that seemed to work is to create a local variable which stores the http results, and always return that variable OUTSIDE the promise functions. But naturally, that will always return the previous state since it will run the asynch call, and return the local variable before the call finishes. This is of course unacceptable.

Or does Aurelia simply not yet support async binding?

Thank you in advance!

EDIT: I have managed to hack into a solution, but it's pretty ugly (using the bindingSignaler in the promise function to rebind, using the newValue and oldValue comparison to avoid infinite computed calls etc). I would still much like to hear the proper way to achieve this!

HotTowelie
  • 123
  • 8
  • you are trying to make an http call in every iteration of the `repeat.for`, which is definitely not a good approach in my opinion. You should rewrite the whole algorithm. In addition, to bind computed properties you don't have to use parenthesis, this `if.bind="boardItemFilter.isStringFilter"` is enough – Fabio May 17 '16 at 15:31
  • Am I? I was under the impression that I will populate the array once, and then just iterate over it with the repeat.for.. could you explain please? EDIT: i just had a look at the network traffic, the api is called only once, not for every iteration.. – HotTowelie May 17 '16 at 15:38
  • Oh, and the isStringFilter() is a function with which i extended the filter object, so the parenthesis are needed.. i could convert it to a computed property though, would make more sense. Thanks :) – HotTowelie May 17 '16 at 15:45
  • Sorry!!! I made a mess with `boardItemFilters` and `isStringFilter()`. They are very similar, I thought they were the same lol. I will read the question again then I can share my opinion – Fabio May 17 '16 at 16:12
  • Maybe [aurelia-async](https://github.com/jdanyow/aurelia-async) is what you're looking for. It's still experimental. Though, I would solve this by changing the logic structure instead of relying on some kind of async binding. – qtuan May 18 '16 at 06:57
  • I checked out aurelia-async yesterday, but judging by this latest issue: https://github.com/jdanyow/aurelia-async/issues/4 It does not work at the moment. How would you change the logic structure if you don't mind me asking? Up until now i've used knockout and Q so i'm used to async binding, not sure how to go about this with aurelia.. – HotTowelie May 18 '16 at 07:35

1 Answers1

3

Here is a way to solve this kind of lengthy computed operation, which do not rely on async binding:

  • Change the dependent property from a computed to a normal property.
  • Manually observe the dependencies to invoke the lengthy operation. Only when the lengthy operation finishes, then update the dependent property.

Example:

export class App {
  @bindable count;

  constructor() {
    this.count = 3;
    this.computeNums(this.count)
      .then(result => this.nums = result);
  }

  countChanged(value) {
    this.computeNums(value)
      .then(result => this.nums = result);
  }

  computeNums(count) {
    let result = [];
    for (let i = 1; i <= count; i++) {
      result.push(i);
    }

    return new Promise((resolve, reject) => {
      setTimeout(() => resolve(result), 1000);
    });
  }
}

Here, nums is computed from count. computeNums simulate a lengthy operation.

Observing count is achieved by a combination of @bindable and countChanged. You may want to use binding engine API instead, as in this question.

Full example that can run: https://gist.run/?id=f2e26044796bfe2334d786554039aab0

Community
  • 1
  • 1
qtuan
  • 687
  • 4
  • 10