可观察数组中的可观察视图模型在 KnockoutJs 中不起作用

Observable viewmodels in observable array don't work in KnockoutJs

我有简单的 HTML 标记,其中包含我的视图模型列表和模板。另外,我在 <pre> 部分有我的视图模型:

<div class="container">
  <div class="row">
    <div class="col-md-6">
      <div class="col-md-12">
        <div class="panel panel-primary">
          <div class="panel-heading">
            Names
            <button type="button" class="btn btn-link" data-bind="click: addName">
              <span class="glyphicon glyphicon-plus"></span>
            </button>
          </div>
          <div class="panel-body">
            <ul class="list-group" id="namesList" data-bind="foreach: Names">
              <li class="list-group-item"><my-comp></my-comp></li>
            </ul>
          </div>
        </div>
      </div>
    </div>
    <div class="col-md-6">
      <div class="col-md-12">
        <pre data-bind="text: ko.toJSON(Names, null, 2)"></pre>
      </div>
    </div>
  </div>
</div>
<script type="text/html" id="fullNameTmpl">
  <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="textInput: firstName" /></p>
  <p>Last name: <input data-bind="textInput: lastName" /></p>

  <p>Full name: <input data-bind="textInput: fullName"></p>

  <button data-bind="click: capitalizeLastName">Go caps</button>
</script>
<script>
  function AppViewModel() {
    var self = this;
    self.firstName = ko.observable("Bert");
    self.lastName = ko.observable("Bertington");

    this.fullName = ko.pureComputed({
      read: function () {
        return self.firstName() + " " + self.lastName();
      },
      write: function (value) {
        var lastSpacePos = value.lastIndexOf(" ");
        if (lastSpacePos > 0) { 
          self.firstName(value.substring(0, lastSpacePos)); 
          self.lastName(value.substring(lastSpacePos + 1)); 
        }
      },
      owner: self
    });

    self.capitalizeLastName = function () {
      var currentVal = this.lastName();        
      this.lastName(currentVal.toUpperCase()); 
    };
  }
  ko.components.register('my-comp', {
    viewModel: AppViewModel,
    template: { element: "fullNameTmpl" }
  });

  function NamesViewModel() {
    var self = this;

    self.Names = ko.observableArray();

    self.addName = function () {
      var vm = ko.observable(new AppViewModel());
      self.Names.push(vm);
    }
  }

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

效果很好,我可以将一个项目添加到列表中,并且这些项目中的数据绑定有效。当我添加新的 AppViewModel 时,我可以在 <pre> 部分的 Names 数组中看到它。问题是,当我在嵌套视图模型中更改某些值时,比如输入新的姓氏等等,Names 数组中当前视图模型的 <pre> 部分没有任何变化。

如何制作一个真正可观察的可观察数组?添加这样的组件最有利可图的方法是什么?我已经完成了可观察的嵌套视图模型,但它似乎不起作用。

您的代码有几个问题。问题不一定出在可观察数组上——这是您使用组件的问题。列出了组件的用途 here。一些注意事项:

  1. 不需要制作可观察的数组元素observable。所以不需要做 var vm = ko.observable(new AppViewModel()); 只需使用 var vm = new AppViewModel(); 并推入你的数组。但是,如果您想使用一个组件,那么这也不正确!正在做的基本上是更新 AppViewModels 两次!你能明白为什么吗?您在上面的行中显式执行一次,然后组件隐式执行一次。每次创建组件时,它都会与 AppViewModel 相关联——这就是您注册组件时发生的情况。
  2. 组件与您相关联,您可以将其视为具有自己的属性、功能、计算等的迷你视图模型。因此,在您的情况下,您可能不需要使用组件 -> 复制并粘贴您的组件的内部结构<script></script> 标记到 <li><li> 中,它应该可以正常工作。

我不确定这是否是您正在寻找的答案,但这应该符合您的要求。如果愿意,您可以使用组件,但您必须进行更多更改和一点点不必要的复杂性。我冒昧地更改了您的变量名。我认为他们这样可能更合适。希望这对您有所帮助!

编辑

我已编辑代码片段以使用组件。这个特定实现的问题是组件现在通过名称 observableArray 与 AppViewModel 紧密耦合。这在您的情况下可能没问题,但通常这会导致不良行为,尤其是当您的视图模型开始变得越来越大和越来越复杂时。当然,您可以传入 firstNamelastName 的未包装值,以便更好地解耦,但是您会回到第一个方块,因为 <pre data-bind="text: ko.toJSON(names, null, 2)"></pre> 不会注册对名称 observableArray。

 ko.components.register('my-comp', {
   viewModel: function(params) {
     var self = this;
     self.firstName = params.data.firstName
     self.lastName = params.data.lastName
     self.fullName = ko.pureComputed({
       read: function() {
         return self.firstName() + " " + self.lastName();
       },
       write: function(value) {
         var lastSpacePos = value.lastIndexOf(" ");
         if (lastSpacePos > 0) {
           self.firstName(value.substring(0, lastSpacePos));
           self.lastName(value.substring(lastSpacePos + 1));
         }
       },
       owner: self
     });

     self.capitalizeLastName = function() {
       var str = self.lastName();
       self.lastName(str.toUpperCase());
     };
   },
   template: {
     element: "fullNameTmpl"
   }
 });

var Name = function(){
  this.firstName = ko.observable("Bert");
  this.lastName = ko.observable("McBertington");
}


 function AppViewModel() {
   var self = this;
   self.names = ko.observableArray();
   self.addName = function() {
     self.names.push(new Name());
   }
 }


 ko.applyBindings(new AppViewModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>

Names
<button type="button" class="btn btn-link" data-bind="click: addName">
  Add Dude
</button>

<ul class="list-group" id="namesList" data-bind="foreach: names">
  <my-comp params="data: $data"></my-comp>
  <!--<li class="list-group-item">
    <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="textInput: firstName" /></p>
    <p>Last name: <input data-bind="textInput: lastName" /></p>
    <p>Full name: <input data-bind="textInput: fullName"></p>
    <button data-bind="click: capitalizeLastName">Go caps</button>
  </li>-->
</ul>


<pre data-bind="text: ko.toJSON(names, null, 2)"></pre>

<script type="text/html" id="fullNameTmpl">
  <li>
    <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="textInput: firstName" /></p>
    <p>Last name: <input data-bind="textInput: lastName" /></p>
    <p>Full name: <input data-bind="textInput: fullName"></p>
    <button data-bind="click: capitalizeLastName">Go caps</button>
  </li>
</script>