Question

All of the code that is about to follow can be seen in action at this JSFiddle. I have left out the HTML data-binds and templates from this question, but they can be seen in the fiddle. I didn't think they were relevant to illustrating the problem.


Introduction

I'm attempting to link a computed observable to another object that I use as a HashMap.

The object with the computed observable is HistoryEntry:

function HistoryEntry(userId, users) {
    this.UserId = ko.observable(userId);

    this.User = ko.computed(function () {
        if (!users[this.UserId()]) return '(' + this.UserId() + ')';
        return '(' + this.UserId() + ') ' + users[this.UserId()].Name();
    }, this);
}

The HistoryEntry contains a UserId which serves as the lookup value in my JavaScript object serving as a HashMap. users is a reference back to this HashMap contained in the ViewModel.

The HashMap contains User objects keyed by their UserId. The User object is as follows:

function User(id, name) {
    this.Id = ko.observable(id);
    this.Name = ko.observable(name);
}

The idea is that the computed observable, HistoryEntry.User() will lookup the name from the corresponding User object in this HashMap.

The HashMap is stored on the parent ViewModel, along with the observable array of HistoryEntry objects:

function ViewModel() {
    this.users = ko.observable({
        '16': new User(16, 'Jack'),
        '17': new User(17, 'Jill')
    });

    this.history = ko.observableArray([
        new HistoryEntry(16, this.users()),
        new HistoryEntry(17, this.users()),
        new HistoryEntry(18, this.users())
    ]);
}

var view = new ViewModel();
ko.applyBindings(view);

The Problem

As you can see above, I've created only two users, yet I have three history entries, each pointing to a different user. One of these entries refers to a User(18) that doesn't yet exist in the HashMap.

For my example, I change User(16)'s name from 'Jack' to 'John' and add User(18):

setTimeout(function () {
    view.users()[16].Name('John');
    view.users()[18] = new User(18, 'Bill');
    view.users.valueHasMutated();
    view.history.valueHasMutated();
}, 1000);

This correctly updates the HistoryEntry that has a computed observable pointing to User(16).

Likewise, I expected that when I add User(18), the HistoryEntry with a computed observable to User(18) would update with the name of User(18).

Unfortunately, it does not.

Questions

  1. I'd like a deeper understanding of why the HistoryEntry pointing to User(18) isn't getting notified of the change, now that User(18) does exist.
  2. Moreover, I need to find a solution that will update the HistoryEntry pointing to User(18) when User(18) is added to the users HashMap.
Was it helpful?

Solution

HistoryEntry wont get updated because you're not unwrapping users wihin the computed.

Try changing

new HistoryEntry(18, this.users())

To

new HistoryEntry(16, this.users)

Then change HistoryEntry to:

function HistoryEntry(userId, users) {
    this.UserId = ko.observable(userId);

    this.User = ko.computed(function () {
        var usersArr = ko.unwrap(users);
        if (!usersArr[this.UserId()]) return '(' + this.UserId() + ')';
        return '(' + this.UserId() + ') ' + usersArr[this.UserId()].Name();
    }, this);
}

See http://jsfiddle.net/W6nRe/

Computeds keep track of all the observables that are used within the computed, since you were using the underlying users object, it didn't have a reference to users. So computeds only get updated if one of the observables it uses is changed.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top