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.
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);
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.
HistoryEntry
pointing to User(18) isn't getting notified of the change, now that User(18) does exist.HistoryEntry
pointing to User(18) when User(18) is added to the users HashMap.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.