从包含其中的viewmodel访问组件视图模型

时间:2016-03-10 15:21:18

标签: javascript knockout.js requirejs web-component

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

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

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

从包含viewmodel:

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>

在包含viewmodel的帮助下:

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

在调用 ko.dataFor 期间出现以下错误,可能是因为页面未完全渲染:

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

是吗?或者我完全错过了这一点?

任何帮助表示感谢。

2 个答案:

答案 0 :(得分:5)

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

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

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

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

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

通过这种方式,我可以访问子组件公开的api,如下所示:config.childApi.aMethosExposedByTheChild()

重要提示:您必须考虑到,当子组件异步加载时,子项公开的信息不会立即可用。

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

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

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

答案 1 :(得分:0)

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

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

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

问题是 itemsSorted() 依赖于子组件视图模型中的 observables,例如

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() 在布局完成之前不包含视图模型,所以它无法使用子 observable。


解决方案

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

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 中的代码不知道差异,不需要更改。这确实将子组件耦合到父组件,这不是组件的良好封装,但它允许父组件立即布局和使用子 observable。