Aurelia repeat.for does not refresh when model changes

本小妞迷上赌 提交于 2020-01-14 03:24:17

问题


I want to build a simple custom component with Aurelia that allows user to input one or more strings. When there are more than one items, list should show remove button for each item on the list.

My problem is that the first item of the list does not show remove button when there are multiple items in the list. This is how it looks

Here is the code and html I have for the custom list component:

View

<template>
  <div repeat.for="item of items">
    <input type="text" value.bind="items[$index]">
    <button click.delegate="remove($index)" 
            if.bind="hasMoreThanOne()">Remove</button>
  </div>
  <button click.delegate="add()">Add</button>
</template>

ViewModel

export class List {
  items: string[];

  constructor() {
    this.items = [];
    this.add();
  }

  add() {
    this.items.push("");
  }

  hasMoreThanOne() {
    return this.items.length > 1;
  }

  remove(index) {
    this.items.splice(index,1);
  }
}

My question is two-fold:

  • Why first list item is not automatically updated when list length changes?
  • How to make first item to show remove button as well?

回答1:


Aurelia treats any functions that are part of a bind command as pure functions. This means that it will not call the function again until the parameters being passed to the function have changed. Since hasMoreThanOne() has a return value that changes based on something that isn't a parameter to the function (naturally, since the function doesn't have any parameters), Aurelia isn't going to call the function again.

The reason Aurelia doesn't re-evaluate the function when the array changes is that the repeater is optimized and sees that the first item in the array has not changed, so it just keeps using the existing DOM it has for it. With a properly created view, this helps greatly increase performance, but in your case, it's causing unwanted issues.

You found one, non-optimal way to deal with this, by using a getter. The reason this is non-optimal is that Aurelia, by default, uses dirty checking every 200ms to check for changes to getters. This fixes the problem you had, but isn't ideal for performance.

The simplest option, given how simple the hasMoreThanOne() function is, would be to simply inline the function in your binding, like this:

<template>
  <div repeat.for="item of items">
    <input type="text" value.bind="items[$index]">
    <button click.delegate="remove($index)" 
            if.bind="items.length > 1">Remove</button>
  </div>
  <button click.delegate="add()">Add</button>
</template>

This is honestly how I would probably handle this.

You could also use the getter as you are doing, but attach the computedFrom decorator to it to preclude dirty checking:

import {computedFrom} from 'aurelia-framework';

export class List {
  items: string[];

  constructor() {
    this.items = [];
    this.add();
  }

  add() {
    this.items.push("");
  }

  @computedFrom('items.length')
  get hasMoreThanOne() {
    return this.items.length > 1;
  }

  remove(index) {
    this.items.splice(index,1);
  }
}

This will give you the exact same performance as the inlined binding I used above, but there is a bit more code to write.




回答2:


If you would pass items.length into hasMoreThanOne() then Aurelia would recalculate the method each time the length changes. Like this: hasMoreThanOne(items.length).

In Html Aurelia doesn't reevalute functions unless the parameters have changed.

I just solved an issue i had with a function using I18n, a function in a table. The function wouldn't reevalute the funtion when the language changed. By passing a boolean to the function which i changed on an event I solved it. Even though the actual data to the function didn't change the changed boolean triggered Aurelia to revalute the whole function.

Like this:

<td>
${functionToTrigger(item.value, triggerBool}
</td>

then

this.eventAggregator('notactualli18nEvent:changed',()=> {this.triggerBool = !triggerBool});



回答3:


I was able to solve this with pure luck. Turning the hasMoreThanOne() function into property fixed the issue and now the remove button is visible also for the first item in the list.

Here are the changes I made:

hasMoreThanOne() {
    return this.items.length > 1;
}

is now

get hasMoreThanOne() {
    return this.items.length > 1;
}

and similar change to view:

<button click.delegate="remove($index)" 
            if.bind="hasMoreThanOne()">Remove</button>

is now

<button click.delegate="remove($index)" 
            if.bind="hasMoreThanOne">Remove</button>

I'm still confused why this changed anything. So if anyone is able to explain me this behaviour, I'm more than happy to hear about it.



来源:https://stackoverflow.com/questions/42624304/aurelia-repeat-for-does-not-refresh-when-model-changes

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!