Reputation: 17863
I got a "TypeError: object is not a function" error when
EntityManager.saveChanges
. The error arises before breeze sends anything to the server.When I read the (long) stack trace, I see that the error is thrown inside a Breeze method called
InitializeEntityPrototype.prototype.getProperty
.
There are other places you might encounter this error; this just happens to be the place I discovered it today.
Note that my application uses Knockout (ko). That means my entity model is managed by the breeze "ko" model adapter which expects every data property to be a ko.observable (an observable
or an observableArray
). That means that your property values are implemented as functions, not primitive data types, arrays, or objects.
Breeze initializes my entities with observable properties so I don't have to do it myself. But it is up to me preserve those observables when I set entity models in my code.
One of the most common mistakes in ko programming is to set the property rather than call the property setter.
todo.Description("foo"); // Correct ... call the ko.observable assignment function
todo.Description = "foo"; // WRONG ... wipes out the observable function !!!
The moment I make the mistake of assigning "foo" to todo.Description
, the observable function is gone ... and so is Breeze's ability to monitor changes to that property.
My data bound HTML controls may appear to continue working. Knockout doesn't have to bind to an observable; it will happily bind to a primitive data value. But now this becomes a one-time, read-only binding. The property ceases to be observable. Subsequent changes to the property will not be propagated to the screen.
In other words, my mistaken assignment results in a silent error in UI behavior.
But my mistake is not silent when Breeze attempts to process the property. Breeze assumes that an entity data property is a ko function. The Breeze ko model adapter doesn't bother to check ... it just calls what it presumes to be a function. Here's the (internal) entity getProperty
implementation:
proto.getProperty = function (propertyName) {
return this[propertyName]();
};
You can see why that is going to throw an exception if this[propertyName]
is anything other than a function.
Sadly for me, the error is likely to be thrown somewhere deep in the bowels of a Breeze operation. The error message "object is not a function" (or something similar) could be about anything. I'm unlikely to make the connection.
This S.O. question is a reminder to look for this particular cause.
It's clearly my fault. Now I have to find where I assigned the entity property instead of calling the function ... and fix that.
My search depends upon knowing the property name. The present error message doesn't tell me the name. So I've got to breakpoint the code and catch the error as it is thrown.
A "simple" expedient is to temporarily monkey-patch this method in breeze.debug.js with a try/catch
version.
return this[propertyName]();
"change it to this:
proto.getProperty = function(propertyName) {
try {
return this[propertyName]();
} catch (err) {
debugger;
err.message = propertyName + ' is not a ko function; did you wipe it out by assignment?\n' +
(err.message || '');
throw err;
}
}
run the app again with the Developer Tools open
It should stop at the debugger
line where you'll learn the property name and the entity involved.
It would be nice if Breeze composed this message for me. In fact, the team is looking into doing that.
The main obstacle is performance. The getProperty
and setProperty
methods are on a hot path. They get called a lot, especially during query result materialization. The Breeze team is leery of extra logic in this sensitive area. We don't want everyone to pay a big price to catch developer error, at least not in a production (minified) Breeze library.
Let's give the Breeze team time to figure this out.
Upvotes: 4
Views: 3107
Reputation: 63719
Your question is well-written, touches on a common issue, and may well be helpful to others searching for this specific problem. However, it still is probably asking for opinions.
In any case...
Me and my colleagues call the problem from your situation "The Knockout Tax": some programming inconvenience you have to deal with in return for getting to work with a great MVVM library. A different instance of the same problem occurs when you data-bind="visible: !myObservable"
and forget to execute it as a function to get the value.
To answer the question: no, I don't think Breeze should handle this problem, at least not with a richer error message. If anything, I think there are three different options if you're in the question's situation:
Description
is a KnockoutObservable<string>
then the compiler will complain if you assign a regular string to it.But that's just my 2 cts - reading my own answer I do feel this ("should situation X be handled by framework Y?") is all a matter of opinion.
Upvotes: 3