Knockout: What is the best way to detect if an `observableArray` is not set yet or really empty in Knockout?

Created on 3 Nov 2015  路  12Comments  路  Source: knockout/knockout

I posted this question in stackoverflow and think people here might know the answer.

I want to ask why you designed ko.observableArray()() equals [] rather than undefined. How do you want developers to tell if it is set explicitly to [] or just remains its default value []?

I think it will be more consistent if the default value of observableArray is undefined.

Most helpful comment

You can assign undefined to an observable array. Thus your initialization would look like this:

var arr = ko.observableArray();
arr(undefined);

You could even create your own wrapper to do this:

ko.undefinedObservableArray = function () {
    var arr = ko.observableArray();
    arr(undefined);
    return arr;
};

All 12 comments

See reply to your SO post... Not sure if it's exactly what you want.

Thanks. We are on the same topic. Please see my comment.

Oh, I can learn from the following code block from observableArray.js.

// Populate ko.observableArray.fn with read/write functions from native arrays
// Important: Do not add any additional functions here that may reasonably be used to *read* data from the array
// because we'll eval them without causing subscriptions, so ko.computed output could end up getting stale
ko.utils.arrayForEach(["pop", "push", "reverse", "shift", "sort", "splice", "unshift"], function (methodName) {
    ko.observableArray['fn'][methodName] = function () {
        // Use "peek" to avoid creating a subscription in any computed that we're executing in the context of
        // (for consistency with mutating regular observables)
        var underlyingArray = this.peek();
        this.valueWillMutate();
        this.cacheDiffForKnownOperation(underlyingArray, methodName, arguments);
        var methodCallResult = underlyingArray[methodName].apply(underlyingArray, arguments);
        this.valueHasMutated();
        return methodCallResult;
    };
});

That said, I wish an observable can turn into an observableArray after mapping if the data being mapped is indeed an array. Or, change the default value of observableArray to undefined.

It does seem quite unusual to have to convert an observable to an observableArray, I've never found a need to do that. I just allow it to sit empty and use empty to mean undefined.

My colleague also pointed out to me that It doesn't make sense to write you code to depend on non-default javascript array functionality.

@simonmurdock For instance, my SPA holds a property, userBooks. Later at some time, an ajax call will be sent to server to fetch the data of userBooks and injects the data into userBooks using ko.mapping.fromJS.

What initial value will you give to userBooks? ko.observable() which evaluates to undefined or ko.observableArray() which evaluates to []?

If you use observable, your userBooks which actually holds an array of books doesn't inherently have functions like push and pop.

If you use the later one, you can't tell if the userBooks is indeed empty or it just hasn't been set by ajax call. You'll have to add an additional flag to indicate it's set or not. There is also a drawback because you'll have to call userBooks.isSet() to determine if it's set, rather than simply !userBooks() which will make your code more consistant.

I see what you are saying, but I have looked at my code and gotten around this issue by doing 2 things:

  • Include a userBookCount variable in your root object, which holds the total number for that user, which I need for paging
  • the div/message which shows when userBooks().length == 0 code is written in such a way that it says "no books available, or no selection made"

@simonmurdock I used to use userBooks().length == 0 but now I tend not to fool the user displaying "no book available" with the fact that the ajax call is not completed yet at that second. Your UI might be "no book available" initially and flash to display the user's books. I know I can set a flag, say canDisplay which is false before ajax call ends, to prevent the false "no book available" message, but I tend not to. A loading animation or plain white page is better than a false message, for me at least.

userBookCount is like the flag I said, with the difference that it holds more information.

BTW, I found Array() also returns []. So KO is consistent with the JS array.

That's what I was saying before about default javascript array functionality. It seems to be more a JS issue than a KO one.

For what it's worth, you could get the behavior you want by writing your own computed / pureComputed with a read/write that returns an underlying array or undefined if it's never been set. Something like

_userBooks = undefined
userBooks = ko.pureComputed(
  read: =>
    return _userBooks
  write: (newValue) =>
    _userBooks = newValue
)

You can assign undefined to an observable array. Thus your initialization would look like this:

var arr = ko.observableArray();
arr(undefined);

You could even create your own wrapper to do this:

ko.undefinedObservableArray = function () {
    var arr = ko.observableArray();
    arr(undefined);
    return arr;
};

@froodian @mbest Cool! Thanks.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

aminroosta picture aminroosta  路  5Comments

andersekdahl picture andersekdahl  路  6Comments

nkosi23 picture nkosi23  路  5Comments

azaslonov picture azaslonov  路  3Comments

IPWright83 picture IPWright83  路  7Comments