KnockOutJS - getting an 'undefined' value,

2019-07-18 20:01发布

http://jsfiddle.net/toddhd/XWgK5/2/

Looking at the fiddler is the best way to see this. If you run it in a browser with a developer mode, such as Chrome, or FF with FireBug, and run the code, you'll see an error show up in the console. It tells me that the value I'm trying to read is undefined.

The error is occurring on line 50:

 if (item.Id == self.selectedOfferTypeDetailsType().Id) {

self.selectedOfferTypeDetailsType().Id is undefined

The thing is, you'll see that I'm referencing this same value elsewhere, and it seems to be working OK elsewhere. Clearly I'm missing something... been banging my head against the wall for two days and could use a fresh set of eyes please.

标签: knockout.js
3条回答
地球回转人心会变
2楼-- · 2019-07-18 20:15

Both Damien and jaux have sharp eyes, but they're only partially correct in this case. It's true that a Knockout computed is evaluated immediately after it's declared so Knockout can determine to which other observables your computed needs to subscribe, but that's not when your error is occurring.

The initial evaluation of your computed after definition is running without issue. However, as soon as you call ko.applyBindings your selects are hooked up and start working with your view models, and that's when your error occurs.

An Unusual Behavior

Once your first select (with id=offerTypes) is bound, it does something unusual : it causes an immediate write to selectedOfferType. This is not the way binding selects usually works. Usually a select doesn't write it's value until the user changes something, but in this case your select binding is writing a value to SelectedOfferType immediately. Bear with me and I'll show you how I found the error.

Let's Get Tracing

SelectedOfferType is being observed by offerDetails, so as soon as your first select is bound and writes a new value to SelectedOfferType, offerDetails runs to check if it's value has changed as well. Inside offerDetails, you write a value to selectedOfferTypeDetailsType, which alerts it's subscribers - subscribers including "offer". "Offer" executes, but you just set self.selectedOfferTypeDetailsType() to null (in offerDetails), and you can't read the "id" property of "null".

So, the question is "why is the select writing a value to SelectedOfferType when it's bound?" The answer lies in your bindings. When you initialize your view model, it looks like you're trying to pre-set the first dropdown with the second value from offerTypes :

self.selectedOfferType = ko.observable(self.offerTypes()[1]);

and your binding

<select id="offerTypes" data-bind="options: offerTypes, ... value: selectedOfferType"></select>

agrees that you're attaching the value to your select. However, you set self.selectedOfferType to the object at self.offerTypes()[1] but the part of your binding that I omitted above indicates that the values of your options aren't objects, but whatever value is at the 'Id' property.

<select data-bind="... optionsValue: 'Id', ..."></select>

This combination of filling selectedOfferType with an object, but assigning the optionsValue to a property causes Knockout to understand that the value in selectedOfferType is invalid (not selectable) so Knockout immediately updates selectedOfferType with the first possible option value. In this case, a '0'.

That triggers a write, which triggers all the subscribers, which results in your error.

Awesome! So how to I fix that?

Since most of your code is written to expect an object in selectedOfferType, I'd just remove "optionsValue: 'Id'" from your select.

How's that for a long-winded answer? :-D

查看更多
Emotional °昔
3楼-- · 2019-07-18 20:18

The problem is in:

self.offerDetails = ko.computed(function() {
    var activeCategories = ko.observableArray();
    ko.utils.arrayForEach(self.offerTypeDetailsTypes(), function (item) {
        if (item.Id == self.selectedOfferType().Id)
            activeCategories.push(item.DisplayName);
    });
    self.selectedOfferTypeDetailsType(activeCategories()[0]); // PROBLEM!
    return activeCategories();
});

self.selectedOfferTypeDetailsType is set to a string at this point, no doubt when you try to access it later, it doesn't have the Id property.

查看更多
Bombasti
4楼-- · 2019-07-18 20:21

In this case what you need to do is to deffer the evaluation of the computed property.

When you create your viewModel instance self.selectedOfferTypeDetailsType will return undefined. With deferEvaluation set to true the evalution of the offer computed is delay until you "consume" (really need the value of) the offer computed.

self.offer = ko.computed({
    read: function () {
        var o = 'Not Found';
        ko.utils.arrayForEach(self.offerTypeDetailsTypes(), function (item) {
            if (item.Id == self.selectedOfferTypeDetailsType().Id) {
                alert('Yay');
            }
        });
        return o;
    },
    deferEvaluation : true
});

I hope this helps.

查看更多
登录 后发表回答