wonea wonea - 10 days ago 8
TypeScript Question

Trigger computed property update

I'm trying to force update a computed property update from a viewmodel to the UI. However I'm only get the initial value not any further values. Mutating the property fails because it's not a function.

Site

<script src="../../libraries/knockout-3.4.1.js"></script>
<input id="nameInput" data-bind="value: processName" type="text"/>
<input id="nodetotalInput" data-bind="value: processNodeCount" type="text"/>


View Model

export class ProcessViewModel {
public processName: KnockoutObservable<string>;
public processNodeCount: KnockoutObservable<number>;

constructor() {
try {
this.testService = new Test.TestService();

this.setBindings();
}
catch (e) {
console.log(e);
}
}

public setBindings(): void {
this.processName = ko.computed<string>(
function() { processViewModel.isLoaded() ? processViewModel.testService.flowModel.process.name : ""; }
);
this.processNodeCount = ko.computed<number>(
function() { processViewModel.isLoaded() ? processViewModel.testModel.nodeCount() : 0; }
);
}

public isLoaded(): boolean {
return this.testService.isLoaded();
}

public refreshProcessDetails() {
try {
let message: string = "IsLoaded" + this.isLoaded();
console.log(message);

/** attempts at triggering an update */
this.processName();
this.processName.valueHasMutated(); // fails because it's not a function

this.processNodeCount();
}
catch (e) {
console.log(e);
}
}
}


Binding

declare var processViewModel: Process.ProcessViewModel;

window.onload = () => {
processViewModel = new Process.ProcessViewModel();
processViewModel.setBindings();
ko.applyBindings(processViewModel);
}


I get the following error when calling this.processName.valueHasMutated() from this.refreshProcessDetails();

stack:"TypeError: this.processName.valueHasMutated is not a function\n at

Answer

valueHasMutated is a method that only normal observables have. Computed observables don't have this method. That's primarily because valueHasMutated doesn't do what you think it does. It doesn't trigger an update in the observable you call it on, instead it triggers an update downstream in subscribing observables, so it wouldn't help you here. In this case, if this worked, it would tell the HTML binding to update, but wouldn't change the value of processName, so the binding wouldn't do anything.

Generally there is no way to trigger an update to a computed observable, other than changing an observable it depends on. Instead, your computer observable should depend on another observable somewhere (preferably), or you should manually write to an observable yourself (if you have to). To do that:

  1. If you can, make the part of processViewModel.testService.flowModel.process.name which is changing (e.g. the process name, or the process itself if that's being changed, etc) observable, so that the computed will be automatically updated when it does.
  2. Alternatively, don't have a computed at all. Leave processName and processNodeCount as normal observables, and when you call refreshProcessDetails, write the correct values into them.

Computeds exist to automatically keep your data model in sync throughout. You have to either commit to that (use observables throughout) or do it yourself (manually update normal observables), you can't stay in the middle.

Note that if you do option 2 you lose a lot (all?) of the value of Knockout. The 'right' way to do Knockout is to keep large sections of your application (the fields here, and the fields in the models this depends on) observable, so that everything magically works. The larger the part of your code using Knockout, the better this works - shrinking it means more manual work that Knockout would rather be doing for you.