I've got a many-to-many relationship in Breeze:
Product *---1 ProductWidgets 1----* Widgets
Product needs to know when any of it's Widgets changes. Widgets can be added or removed from Products at any time.
Ideally, I'd want to do something like:
product.widgets.on('change', function () {});
...but I'm imagining I need something like:
var handleWidgetChange = function (changes) {
console.log("here are the changes", changes);
};
for(var i = 0; i < product.productWidgets.length; i++) {
// make sure we're getting events for the current set of widgets
product.productWidgets[i].widget.entityAspect.propertyChanged.subscribe(handleWidgetChange);
// if any of the current set of product widgets gets pointed elsewhere, catch that
product.productWidgets[i].entityAspect.propertyChanged.subscribe(function (change) {
if (change.propertyName === "widget") {
change.oldValue.entityAspect.propertyChanged.unsubscribe();
change.oldValue.entityAspect.propertyChanged.subscribe(handleWidgetChange);
}
})
}
// handle new product widgets and removal of product widgets
product.productWidgets.arrayChanged.subscribe(function (change) {
if (change.added) {
change.added[0].widget.entityAspect.propertyChanged.subscribe(handleWidgetChange);
} else if (change.removed) {
change.removed[0].widget.entityAspect.propertyChanged.unsubscribe();
}
});
Is there a recommended way to achieve this?
(Note: I'm using angular, and would love to just $watch('product.productWidgets', function () {}, true)
but that gives a circular reference error.)
Memory leaks are a huge risk in JavaScript, in part because there are no weak references. You must be careful with events. You really don't want to iterate over entities adding and removing subscriptions.
You also do not want to use Angular watches for monitoring model changes because you'll drive UI performance into the ground. There are too many entities with too many properties and you'll surely make a mistake by leaving watches in place long after you should have stopped watching.
Fortunately, Breeze provides a central entity change monitoring facility. A Breeze EntityManager
listens for changes to any of the entities it holds in cache.
var widgetType = manager.metadataStore.getEntityType('Widget'); var productWidgetType = manager.metadataStore.getEntityType('ProductWidget'); entityManager.entityChanged.subscribe(entityChanged); function entityChanged(changeArgs) { var entity = changeArgs.entity; if (entity.entityType === productWidgetType || entity.entityType === widgetType) { // do what you do when someone does something to an entity of this type // perhaps call back into a method on that instance that knows what to do entity.somethingChanged(changeArgs.entityAction); } }
This one event notifies you of any change to any entity in the manager's cache. It will be called frequently so be crisp in your evaluation. For example, consider deactivating your event handler during queries.
The changeArgs.entityAction
tells you what just happened to the entity. There are many actions that trigger this event: a property could change, its EntityState
could change (add/modify/delete/detach), etc.
You don't have to worry about the product.productWidgets
array. When a ProductWidget
is added or removed from that array, the ProductWidget.productId
foreign key will change ... and you're picking that up in this entityChanged
handler.
There is no need to worry about a memory leak because the EntityManager
already holds a reference to the entity and will continue to do so until you detach the entity or dispose of the EntityManager
instance (and all of your own or the UI's references to the entity). That, to my mind, is appropriate lifetime management.