I have a custom binding handler that I am binding to a complex object in my view model.
The binding handler works correctly and the update
function is called when any of the observable's properties update. However, the update
function is called for every updated property, leading to odd behaviour since I am relying on the entire object to be available and up to date.
I understand why this is happening, as each property is causing an update to be called, and I think I know how to prevent this - by using the deferred updates functionality of Knockout.
However, I am unable to find how to enable deferred updates just for the observable in my custom binding. I do not want to enable it application wide as I am writing the binding as a library function.
I have tried many different methods including:
init
function;valueAccessor
;valueAccessor
with a new observable with deferred
applied;All of which have not worked.
I have not found any other custom binding handler that comes remotely close to this sort of function and have been trying to piece it together from other functions.
My binding code itself is relatively simple, I am taking the bound object and simply splitting out the parameters and passing them to a Code Mirror instance.
ko.bindingHandlers.editor = {
init: function(element, valueAccessor, allBindingsAccessor) {
var observableValue = ko.utils.unwrap(valueAccessor());
initEditor(element, observableValue, allBindingsAccessor);
},
update: function(element, valueAccessor, allBindingsAccessor) {
var observableValue = ko.unwrap(valueAccessor());
createEditor(codeEditorDiv, observableValue);
resize();
updateEditor(element, observableValue, allBindingsAccessor);
}
};
And my HTML code is:
<div id="editor" data-bind="editor: EditorVM"></div>
I am using Dotnetify for the ViewModel so it is a reasonable complex C# class, but suffice it to say that the binding is working and updating, but I need it to only call 'update' once all properties have been updated.
It's unfortunate you haven't shown what initEditor
, createEditor
and updateEditor
do with the observableValue
, because that's probably where you should be extending your observables.
The init
and update
methods of a binding create computed dependencies, meaning that any observable that is unwrapped in the call stack starting from init
will cause the update
method to be called.
In an abstract example:
const someVM = ko.observable({
a: ko.observable(1),
b: ko.observable(2),
c: ko.observable(3)
});
// Some function that unwraps properties
const logABC = function(vm) {
console.log(
vm.a(),
vm.b(),
vm.c()
);
}
// Regular binding update:
ko.computed(function update() {
console.log(
"Regular binding update:",
)
logABC(someVM())
});
// Change VM
someVM(someVM());
// Change a, b, and c
someVM().a("A");
someVM().b("B");
someVM().c("C");
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
Note that update
is called:
There are several ways of solving the issue, of which the simplest is to create your own computed
inside the init
method of your binding and extend it to be deferred
.
const someVM = ko.observable({
a: ko.observable(1),
b: ko.observable(2),
c: ko.observable(3)
});
const getABC = function(vm) {
return [vm.a(), vm.b(), vm.c()].join(", ");
}
ko.bindingHandlers.renderABC = {
init: function(el, va) {
el.innerText += "Init.\n";
// This ensures any inner unwrapping gets deferred
var updateSub = ko.computed(function update() {
el.innerText += getABC(ko.unwrap(va())) + "\n";
}).extend({ deferred: true });
ko.utils.domNodeDisposal.addDisposeCallback(el, function() {
updateSub.dispose();
});
}
}
ko.applyBindings({ someVM: someVM });
// Change VM
someVM(someVM());
// Change a, b, and c
someVM().a("A");
someVM().b("B");
someVM().c("C");
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<pre data-bind="renderABC: someVM"></pre>