Search code examples
angularjsbreeze

breeze - how to subscribe to changes on a many-to-many relationship


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.)


Solution

  • 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.