How to force binding re-evaluate or re-rendering i

2019-03-18 14:27发布

问题:

I am starting with a simple TODO app with Aurelia, RethinkDB & Socket.IO. I seem to have problem with re-rendering or re-evaluating an object that is changed through Socket.IO. So basically, everything works good on the first browser but doesn't get re-rendered in the second browser while displaying the object in the console does show differences in my object. The problem is only on updating an object, it works perfectly on creating/deleting object from the array of todo items.

HTML

<ul>
    <li repeat.for="item of items">
      <div show.bind="!item.isEditing">
        <input type="checkbox" checked.two-way="item.completed" click.delegate="toggleComplete(item)" />
        <label class="${item.completed ? 'done': ''} ${item.archived ? 'archived' : ''}" click.delegate="$parent.editBegin(item)">
          ${item.title}
        </label>
        <a href="#" click.delegate="$parent.deleteItem(item, $event)"><i class="glyphicon glyphicon-trash"></i></a>
      </div>
      <div show.bind="item.isEditing">
        <form submit.delegate="$parent.editEnd(item)">
          <input type="text" value.bind="item.title" blur.delegate="$parent.editEnd(item)" />
        </form>
      </div>
    </li>
  </ul>

NodeJS with RethinkDB changefeeds

// attach a RethinkDB changefeeds to watch any changes
r.table(config.table)
    .changes()
    .run()
    .then(function(cursor) {
        //cursor.each(console.log);
      cursor.each(function(err, item) {
        if (!!item && !!item.new_val && item.old_val == null) {
          io.sockets.emit("todo_create", item.new_val);
        }else if (!!item && !!item.new_val && !!item.old_val) {
          io.sockets.emit("todo_update", item.new_val);
        }else if(!!item && item.new_val == null && !!item.old_val) {
          io.sockets.emit("todo_delete", item.old_val);
        }
      });
    })
    .error(function(err){
        console.log("Changefeeds Failure: ", err);
    });

Aurelia code watching Socket.on

// update item
socket.on("todo_update", data => {
  let pos = arrayFindObjectIndex(this.items, 'id', data.id);
  if(pos >= 0) {
    console.log('before update');
    console.log(this.items[pos]);
    this.items[pos] = data;
    this.items[pos].title = this.items[pos].title + ' [updated]';
    console.log('after update');
    console.log(this.items[pos]);
  }
});

// create item, only add the item if we don't have it already in the items list to avoid dupes
socket.on("todo_create", data => {
  if (!_.some(this.items, function (p) {
    return p.id === data.id;
  })) {
    this.items.unshift(data);
  }
});

// delete item, only delete item if found in items list
socket.on("todo_delete", data => {
  let pos = arrayFindObjectIndex(this.items, 'id', data.id);
  if(pos >= 0) {
    this.items.splice(pos, 1);
  }
});

The socket.on("todo_update", ...){} is not making the second browser re-render but showing the object in the console before/after update does show differences in the object itself. I even changed the todo title property and that too doesn't get re-rendered.

How can I get Aurelia to re-render in my second browser with the new object properties? Don't be too hard on me, I'm learning Aurelia/RethinkDB/NodeJS/Socket.IO all the same time...

回答1:

Aurelia observes changes to the contents of an array by overriding the array's mutator methods (push, pop, splice, shift, etc). This works well for most use-cases and performs really well (no dirty-checking, extremely lightweight in terms of memory and cpu). Unfortunately this leaves one way of mutating an array that aurelia can't "see": indexed assignment... eg myArray[6] = 'foo'. Since no array methods were called, the binding system doesn't know the array changed.

In your case, try changing this:

// update item
socket.on("todo_update", data => {
  let pos = arrayFindObjectIndex(this.items, 'id', data.id);
  if(pos >= 0) {
    console.log('before update');
    console.log(this.items[pos]);

    this.items[pos] = data; // <-- change this to: this.items.splice(pos, 1, data);

    this.items[pos].title = this.items[pos].title + ' [updated]';
    console.log('after update');
    console.log(this.items[pos]);
  }
});