3

My viewModel consist of observable array with observable elements.

// viewmodel
var viewModel = function () {
    this.o = ko.observableArray();
    for (var i = 0; i < 3; i++)
    this.o.push(ko.observable(0));
};

I need to change the values of these elements. And for this purpose I create the component. Simple example of it is below:

//custom element <component>
ko.components.register("component", {
    viewModel: function (params) {
        var self = this;
        this.value = params.value;
        console.log("init component");
        this.i = 1;
        this.change = function () {
            self.value(self.i++);
            console.log("change to " + self.value());
        }
    },
    template: "<span data-bind='text: value'></span>  <button data-bind='click:change'>Change</button>"
});

This component can change value of observable element which come in params.value.

My view is very simple:

<!--ko foreach:o-->
       <component params="value: $rawData"></component>
<!--/ko-->

Full example: http://jsfiddle.net/tselofan/xg16u5cg/7/ Problem is when value of observable element in observable array is changed, component is rendered again, because it is located inside foreach binding. You can see this in logs. What the best practice can I use in this situation? Thank you

Andrea Casaccia
  • 4,802
  • 4
  • 29
  • 54
Tselofan
  • 146
  • 7

2 Answers2

1

The component is being recreated each time the number changes because the context of the component is the number.

http://jsfiddle.net/Crimson/xg16u5cg/8/

<!-- ko foreach: o -->
    <component params="value: $data.myNumber"></component>
<!-- /ko -->

//Test how components work in foreach binding
//custom element <component>
ko.components.register("component", {
    viewModel: function (params) {
        var self = this;
        this.value = params.value;
        console.log("init component");
        this.i = 1;
        this.change = function () {
            self.value(self.i++);
            console.log("change to " + self.value());
        }
    },
    template: "<span data-bind='text: value'></span>  <button data-bind='click:change'>Change</button>"
});


// viewmodel
var viewModel = function () {
    this.o = ko.observableArray();
    for (var i = 0; i < 3; i++)
        this.o.push({myNumber: ko.observable(0)});
};

ko.applyBindings(new viewModel());
CrimsonChris
  • 4,651
  • 2
  • 19
  • 30
  • As an alternative, you can still pass in the simple value, but create a new component viewmodel value as an observable within the init. http://jsfiddle.net/xg16u5cg/9/ – Roy J Jul 16 '15 at 14:14
  • Any solution that avoids directly changing the context of the component will work. – CrimsonChris Jul 16 '15 at 14:19
  • Thanks for reply! CrimsonChris, your solution force me to change domain model. I would like to avoid it. @Roy J, changing observable inside component doesn't force changing observable element in array. See http://jsfiddle.net/tselofan/xg16u5cg/10/ . My code works good for observable elements outside of foreach binding despite the changing of context. See http://jsfiddle.net/tselofan/xg16u5cg/12/ . That is because foreach binding aggregates the template binding, which rerenders content when the context is changed. May be I can stop rerender content of foreach template? Thanks. – Tselofan Jul 16 '15 at 15:45
  • @Tselofan Possibly Knockout-Repeat (https://github.com/mbest/knockout-repeat) would work for you. – Roy J Jul 16 '15 at 16:14
  • You could just remove the state from the component. Then you won't have to care if Knockout remakes it. http://jsfiddle.net/Crimson/xg16u5cg/16/ – CrimsonChris Jul 16 '15 at 16:31
  • CrimsonChris in your last solution component still continue to be recreated. See logs. @Roy J, your solution is answer on my question.It works as I expected. See http://jsfiddle.net/tselofan/a54jnt7d/1/ Please answer so I can rate you. Thank you all! – Tselofan Jul 17 '15 at 06:59
1

Knockout-Repeat (https://github.com/mbest/knockout-repeat) is an iterative binding that does not create a new binding context, and has a foreach mode, so it should work as you expect with your component.

Roy J
  • 42,522
  • 10
  • 78
  • 102
  • Be carefull with this binging. 1. It provides pseudo observable $item context variable, which is not ko.subscribable. 2. It uses strange algorithm of regenerating markup when you remove element from observable array: as I understand it, first it removes first DOM node (regardless of index of deleted element), and then it updates ko context of the remaining DOM nodes. For some reasons (perhaps because of pseudo subscribable context), my component cannot process this algorithm. Thanks for your answer. – Tselofan Jul 27 '15 at 13:57