Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    primarykey
    data
    text
    <p>Oh my. Big trouble in your code! Before delving into details, what leaps off the page is the likelihood that you are adding subscriptions repeatedly to every episode in the <code>episodes</code> array every time that array changes. YIKES!</p> <h2>Event propagation delays</h2> <p>Before I go on about my misgivings, I want to acknowledge your specific experience of inconsistency between when your listener fires and the value of <code>manager.hasChanges</code>. That's real. The KO subscription is firing before the <code>EntityManager</code> has heard about the change ... which is why it reports false. The news of the change has not yet arrived for the manager to hear.</p> <p>I've noticed this too. In fact, if you look at the <em>viewModel.js</em> in the <a href="http://www.breezejs.com/samples/todo-knockout" rel="nofollow">TodoSample</a>, you'll see that we worked around it by adding a <code>setTimeout</code> to give Breeze a chance to hear the change:</p> <pre> // listen for changes with Breeze PropertyChanged event item.entityAspect.propertyChanged.subscribe(function () { if (suspendItemSave) { return; } // give EntityManager time to hear the change setTimeout(saveIfModified, 0); function saveIfModified() { if (item.entityAspect.entityState.isModified()) { dataservice.saveChanges(); } } }); </pre> <p>I wish I knew another way. That's just a timing problem between KO and Breeze. We haven't figured out how to finesse it yet. Not sure we'll be able to.</p> <h2>Model rule or View rule?</h2> <p>Let's back up. Is the <code>episode</code> supposed to save when its <code>listenedTo</code> property changes <strong>no matter what View it appears in?</strong> If your answer is "yes", then you have an application business rule that truly does belong in the model and you really do want something listening for episode changes.</p> <p>But if the answer is "no" ... if the save should be triggered by the "checkbox", not a change to the object, then this is a UI rule ... a View rule ... and you should use KO to listen to the <strong>checkbox</strong>, not to the episode property.</p> <h2>Saving when an entity changes</h2> <p>Let's go with the first case and assert that your application rule is as follows: "<em>always save when <code>listenedTo</code> changes, no matter what causes it to change</em>".</p> <p>I know in the <a href="http://www.breezejs.com/samples/todo-knockout" rel="nofollow">TodoSample</a> we demonstrated a way to listen directly to the entity for changes to one of its properties (any of its properties). That's powerful and it's intuitive.</p> <p>I've come to believe that it is not the safest way. It has the potential for memory leaks in apps with multiple ViewModels that share the same entity. This "Todo" app has only one screen so that's not a real worry. But in a larger app ... I'd be concerned.</p> <p>So I'm recommending against listening for changes to an <code>Episode</code> property. Instead <strong>listen to the <code>EntityManager</code>!</strong> Look at this snippet from the <em>datacontext.js</em> in the recently released <a href="http://www.breezejs.com/spa-template" rel="nofollow">Breeze SPA template</a>.</p> <pre> function configureManagerToSaveModifiedItemImmediately() { manager.entityChanged.subscribe(entityStateChanged); function entityStateChanged(args) { if (args.entityAction === breeze.EntityAction.EntityStateChange) { var entity = args.entity; if (entity.entityAspect.entityState.isModified()) { saveEntity(entity); } } } } </pre> <p>Notice how it listens for a state change in any entity that the EntityManager caches. It is interested only in transitions to the "Modified" state. When detected, it saves the entity.</p> <p>Now this may be too broad for you. But you can imagine registering extra filtering logic that fits the specific needs of your application.</p> <h2>SaveQueuing</h2> <p>New issue having to do with auto-saving. This issue is important no matter how you trigger the save.</p> <p>The user can click pretty fast. She is bound to trigger save requests faster than they can be processed. Breeze (by default) won't let an <code>EntityManager</code> save again while it is waiting for the server to return results from a pending save operation. It will throw an exception.</p> <p>It has to wait because it can't change the state of the entity from changed to unchanged until it knows if the save was successful. If the save fails, you want to keep the entity in its "unsaved" state.</p> <p>Look at the top of the <em>datacontext.js</em> for <code>manager.enableSaveQueuing(true)</code>. This is <strong>not</strong> a native feature of breeze. It's a feature of a plugin, <em>Scripts/breeze.savequeuing.js</em> You'll want to load this plugin. Read about it in the write up for the <a href="http://www.breezejs.com/spa-template" rel="nofollow">Breeze SPA template</a> </p> <h2>Why so complicated</h2> <p>You might think <strong>this Breeze thing is mighty complicated</strong>. Actually, it's not Breeze that's introducing the complication. It's your desire to trigger a save when the entity changes its state that is adding the complications. </p> <p>I'm not saying you're wrong to do so. I am saying that this approach is an opportunity afforded by Breeze that takes some care in its implementation.</p> <p>Without Breeze, you'd have a devil of a time tracking the entity state at all. So your only really safe option would be to trigger the save based on a change to the checkbox ... in which case the effort is no more complicated than a KO binding to the checkbox.</p> <p>Well ... ok ... it is a little more complicated due to the Breeze/KO timing problem that you tripped over which necessitate the<code>setTimeout</code> gambit. But I hope you see my point.</p>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. VO
      singulars
      1. This table or related slice is empty.
    2. VO
      singulars
      1. This table or related slice is empty.
    3. VO
      singulars
      1. This table or related slice is empty.
    1. COGreat answer thanks. This app only has a single view to modify this particular entity so I may resort to the timeout fix. It's difficult to say whether this piece of logic is part of the domain model or the view when there is only a single view modifying the entity! The domain model (in my mind) only exists serverside. The entity Breeze manages is just a DTO. Slightly offtopic but does anyone know how to correct my subscribe code? JS is all new to me.
      singulars
    2. COI understand when you say "*The domain model (in my mind) only exists serverside. The entity Breeze manages is just a DTO.*" Yes, the server (storage and business logic) is the "single source of truth". That's important. On the other hand, I urge you to think of your stateful, SPA application client as having a domain model of its own. Its "domain context" reflects the "meaning" of the model *on the client* as experienced by the user. Clearly the model on the client is more than a DTO ... more than a bag of data. It's a different domain than the server, but a domain nonetheless.
      singulars
    3. COUnfortunately I seem to have accepted this a little prematurely. The timeout statement doesn't seem to resolve the issue for me. HasChanges is still false, even after increasing the timeout value. I thought about your comments re. subscribing to the EntityManager's property change notification instead and this actually seemed to make more sense. The problem here is, I change the checkbox bound to the ListenedTo property and for some reason Breeze told me 3 properties had changed even though old/new values were shown to be the same.
      singulars
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload