【问题标题】:Observable viewmodels in observable array don't work in KnockoutJs可观察数组中的可观察视图模型在 KnockoutJs 中不起作用
【发布时间】:2017-02-19 03:25:13
【问题描述】:

我有一个简单的 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 时,我可以在&lt;pre&gt; 部分的Names 数组中看到它。问题是,当我在嵌套视图模型中更改某些值时,例如键入新的姓氏,无论如何,Names 数组中当前视图模型的 &lt;pre&gt; 部分没有变化。

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

【问题讨论】:

  • 我认为这里的问题是淘汰 observableArray 将数组本身视为可观察的,通知推送和删除等事件,但不会通知数组中某个项目的单个属性发生变化。我很确定您将自己实现(文档中有一个“关键点”非常简短地提到了这一点 - knockoutjs.com/documentation/observableArrays.html)
  • @NickDeFazio 但是在addName 方法中,我添加了ko.observable,这还不够吗?

标签: knockout.js viewmodel observable


【解决方案1】:

您的代码存在一些问题。问题不一定与可观察数组有关 - 这是您使用组件的问题。组件的用途列在here。需要注意的几点:

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

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

编辑

我已经编辑了代码 sn-p 以使用组件。这个特定实现的问题在于该组件现在通过名称 observableArray 与AppViewModel 紧密耦合。在您的情况下这可能没问题,但这通常会导致不良行为,尤其是当您的视图模型开始变得更大和更复杂时。当然,您可以传入 firstName 和 lastName 的展开值以便更好地解耦,但随后您将回到第一方,因为 &lt;pre data-bind="text: ko.toJSON(names, null, 2)"&gt;&lt;/pre&gt; 不会注册对名称 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>

【讨论】:

  • 感谢您的回答!有用!但是,在第一个注释中,您说:每次创建组件时,它都会与 AppViewModel 相关联 - 这就是您注册组件时发生的情况。,我怎样才能正确注册然后调用我的组件?就我而言,我确实需要使用组件,因为我需要在任何地方使用这个名称编辑器。据我了解,组件有问题,您能否提供一个示例,就我而言,我该如何使用它们?
  • 我不明白为什么我们需要Name viewmodel,它的目的是什么?
猜你喜欢
  • 2012-04-20
  • 2017-11-25
  • 2015-11-30
  • 2012-06-13
  • 2015-09-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-06-13
相关资源
最近更新 更多