How to force binding re-evaluate or re-rendering in Aurelia
Asked Answered
P

1

12

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...

Prepuce answered 4/4, 2016 at 4:4 Comment(0)
H
24

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]);
  }
});
Hammock answered 4/4, 2016 at 18:15 Comment(4)
Fantastic, the explanation are exactly what I wanted to know and stays out of dirty checking... is that document somewhere by any changes? This information is gold :)Prepuce
in some cases this trick doesn't work and I found another SO Question which detail how to do it through a BindingEnginePrepuce
If there’s a case where splice doesn’t work, it’s a bug- please report it 👍Hammock
I should have mentioned that it wasn't working with a push to the array, which you did write in the other SO question. I just realized it was also answered by you :PPrepuce

© 2022 - 2024 — McMap. All rights reserved.