angularjsangularjs-directiveangularjs-scopeangularjs-apply

refresh specific directives in angularjs


I've been pulling my hair out trying every combination imaginable to try to get branches of a tree menu to refresh properly when changes are made to the tree structure. Essentially what I have is a set of recursive nested directives to display the branching of the tree. I built a javascript class to store the directive data for any given portion of the tree structure. The class holds a model that I've essentially interfaced so it has a handful of known methods (e.g. getVals() to load data from ajax and getId(), getName(), getLabel() to retrieve common properties for building the tree). I then use it in two ways to build the tree with pairs of this object, the first is for a given branching point it will hold a single model (with multiple subBranches) describing a 'group' (with data loaded from a Doctrine ORM collection that is basically an array of doctrine entity style objects), and the other being a single model representing the entities that are the actual branchings. The idea being that any trunk (branching point) will have two options - one to display the details for that group/parent, the other to show just the names of the children. Then each child will have an option to show it's details or show it's children and so on.

function treeBranch() {
    this.refresh = false;

    // class and title information for expander button
    this.toggleShown = function() {
        this.vis = !(this.vis);

        if(this.vis == true) {
            this.trunkClass = 'glyphicon glyphicon-chevron-down';
            this.trunkTitle = 'collapse';
        } else {
            this.trunkClass = 'glyphicon glyphicon-chevron-right';
            this.trunkTitle = 'expand';
        }
};

    // default populator code
    this.populate = function(toggle,force,pcbf) {
        toggle = (typeof(toggle) == 'boolean') ? toggle : true;
        force  = (typeof(force) == 'force') ? force : false;
        var br = this;
        if((typeof(br.subBranches) == 'undefined') || (br.subBranches.length == 0))
            force = true;

        if((typeof(br.model) != 'undefined') && (typeof(br.loadMethod) != 'undefined')) {
            br.model[br.loadMethod](
                function() {
                    br.subBranches = []; // reset subBranches array
                    for(var k in br.model.subs) { // subs should be loaded with a keyed array
                        var newSubBranch = new treeBranch();
                        newSubBranch.model = br.model.subs[k];
                        newSubBranch.label = 'Name: ' + newSubBranch.model.getName(); // expects model to have a getName method
                        newSubBranch.trunk = br;
                       br.subBranches.push(newSubBranch);
                    }
                    (toggle == true) && br.toggleShown();
                     (typeof(pcbf) == 'function') && pcbf();
                 },
                 force,
                 br.model.getFilter()
             );
         }
     };

     this.getLabel = function() {
         if(this.model != null) {
             return ((typeof(this.model.getId) == 'function') ? this.model.getId() : '') +
                 ((typeof(this.model.getName) == 'function') ? ' '+this.model.getName() : '');
         } else {
             return this.label;
         }
     }

     // core properties
     this.label = '';
     this.vis = false;
     this.trunkClass = 'glyphicon glyphicon-chevron-right';
     this.trunkTitle = 'expand';
     this.subBranches = [];
     this.trunk = null;

     this.model = null;
     this.loadMethod = 'getVals'; // function of model to populate it with data

 };
/**
   -Trunk {treeBranch with entity as model}
      [show details option]
      +--> branches {treeBranch instance with collection as model}
           [show branches/children option]
           - branch1 {treeBranch entity model}
            [show details option]
                +--> branches {treeBranch collection model}
                     [show branches/children option]
                     - subbranch1 ... etc
           - branch2 {treeBranch with entity as model}
            [show details option]
                +--> branches {treeBranch collection model}
                     [show branches/children option]
     ... etc ...
*/

My problem is coming in when I start adding functions to manipulate the properties of a given branch point (entity) and/or add/delete/move branches themselves. I have the forms working for the add/update/delete (still working on the move) but when the data is saved, because my models are doing their own ajax rest calls, the tree doesn't update to reflect the changes. One of the problems is that I set up the add button after the UL tag for the children - this I can update fine because it's in the same directive as the template with the UL tags. But the children are under a second directive that is tied to an ng-repeat around the LI for displaying each sub-branch wrapper. Thus the update/delete buttons show up in that template, but the scope I need to refresh is the parent. I understand I can do the $parent.$parent.reload() to call my refresh script, but when I get around to doing the 'move' functionality, I may be moving a list item half-way across the tree. I have a 'refresh' button at the branching points in the template, and tried doing a document.getElementById('branchpointX').click(); but that is cludgy and I've been having fits trying to figure out the best way to refresh that point in scope within that scope's .reload() function. $scope.$apply sometimes collides with existing $digest refreshes, and even throwing them in a $timeout sometimes still throws an error that a refresh is already in progress.

So I'm wondering what is the best way to refresh a specific directive in a heirarchical tree of said directives? I've tried building a stack in each subBranch with tags of it's branch-points back to the origin, but then if I try running treeStack['someParentId'].reload() it doesn't seem to work when in the child scope.

SW


Solution

  • Turns out what I was looking for was to set $broadcast triggers using the rootScope that I could then listen for with $on and a callback function in each respective scope.

    e.g. the save, when called has a callback function so my edit directive (in a different factory controller) can add $rootScope.$broadcast('branch1Reload'); to the callback, then the directive for that branch just needs a $rootscope.$on('branch1Reload', function() { /* do stuff */ ; scope.reload(); });