【问题标题】:Accessing a component viewmodel from its containing viewmodel从其包含的视图模型访问组件视图模型
【发布时间】:2016-06-25 12:49:33
【问题描述】:

我正在使用 knockout.js 组件和 require.js。 到目前为止,这运行良好,但我正在努力解决以下问题。

假设我在一个非常简单的 html 页面中有一个组件实例:

<div id="exams">
    <databound-exam-control></databound-exam-control>
</div>

来自包含的视图模型:

require(['knockout', 'viewModel', 'domReady!'], function (ko, viewModel) {
    ko.components.register('databound-exam-control', {
        viewModel: { require: 'databound-exam-control-viewmodel' },
        template: { require: 'text!databound-exam-control-view.html' }
    });

    ko.applyBindings(new viewModel());
});

我想获取子视图模型内容,以便稍后在单击按钮时保存页面的所有数据。

现在我只是尝试在 pre 标记中显示父/子视图模型:

<div>
    <pre data-bind="text: childViewModel()"></pre>
</div>

借助包含的视图模型:

function childViewModel() {
        var model = ko.dataFor($('databound-exam-control').get(0).firstChild);
        return ko.toJSON(model, null, 2);
};

在调用 ko.dataFor 时出现以下错误,可能是因为页面没有完全呈现:

除非您指定“写入”选项,否则无法将值写入 ko.computed。如果您想读取当前值,请不要传递任何参数。

是这样吗?还是我完全错过了这里的重点?

任何帮助表示赞赏。

【问题讨论】:

  • 我会说最好设置您的主视图模型以在您创建它们时跟踪这些子模型。作为一个人为的示例,您可以查看:jsfiddle.net/timh06/91n3m25t 也可以查看 ko.contextFor,而不是 ko.dataFor,它应该可以访问元素的绑定上下文。

标签: javascript knockout.js requirejs web-component


【解决方案1】:

父视图模型和子组件之间更简单的通信方式是使用参数。

  • 在父视图模型中创建一个新的可观察属性,如childViewModel = ko.observable()
  • 将其作为参数传递给子组件&lt;databound-exam-control params= "{modelForParent: childViewModel}"&gt; 请注意,子视图模型中的参数将称为modelForParent,而在父视图模型中将称为childViewModel
  • 在组件的视图模型构造函数中,在您的databound-exam-control-viewmodel.js 脚​​本中,您会收到参数作为构造函数的唯一参数。因此,如果您的构造函数如下所示: function SomeComponentViewModel(params) 您可以以params.modelForParent 访问参数
  • 使用该参数将您需要的任何信息从子组件传递给父组件,例如:params.modelForParent(createdChildViewModel)

现在,父组件可以使用 childViewModel observable 访问子视图模型。

使用 observables 只是一种可能性。你可以通过其他方式来做到这一点。例如,将回调作为参数传递,并在 viewmodel 构造函数中执行该回调以将您想要的任何内容返回给父级。我有时会使用这种模式:

  • 在父级中创建一个注册API回调,像这样:registerApi = function(api) { config.childApi = api }
  • 将此作为参数传递给子组件
  • 在子视图模型构造函数中调用传递子 api 的回调

这样,我可以像这样访问子组件暴露的api:config.childApi.aMethosExposedByTheChild()

重要提示:您必须考虑到,由于子组件是异步加载的,因此子组件公开的信息不会立即可用。

除非您需要立即使用它,否则这不是问题。例如,在您的情况下,看起来您会在加载组件并且用户与之交互后从子视图模型中获取信息,所以这不是问题。

如果您需要尽快访问它,您可以使用轮询 --- 或更好,将延迟(示例实现:$.Deferred)暴露给孩子,以便它可以解决它以让父查看模型知道它已经可用。当子视图模型依赖于通过 AJAX 调用加载外部资源(例如加载下拉列表或服务器上存在的一些其他信息)时,也会发生这种情况。

