How to subscribe to a collection of items in KO

86 views
Skip to first unread message

Ciaran Bruen

unread,
Apr 20, 2013, 12:42:05 PM4/20/13
to knock...@googlegroups.com
Hi - I have an MVC project with a one to many collection where I use KO for the client model. Let's say my model is Category - Product where a Category can have 1 to many products. Up until now our code has only had 1 Product per Category so we've only ever had to reference Products[0], but now we are adding multiple products. I have a property that is subscribed to and when it changes I call a function that retrieves other data from a remote service. This has worked fine for a single product but now I'm not sure how to subscribe to every property in the Products collection and reference the correct one in KO.

Here's my existing code: The last step in $(document).ready I subscribe to the property and call a function passing in the new value:

    window.batchViewModel.currentCategory.Products()[0].UniCodeID.subscribe(function (newValue) {
        getProductUniCode
(newValue);
   
});

   
function getProductUniCode(uniCodeId) {

       
// If new value is empty don't call service just return
       
if (!uniCodeId) {
            window
.batchViewModel.currentCategory.Products()[0].ProductUniCode("");
           
return false;
       
}

       
var result = dataFromRemoteService(uniCodeId);
        updateProductCategoryCode
(result);
   
}
   
   
function updateProductUniCode(newUniCodeFromService) {

       
// Other code here
       
       
// Set related value in KO based on data from remote service.
        window
.batchViewModel.currentCategory.Products()[0].ProductUniCode(newUniCodeFromService);

       
return true;
   
}


I've tried this but productIndex is always 1 more than the number of products no matter how many products in the collection and no matter which one is being updated:

    var productCount = window.batchViewModel.currentCategory.Products().length;
   
for (var i = 0; i < productCount; i++) {
        window
.batchViewModel.currentCategory.Products()[i].UniCodeID.subscribe(function (newValue) {
            getProductUniCode
(newValue, i);
       
});
   
}
   
   
function getProductUniCode(uniCodeId, productIndex) {

       
// If new value is empty don't call service just return
       
if (!uniCodeId) {
            window
.batchViewModel.currentCategory.Products()[productIndex].ProductUniCode("");
           
return false;
       
}

       
var result = dataFromRemoteService(uniCodeId);
        updateProductCategoryCode
(result, productIndex);
   
}
   
   
function updateProductUniCode(newUniCodeFromService, productIndex) {

       
// Other code here
       
       
// Set related value in KO based on data from remote service.
        window
.batchViewModel.currentCategory.Products()[productIndex].ProductUniCode(newUniCodeFromService);

       
return true;
   
}


Anyone got any ideas?   

TIA.

Denys Khanzhiyev

unread,
Apr 21, 2013, 5:55:06 AM4/21/13
to knockoutjs
1. You have made classical mistake with index variable in closure. Your FOR loop should look like this:
for (var i = 0; i < productCount; i++) {

        window
.batchViewModel.currentCategory.Products()[i].UniCodeID.subscribe((function (j){
          return function (newValue) {
            getProductUniCode(newValue, j);
        
};})(i));
}

2. Do you receive data from remote service synchronously? Usually next line does not look like in your code.
var result = dataFromRemoteService(uniCodeId);

3. How do you construct your product viewmodels? Why do you subscribe and update its properties in some outer context. This functionality should reside in product viewmodel.


2013/4/20 Ciaran Bruen <cbr...@yahoo.com>

--
You received this message because you are subscribed to the Google Groups "KnockoutJS" group.
To unsubscribe from this group and stop receiving emails from it, send an email to knockoutjs+...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
 
 

Ciaran Bruen

unread,
Apr 21, 2013, 9:19:01 AM4/21/13
to knock...@googlegroups.com
Hi Denis thanks for your reply...it wouldn't be the first mistake I've made and I'm sure it won't be the last :) I used your code sample in 1 and initial results look good it seems to work correctly so thanks.

2. You're right I receive the data asyncronously. For brevity I included that line of code in my post but the update of the ProductUniCode takes place in the "dataFromRemoteService" function.

3. I use ko.mapping to create my viewmodel called from $(document).ready:

    function initBatchBinding() {
       
var mapping = {
           
'Products': {
                create
: function (options) {
                   
return new MYAPP.model.Product(options.data);
               
}
           
}
       
};
       
var boundData = {
            currentBatch
: ko.mapping.fromJS(window.defaultBatch, mapping)
       
};
       
return boundData;
   
}


I subscribe to that property outside the viewmodel as that's how I got it working at the time. I was and still am pretty new to knockout. This code was written several months ago and I'm getting back to it now and trying to update it and also do it the correct way. I added the code below to my Product model but again I don't know how to get the index of the individual product whose property is being updated. I think this is the correct place to do the subscription if I can figure out how to get the index:

    this.UniCodeID.subscribe(function(newUniCodeID) {
       
if (!newUniCodeId) {
           
this.ProductUniCode("");
       
} else {
           
var idx = this.$index;    // this.$index doesn't work - how do I get index here?
           
var result = dataFromRemoteService(uniCodeId);
            updateProductUniCode
(result, productIndex);
       
}
   
}, this);


Thanks for the help.

Ciaran

Ciaran Bruen

unread,
Apr 21, 2013, 12:34:44 PM4/21/13
to knock...@googlegroups.com
oops in that line of code, should idx instead of productIndex:
    updateProductUniCode(result, idx);

Denys Khanzhiyev

unread,
Apr 21, 2013, 2:06:47 PM4/21/13
to knockoutjs
You do not need $index. Normally that looks like following:

this.UniCodeID.subscribe(function(newUniCodeID) {
        
if (!newUniCodeId) {
            
this.ProductUniCode("");
        
} else {

            
var self = this;    // 
            
dataFromRemoteService(newuniCodeId,function(result){
               self.ProductUniCode(result);
            });
       
}
    
}, this);


2013/4/21 Ciaran Bruen <cbr...@yahoo.com>

Ciaran Bruen

unread,
Apr 22, 2013, 2:42:29 AM4/22/13
to knock...@googlegroups.com
Excellent Denis thanks a lot I'll give that a try. There's plenty of documentation online about subscribing to single elements but I couldn't find much about collections or arrays.
Reply all
Reply to author
Forward
0 new messages