2

In a function that takes a generator, that may be a plain JS Array or an ko.observableArray, how can I use the observableArray with minimal overhead?

This is what the code currently looks like:

function (itemGenerator) { // returns observableArray or JS array
  var allItems = ko.observable();

  // triggered after user interaction
  var itemsFromGenerator = itemGenerator();
  if (ko.isObservable(itemsFromGenerator)) {
    allItems(itemsFromGenerator());

    itemsFromGenerator.subscribe(newValue => {
      allItems(newValue)
    });
  } else {
    allItems(children);
  }
}

Is there any way to replace allItems with the observableArray if itemsFromGenerator is an observableArray? So that I don't have to subscribe to further changes "manually" and have to copies of the data around.

(This is used in a TreeView implementation, where I only want to generate the items array when a node is expanded.)

Timm
  • 2,652
  • 2
  • 25
  • 34

2 Answers2

1

Just have allItems contain the observable array, then you have the outside observable to track when the observable array is replaced. No subscriptions are needed.

function (itemGenerator) { // returns observableArray or JS array
  var allItems = ko.observable();

  // triggered after user interaction
  var itemsFromGenerator = itemGenerator();
  if (ko.isObservable(itemsFromGenerator)) {
    allItems(itemsFromGenerator);
  } else {
    allItems(ko.observableArray(itemsFromGenerator));
  }
}

This answer is based on the information you have given. I still don't know why you need to maintain the reference to itemsFromGenerator unless it is somehow independently referenced outside of this function. I also don't know when the code under // triggered after user interaction is actually called. Finally, I don't know where/how these items in the array are used.

wired_in
  • 2,593
  • 3
  • 25
  • 32
  • Ok, I was hoping to be able to avoid that nesting (observableArray inside observable), but it probably is not that bad. The small overhead from wrapping plain arrays in observableArrays is also not an issue here. This is probably the best idea, or changing the API contract so that the itemGenerator always returns an observableArray. I have updated the question with the information, that this is used in a TreeView implementation. And so when a new item matches a search query, I want this to automatically add to the TreeNode's children. – Timm Sep 05 '14 at 08:41
1

If I got it right, you could use ko.utils.unwrapObservable() (see good explanation):

You should use ko.utils.unwrapObservable in cases where you don't know if you have been given an observable or not.

Code should be like this:

function ViewModel(itemGenerator) {
    var self = this;
    this.itemsFromGenerator = ko.observableArray();
    this.generate = function() {
        self.itemsFromGenerator(ko.utils.unwrapObservable(itemGenerator()));                                       
    };
}

Where generate is your trigger to run items generator. See the demo. In generator function you can switch between plain array and observable array.

Community
  • 1
  • 1
Ilya Luzyanin
  • 7,910
  • 4
  • 29
  • 49
  • Ilya: In my test, this does not update the itemsFromGenerator array, when the observableArray created by the itemGenerator function is updated. I don't need to update the array from inside the view model, I just want to get changes, if it is an observableArray. See updated fiddle: http://jsfiddle.net/483abv7g/1/ – Timm Sep 05 '14 at 08:37
  • BTW, as of now there is a "shortcut" ko.unwrap to unwrapObservable (ko.utils.unwrapObservable === ko.unwrap // true). – Timm Sep 05 '14 at 08:43