I am having trouble getting a Knockout component to work - i can't seem to get it to bind properly to the members of an array on my ViewModel during a foreach
accessed using $index
.
In this Fiddle you will see what I mean.
There are two little view Models:
function OtherThingViewModel(thingString){
this.thingString = ko.observable(thingString);
}
function ThingViewModel(thingNumber, thing){
this.thingNumber = ko.observable(thingNumber);
}
Instances are created in the main viewModel:
function ViewModel(){
var self = this;
this.things = [
new ThingViewModel(1),
new ThingViewModel(2),
new ThingViewModel(3)
];
this.otherThings = [
new OtherThingViewModel("a Thing"),
new OtherThingViewModel("another Thing"),
new OtherThingViewModel("some Thing")
];
this.specialThing = ko.unwrap(this.things)[0];
this.specialOtherThing = ko.unwrap(this.otherThings)[0];
};
Then I have a component:
ko.components.register('combinedthing-component', {
template:
'<div>'
+ ' <h3 data-bind="text: \'Thing \' + thing.thingNumber()"></h3>'
+ ' <p>'
+ ' <label>thingNumber: <input data-bind="value: thing.thingNumber" /></label>'
+ ' <span data-bind="text: thing.thingNumber" />'
+ ' </p>'
+ ' <p>'
+ ' <label>thingString: <input data-bind="value: otherThing.thingString" /></label>'
+ ' <span data-bind="text: otherThing.thingString" />'
+ ' </p>'
+ ' <p data-bind="text: JSON.stringify(ko.unwrap(otherThing))"></p>'
+ '</div>'
});
for displaying data from the two view models.
In the HTML I can successfully use the the component and using a foreach
I can combine the two objects:
<h1>1 component</h1>
<combinedthing-component params="thing: specialThing, otherThing: specialOtherThing"></combinedthing-component>
<h1>Foreach</h1>
<!-- ko foreach: things -->
<div>
<h3 data-bind="text: 'Thing ' + thingNumber()"></h3>
<p>
<label>thingNumber <Input data-bind="value: thingNumber" /></label>
<span data-bind="text: thingNumber" />
</p>
<p>
<label>thingString: <input data-bind="value: $root.otherThings[$index()].thingString" /></label>
<span data-bind="text: $root.otherThings[$index()].thingString" />
</p>
</div>
<!-- /ko -->
but if I try and combine the two - looping thorugh things
with the foreach
and then accessing the otherThings
Array using $index and binding these to the component:
<h1>Many Components</h1>
<!-- ko foreach: things -->
<combinedthing-component params="thing: $data, otherThing: $root.otherThings[$index()]"></combinedthing-component>
<!-- /ko -->
Then while I get an object in otherThing
(as proved by the ko.toJSON binding) its properties are not binding to the input
and span
.
What gives?
The problem is due to how params are passed into a component when using the "web component" syntax. The objects passed through
params=""
get transformed into dependent observables (computeds). Behind the scenes,$root.otherThings[$index()]
essentially becomes a computed observable with this implementationfunction () { return $root.otherThings[$index()]; }
.The simplest way is to get what you want is to add a
ko.utils.unwrapObservable
when referencingotherThing
. This will ensure that you're always working with the actualotherThing
instead of an observable wrapping it.The ideal place to do this unwrapping would be in view model part of the component registration.
JSFiddle