Aaron Jessen
Aaron Jessen

Reputation: 404

Edit or Remove BreezeJS EntityManager Once Instance Reference Lost

I'm constructing a CRM application with a SPA structure, using BreezeJS and AngularJS, and I'm utilizing a dynamically-generated tabbed environment to display what I'll refer to here on out as modules. When a user clicks a side menu item, a new tab is created, and an HTML template (aka module) is loaded into the new content area. While some of the module types are to be opened only once at a time (a grid of Accounts), others, such as an Account Editor module, can be opened many times. That way, the user can modify many accounts at any given time throughout the day, but always be doing so from a single Accounts grid instance.

So far, I have a working system for the Account Editor module, in that I create a master Breeze EntityManager with appropriate configuration parameters (service name, save options, etc.) and then make a copy of it any time a new Account Editor is created, using masterManager.createEmptyCopy() (as per the code outlined at http://www.breezejs.com/documentation/multiple-managers#CreateMultipleManagers):

var serviceName = "/breeze/accounts";

var ds = new breeze.DataService({
    serviceName: serviceName,
    hasServerMetadata: true
});

var masterManager = new breeze.EntityManager({
               dataService: ds,
               saveOptions: new breeze.SaveOptions({ allowConcurrentSaves: false })
           });

function createManagerCopy() {
    // same configuration as master; no entities in cache.
    var sandboxManager = masterManager.createEmptyCopy();
    return sandboxManager;
}

Then, I call an EntityQuery, by passing in the copied EntityManager and entity Id (key), in order to get the appropriate Account and populate each open editor with apparently all the benefits of Breeze.

function queryAccountByKey(mgr, key) {
    var keyPredicate = breeze.Predicate.create('id', 'eq', key);
    var query = new breeze.EntityQuery('AccountsBase')
                          .expand('ContactsBase')
                          .where(keyPredicate);

    var promise = mgr.executeQuery(query)
                     .catch(queryFailed);

    return promise;

    function queryFailed(error) {
        console.log('Query Account by Id Failed', error);
        return $q.reject(error); // so downstream promise users know it failed
    }
}

There does not appear to be any conflict with the other open editors and their respective entity managers, so long I maintain the copied EntityManager in the module's Angular scope. Editing and saving this way is, well…a breeze! :P (sorry)

The problem I’m having is when I switch to another Angular route, such as a login screen, and then come back to the home screen. Due to the complexities of each tabbed module, any Account Editor module I had opened before the routing took place must be reloaded from stored settings (even if the layout is stored in the cache). However, then there are two Breeze entity managers handling that account. The effect is that saving changes for a single Account Editor now commits twice (or however many times you’ve gone away and back from the home layout).

What I’m trying to figure out is how to get access to a specific EntityManager instance, from the client, once I navigate back to the home layout and the original instance reference (in the scope, in my case) is lost. In other words, is there an EntityManager collection that I can query to reuse or delete an instance? If I can grab a manager instance by name or other Id, I can simply reassign it to my scope without removing and recreating it.

In the event that I do need to remove the EntityManager, I can’t find anything anywhere to describe implementing something like a destroy() method…just a clear() method, which only clears the entities and does not remove the EntityManager from the client. Of course, if I'm going about this all wrong, please advise as to a better approach. Hopefully I've explained things clearly enough that someone can lend a possible solution.

SOLUTION

So, thanks to PW Kad's answer, I was able to reuse the entity managers instead of deleting and re-creating them, by adding them to an empty object collection on the $rootScope when they're initialized (still using the createEmptyCopy() method outlined above). This allows for access throughout the Angular app, without polluting the global namespace. I had already implemented a unique ID to be associated with each tab - and thus the modules in the content areas - so I appended that ID to create a name, such as 'EM_' + instanceId, for storage in the $rootScope.entityManagers object. Later, I can retrieve the EntityManager instance in the $rootScope, using this ID, which is found in each Account Editor's Angular Controller.

Here's the new code:

// NEW: Add an 'entityManagers' object to the $rootScope of my main app module
angular.module('app', [])
.run(['$rootScope', function($rootScope) {
    $rootScope.entityManagers = {};
    $rootScope.entityManagers.count = 0;
}]);

// In the dataServices factory for the main app module
var serviceName = "/breeze/accounts";

var ds = new breeze.DataService({
    serviceName: serviceName,
    hasServerMetadata: true
});

var masterManager = new breeze.EntityManager({
    dataService: ds,
    saveOptions: new breeze.SaveOptions({ allowConcurrentSaves: false })
});

function createManager(instanceId) {
    // make a copy of the above EntityManager (with no cached entities)
    var sandboxManager = masterManager.createEmptyCopy();

    // NEW: Save the EntityManager instance to the $rootScope
    $rootScope.entityManagers['EM_' + instanceId] = sandboxManager;
    $rootScope.entityManagers.count++;

    return sandboxManager;
}

// In the event that you want to delete the EntityManager from the $rootScope
function deleteManager(instanceId) {
    var manager = $rootScope.entityManagers['EM_' + instanceId];
    manager.clear();
    delete $rootScope.entityManagers['EM_' + instanceId];
    $rootScope.entityManagers.count--;
}

// And lastly, inside any Angular controller
$scope.instanceId = '1234'; // Dynamically-assigned at runtime

$scope.init = function() {
    var manager = $rootScope.entityManagers['EM_' + $scope.instanceId];
    if (manager === undefined) {
       $scope.entityManager = 
              entityManagerService.createManager($scope.instanceId);
    } else {
       $scope.entityManager = manager;
    }
}

$scope.getEntityById = function(id){
    //use $scope.entityManager here to query database via Breeze
}

While I would still like to know where BreezeJS keeps its EntityManager collection, I have a valid solution. I hope it helps someone!

Upvotes: 0

Views: 590

Answers (1)

PW Kad
PW Kad

Reputation: 14995

It seems like a poor design choice to reinstantiate a new entity manager every time you change pages as in effect you are losing the caching mechanism and sharing the entities across pages but if you must do so you can always do so fairly trivially with something like -

var manager = {};

manager = manager ? manager.clear() : new breeze.EntityManager();

or

manager = manager ? (function () { delete manager; return new breeze.EntityManager(); })() : new breeze.EntityManager();

or many other ways really.

I would recommend not doing that though and just doing something like this -

var manager = {};

// Some route activation logic
if (!manager) {
    manager = new breeze.EntityManager();
}

Edit

Well the short answer is I am not 100% sure how the breeze object in the global namespace is referencing the entity manager. I don't think there is a collection of entity managers that the breeze object keeps, but I may be wrong. I don't see why calling delete on the entity manager isn't working but this should do what you are trying to do -

Somewhere in either one of your closures or in the global namespace create an object called entityManagers. Example -

window.entityManagers = {};
window.entityManagers.count = 0;

Then tack the manager while it is created onto that namespace. There is probably a more dynamic way to do this just giving some pseudo-code -

window.entityManagers.createNewManager = function (name) {
    window.entityManagers[name] = new breeze.EntityManager();
}


window.entityManagers.createNewManager('ManagerNumber1');

And then when you want to dispose of a specific instance just clear it then delete it. Get the instance you want either by a variable reference or if you can't do that for some crazy reason just grab it off the affected entity -

window.entityManagers.deleteManager = function (name) {
    window.entityManagers[name].clear();
    delete window.entityManagers[name];
}

window.entityManagers.deleteManager('ManagerNumber1');

For all intents and purposes, as long as there no other references to that instance of the manager in other modules / controllers / whatever than this should delete the entityManager from the world. I still don't fully understand the use case so take it with a grain of salt.

Upvotes: 1

Related Questions