I'm working on an MVC Web application with ASP.NET and Knockout js (V3.5.1). I'm struggling with Knockout observable not updated when an input value is change. Both of them are dynamically created in Javascript. The initial value set to the observable is not reflected on the input when the observable is created. I'm puzzled why the observable not catching the input's change.
Please find my code below for further demonstration. I really appreciate if there is any help.
CustomViewModel.cs
public class CustomViewModel
{
public int Id { get; set; }
public int Prop { get; set; }
}
HTML with Razor
@model CustomViewModel
<div id="inputContainer"></div>
@section Scripts {
@Scripts.Render("~/bundles/jqueryval", "/Scripts/ViewModels/CustomFormViewModel.js")
<script>
var vm = new CustomFormViewModel(@HtmlHelperExtensions.HtmlConvertToJson(Html, Model));
vm.createInput = function (data) {
return `@Html.Editor("Prop", new { @htmlAttributes = new { @id = "${data.Id}Prop", @data_bind = "value: ${data.Id}Prop" } })`;
};
ko.applyBindings(vm);
</script>
}
/Scripts/ViewModels/CustomFormViewModel.js code
function CustomFormViewModel(self) {
var self = this;
var data = loadData();
self[`${data.Id}Prop`] = ko.observable(data.Prop);
var newInput = self.createInput(data);
$("#inputContainer").append(newInput);
};
I will only focus on the issue at hand- which is the input.
In general, since you are using knockoutJS you should refrain from using jQuery to append elements to the dom, instead you should change your View, so that it reflects what you are trying to achieve.
So you should have something like so:
<div data-bind="if: showThisInput">
<input data-bind="value: myBindedObservable"
</div>
This way you control if the input should be rendered/shown, instead of appending it later on via jQuery.
When you call applyBindings you only bound to any html already existing on the view. Because you are injecting the input AFTER you have already bound the vm to the view, the engine does not know of the element since its not controlled by knockout, but rather added by you manually.
One way to solve this would be to actually call the razor inside the normally rendered view, so that when the applyBindings is executed, the input element exists on the dom
@Html.Editor("Prop", new { @htmlAttributes = new { @id = "${data.Id}Prop", @data_bind = "value: ${data.Id}Prop" } })
The only other option would be to clearBindings on the element and-rebind, which is also a bad practice for this use-case.