另一个我不喜欢的选项是父视图模型包含整个子视图模型并将其作为参数传递,因此父视图模型可以完全控制子视图模型。显然这种方案不允许组件负责自己的viewmodel,所以父viewmodel和子组件之间存在紧密耦合,这是不可取的。

【讨论】:

  • 感谢您的详细回答,我会尝试您的所有建议并回复您。我也在尝试使用knockout-postbox
【解决方案2】:

我遇到了一个问题,我的父组件需要访问子视图模型以进行排序。我的父布局代码看起来像这样

<div class="parent-component">
    <!-- ... -->

    <div data-bind="foreach: { data: itemsSorted() as: 'item' }">
        <child-component params="item: item"></child-component>
    </div>
</div>

问题是itemsSorted() 依赖于子组件视图模型中的可观察对象,例如

var ParentViewModel = function(items) {
    var self = this;

    self.items = ko.observable(items);

    self.itemsSorted = ko.pureComputed(function() {
        return self.items().sort(function(a, b) {
            // ERROR here: The timestamp() observable isn't present yet,
            // since ChildViewModel hasn't created the observable
            return a.timestamp() - b.timestamp();
        });
    });
};

var ChildViewModel = function(item) {
    self = this;

    self.timestamp = ko.observable(item.timestamp);
};

因为itemsSorted() 在布局完成之前不包含视图模型,所以它无法使用子可观察对象。


解决方案

我的解决方案是在父级中创建子级的视图模型,然后通过参数传递:

var ParentViewModel = function(items) {
    var self = this;

    self.items = ko.observableArray(items.map(function(item) {
        // Initialize view model in parent to allow for sorting
        return new ChildViewModel(item);
    }));

    self.itemsSorted = ko.pureComputed(function() {
        return self.items().sort(function(a, b) {
            // This works, because ChildViewModel's observables are already instantiated
            return a.timestamp() - b.timestamp();
        });
    });
};

var ChildViewModel = function(item) {
    self = this;

    self.timestamp = ko.observable(item.timestamp);
};

接下来,在 HTML 中,将视图模型传递给组件

<div class="parent-component">
    <!-- ... -->

    <div data-bind="foreach: { data: itemsSorted(), as: 'viewModel' }">
        <child-component params="viewModel: viewModel"></child-component>
    </div>
</div>

最后,在组件注册中,修改child-component的视图模型,使用父级的实例化版本:

ko.components.register('child-component', {
    template: /* ... */,
    viewModel: function(params) {
        // viewModel is already created by parent, pass into component
        return params.viewModel;
    },
});

现在ParentViewModel 可以创建和使用ChildViewModel 的属性进行排序,而无需等待初始布局。 ChildViewModel 中的代码不知道差异,不需要更改。这确实将子组件耦合到父组件,这不是对组件的良好封装,但它允许父组件立即布局和使用子可观察对象。

【讨论】:

    【解决方案3】:

    这是我从更高级别控制我的视图模型组件实例的方式。

    //ViewModel for Component...
    function ComponentViewModel(params) {
      const self = this;
      self.Message = ko.observable(params.Message);
    };
    
    //Register Component...
    ko.components.register("my-component", {
        template: { element: "my-component-template" },
        viewModel: function (params) {
            return params.ViewModel; //Return the instance created on root (or wherever you instace your component)...
        }
    }); 
    
    //Principal ViewModel...
    function RootViewModel() {
      const self = this;
      self.ComponentViewModel = ko.observable(new ComponentViewModel({
        Message: "Hello from component!" //Params for component go here...
      }));
    };
    
    ko.applyBindings(new RootViewModel()  );
    <script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
    
    <my-component params="ViewModel: ComponentViewModel"> </my-component>
    
    <template id="my-component-template">
      <p data-bind="text: Message"> </p>
    </template>

    【讨论】:

      猜你喜欢
      • 2013-09-10
      • 1970-01-01
      • 2014-08-15
      • 1970-01-01
      • 2018-06-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多