Multiple Knockout viewmodels with Asp.Net Core ViewComponents

I have simple FullName viewcomponent with knockout viewmodel inside:

p>First name: <strong data-bind="text: firstName"></strong></p>
<p>Last name: <strong data-bind="text: lastName"></strong></p>

<p>First name: <input data-bind="value: firstName" /></p>
<p>Last name: <input data-bind="value: lastName" /></p>

<p>Full name: <strong data-bind="text: fullName"></strong></p>

<script>
function AppViewModel() {
    this.firstName = ko.observable("");
    this.lastName = ko.observable("");

    this.fullName = ko.computed(function() {
        return this.firstName() + " " + this.lastName();    
    }, this);
}

ko.applyBindings(new AppViewModel());
</script>

When I invoke this component, like @(await Component.InvokeAsync("FullName")) , it works great.

However, when I'm trying to invoke this component multiple times:

@(await Component.InvokeAsync("FullName"))
@(await Component.InvokeAsync("FullName"))
@(await Component.InvokeAsync("FullName"))
//etc...

I have an error, that Knockout can't apply multiple same bindings.

So, how can I include multiple ViewComponents with Knockout viewmodels into one page?


In your code, you are calling applyBindings once per component you add, and Knockout wont let you bind something more than once unless you first remove the bindings(as per Matt.kaaj's comment).

That being said, I think you should look at scoping your bindings if you want to reuse your viewmodel in multiple places.

Full disclosure - I do not know much about asp.net and it's ViewComponents , so apologies if the syntax is incorrect, but it seems like they support passing in parameters. It looks like you could fix this by changing your component definition to something like:

<div id=${idThatIPassIn}>
<p>First name: <strong data-bind="text: firstName"></strong></p>
<p>Last name: <strong data-bind="text: lastName"></strong></p>

<p>First name: <input data-bind="value: firstName" /></p>
<p>Last name: <input data-bind="value: lastName" /></p>

<p>Full name: <strong data-bind="text: fullName"></strong></p>
</div>
<script>
function AppViewModel() {
    this.firstName = ko.observable("");
    this.lastName = ko.observable("");

    this.fullName = ko.computed(function() {
        return this.firstName() + " " + this.lastName();    
    }, this);
}

ko.applyBindings(new AppViewModel(), document.getElementById('${idThatIPassIn}'));
</script>

And then initialize with:

@(await Component.InvokeAsync("FullName"), new { idThatIPassIn = "name-1" })
@(await Component.InvokeAsync("FullName"), new { idThatIPassIn = "name-2" })
@(await Component.InvokeAsync("FullName"), new { idThatIPassIn = "name-3" })

This way, each time you tell knockout to apply bindings, the binding is contextualized to a single div that wraps your <p> elements, so you wont end up trying to re-bind something that's already been bound.

Alternatively, instead of passing in an ID, you could assign a random arbitrary ID and bind to that instead. In either case, scope your applyBindings call to an element.

Edit - You mentioned not wanting to bind by IDs. Instead try using a class, and jQuery's .each method. Something like:

<div class="name-block">
<p>First name: <strong data-bind="text: firstName"></strong></p>
<p>Last name: <strong data-bind="text: lastName"></strong></p>

<p>First name: <input data-bind="value: firstName" /></p>
<p>Last name: <input data-bind="value: lastName" /></p>

<p>Full name: <strong data-bind="text: fullName"></strong></p>
</div>

initialized as you currently have it:

@(await Component.InvokeAsync("FullName"))
@(await Component.InvokeAsync("FullName"))
@(await Component.InvokeAsync("FullName"))

Then in another separate .js include, add:

<script>
//I'm assuming that jQuery is available
$(document).ready(function(){

  function AppViewModel() {
      this.firstName = ko.observable("");
      this.lastName = ko.observable("");

      this.fullName = ko.computed(function() {
          return this.firstName() + " " + this.lastName();    
      }, this);
  }


  $(".name-block").each(function(index, obj){
      ko.applyBindings(new AppViewModel(), obj);
  });
});
</script>

This way, your viewmodel is only defined once, and your bindings are applied for each individual div that has the class "name-block".

I also wrapped the entire process in jQuery's document ready function, so you can safely assume that the DOM is ready to manipulate. Just make sure your script is included in your page as well, as now it's separated from the view component.

链接地址: http://www.djcxy.com/p/83520.html

上一篇: 检查输入是否为空,如果不是,则将类添加到父div

下一篇: 带有Asp.Net核心ViewComponents的多个Knockout视图模型