Reputation: 408
Is there a way to access a child view model from any of its parents? My use case is i have some custom knockout components. I have a base definition for each component in a separate file. I would like to be able to customize them in different ways depending on which part of the site its used in. Is this possible? There is lots of documentation on how to access a parent view model from a child but not the reverse.
So ideallly I want to be able to do things like:
parentViewModel.someChildModel.someProperty(someValue);
Upvotes: 3
Views: 2520
Reputation: 33399
The way I've found to do this is to have the parent VM construct the child VM, then inject the child VM into the child component as a parameter, by defining the child as:
ko.components.register("child", {
viewModel: {
createViewModel: function(params, componentInfo) {
return params.viewModel;
}
},
template: {
element: 'child-template'
}
});
Instead of defining a specific constructor or instance, the child component uses a createViewModel
function, which uses the "viewModel" component param as the viewmodel. (For safety, there could be checking here that a viewmodel is provided and that it is the expected class)
Then the parent VM can simply construct child VMs and, as a result, has full access to them and their properties. (e.g. self.child = new ChildVM()
)
And finally the template has to actually provide the ViewModel to the child component:
<child params="viewModel: child"></child>
It involves a bit of boilerplate, but it's the best way I've found to communicate from parent to child.
Full example:
function ParentVM() {
var self = this;
self.message = ko.observable();
var callback = function (message) {
self.message(message);
}
self.children = ["foo", "bar"].map(function (text) {
return new ChildVM(text, callback);
});
}
function ChildVM(text, callback) {
var self = this;
self.text = text;
self.onClick = function () {
callback(text + " clicked");
}
}
vm = {};
ko.components.register("parent", {
viewModel: ParentVM,
template: {
element: 'parent-template'
}
});
ko.components.register("child", {
viewModel: {
createViewModel: function(params, componentInfo) {
return params.viewModel;
}
},
template: {
element: 'child-template'
}
});
ko.applyBindings(vm);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<parent></parent>
<template id='parent-template'>
<!-- ko foreach: children -->
<child params="viewModel: $data"></child>
<br />
<!-- /ko -->
<span data-bind="text: message"></span>
</template>
<template id='child-template'>
<span data-bind="
text: text,
click: onClick
"></span>
</template>
Upvotes: 4