Search code examples
knockout.jsknockout-3.0

How to pass an item from knockout foreach to partial view as data-bind?


I have a table with the list myListB() of objects, which are displayed by knockout foreach making as many rows as myListB().Count.

<!--ko with: myProperty-->
    <table class="table">
                    <thead>
                        <tr>
                            <th>Column 1</th>
                            <th>Column 2</th>
                        </tr>
                    </thead>
                    <tbody>
                        <!-- ko foreach: myListB()-->
                        <tr>
                            <td data-bind="text: Property1"></td>
                            <td data-bind="text: Property2"></td>
                            @await Html.PartialAsync("~/Views/MyView.cshtml", this)
                        </tr>
                        <!-- /ko -->
                    </tbody>
                </table>
  <!-- /ko -->

Data Model:

    class viewmodel{
    public myProperty:Foo;
    
    }
    class Foo{
public myListB: KnockoutObservableArray<Model1> = ko.observableArray([])
public myPropertyA:string;
    }
 class Model1{
    public Property1:any;
    public Property2:any;
    }

Is there a way to pass an item from foreach to partial view MyView.cshtml (it would be an object of type Model1), so I could use it for data-bind in partial view and access there value of Property1 of each item of array myListB()? As it's written in code,  myProperty is passed by ko with, so in partial view I can access either whole myListB() or myPropertyA. Wrapping partial view with ko with: $data doesn't help, I still can access only myPropertyA and the whole myListB(). I can't get rid of foreach and partial view here, it should remain as it is now, the question is about the possibility of data binding an array item from foreach to partial view

MyView.cshtml:

<div class="modal"
      role="dialog">
    <div class="modal-dialog"
         role="document">
        <div class="modal-content ">
            <div class="modal-header">
                <h4 class="modal-title">MyTitle</h4>
            </div>
            <!--ko if: $data!=null -->
            <div class="modal-body ">
                <span data-bind="text: $data.Property1"></span>
            </div>
            <!-- /ko -->
        </div>
    </div>
</div>

Solution

  • The problem with what you are trying to do is that knockoutjs runs in the client browser where as @await Html.PartialAsync runs on the server. By the time knockoutjs is processing the contents of myListB, the server has already processed and rendered the partial view as html as is part of the table row.

    so what I think you are seeing on the client side is something like the following in the browser.

        function viewModel() {
          var self = this;
          self.myProperty = ko.observable(new Foo());
    
        }
    
        function Foo() {
          var self = this;
          self.myListB = ko.observableArray([new Model1(), new Model1()]);
          self.myPropertyA = '';
        }
    
        function Model1() {
          var self = this;
          self.Property1 = "test 1";
          self.Property2 = "test 2";
        }
    
        var vm = new viewModel();
    
        ko.applyBindings(vm);
    <script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
    <!--ko with: myProperty-->
    <table class="table">
      <thead>
        <tr>
          <th>Column 1</th>
          <th>Column 2</th>
          
        </tr>
      </thead>
      <tbody>
        <!-- ko foreach: myListB-->
        <tr>
          <td data-bind="text: Property1"></td>
          <td data-bind="text: Property2"></td>
    
            <div class="modal" role="dialog">
              <div class="modal-dialog" role="document">
                <div class="modal-content ">
                  <div class="modal-header">
                    <h4 class="modal-title">MyTitle</h4>
                  </div>
                  <!--ko if: $data!=null -->
                  <div class="modal-body ">
                    <span data-bind="text: $data.Property1"></span>
                  </div>
                  <!-- /ko -->
                </div>
              </div>
            </div>
    
        </tr>
        <!-- /ko -->
      </tbody>
    </table>
    <!-- /ko -->
    
    <pre data-bind="text: ko.toJSON($root)" />

    Part of the problem that I can see is that partial view is added into the middle of the table and the browser is struggling to render the table correctly.

    if you add a <td></td> tag around the partial view then the table will render correctly.

        function viewModel() {
          var self = this;
          self.myProperty = ko.observable(new Foo());
    
        }
    
        function Foo() {
          var self = this;
          self.myListB = ko.observableArray([new Model1(), new Model1()]);
          self.myPropertyA = '';
        }
    
        function Model1() {
          var self = this;
          self.Property1 = "test 1";
          self.Property2 = "test 2";
        }
    
        var vm = new viewModel();
    
        ko.applyBindings(vm);
    <script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
    <!--ko with: myProperty-->
    <table class="table">
      <thead>
        <tr>
          <th>Column 1</th>
          <th>Column 2</th>
          <th>Column 3</th>
          
        </tr>
      </thead>
      <tbody>
        <!-- ko foreach: myListB-->
        <tr>
          <td data-bind="text: Property1"></td>
          <td data-bind="text: Property2"></td>
          <td>
            <div class="modal" role="dialog">
              <div class="modal-dialog" role="document">
                <div class="modal-content ">
                  <div class="modal-header">
                    <h4 class="modal-title">MyTitle</h4>
                  </div>
                  <!--ko if: $data!=null -->
                  <div class="modal-body ">
                    <span data-bind="text: $data.Property1"></span>
                  </div>
                  <!-- /ko -->
                </div>
              </div>
            </div>
          </td>
        </tr>
        <!-- /ko -->
      </tbody>
    </table>
    <!-- /ko -->
    
    <pre data-bind="text: ko.toJSON($root)" />