AngularJS directive shares scope with ng-repeat -

2019-08-01 07:39发布

问题:

I'm experiencing a weird situation. I need to have two sortable lists that should interchange elements by drag-n-drop or Add/Remove events.

I created a directive that works well. Also the controller event do the right job. The problem begins when methods are combined (button Add + drag-n-drop + button Add again). KA-BOOM!

I put together this plnkr: http://plnkr.co/edit/DumufP1kDdkz1INAXwmF?p=preview

Click on the elements before click the button action (Add/Remove).

Let me share some of the code of the directive just for fun but please visit the link to see the entire implementation. There is more information of how to reproduce the issue in the plnkr

.directive('sortableList', function ($log) {
    return {
        restrict: 'A',
        scope: {
            fromList: '=',
            toList: '='
        },
        link: function (scope, elm, attrs) {                        

            var callback = {
                receive: function (event, ui) {

                    //-- Get the scope of the list-item
                    var scopeItem = ui.item.scope();
                    //-- Get new list index
                    var newIdx = ui.item.index();

                    //-- Find position in the list
                    var prevIdx = scope.fromList.indexOf(scopeItem.obj);                    

                    //-- Remove from source list
                    scope.fromList.splice(prevIdx, 1);
                    //-- Add to target list
                    if (newIdx >= scope.toList.length) {
                        scope.toList.push(scopeItem.obj);
                    }
                    else {
                        scope.toList.splice(newIdx, 0, scopeItem.obj);
                    }

                    //ui.item.removeClass('selectedSortListItem').addClass('sortListItem');

                    scope.$apply();
                },
                stop: function (event, ui) {
                    //$log.log(ui);
                }
            };            

            //-- Apply jquery ui sortable plug-in to element
            elm.sortable({
                handle: ".handle",
                connectWith: '.sortColumnsConnect',
                dropOnEmpty: true,
                cancel: ".ui-state-disabled",
                receive: callback.receive,
                stop: callback.stop
            }).disableSelection();

            //-- Sniff for list changes 
            /*scope.$watch(attrs.sortableList, function (newVal) {
                //-- Apply callback

                //if (angular.isUndefined(newVal)) return;

                elm.sortable('option', 'receive', callback.receive);

                if (!angular.isUndefined(attrs.trackSorting) && Boolean(attrs.trackSorting)) {
                    elm.sortable('option', 'stop', callback.stop);
                }
            });*/


        }
    }
})

Help is appreciated.

回答1:

I finally made it work. I have it all working in this plunker. I thought it was related to the scope of both directives (custom + ng-repeat) but it turned out to be that I needed to leave ng-repeat do the whole job and never remove the ng-repeat comments, otherwise the angular directive will brake.

One thing though that my directive needs to take care is the $destroy even, as the directive itself is holding a reference of an object that better is removed later when the page leaves or something in order to avoid a memory leak situation.

Now, let's share some code here for fun.. again..

.directive('sortableList', function ($log, $parse,$timeout) {
return {
    restrict: 'A',
    scope: {
        list: '='
    },
    link: function (scope, elm, attrs) {                        

        /*
         * We need to preserve a copy of ng-repeat comments
         * in order to not brake the ng directive. Lets made a copy and
         * and insert it back to the html list in the remove even.
         */
        var comments = elm.contents().filter(function(){ 
          return this.nodeType == 8;
        });

        var comment = undefined;
        if (comments.length > 0){
          comment = comments[0];
        }

        var callback = {
            start: function(event, ui){

              ui.item.sortable = {
                received: false,
                startIndex: ui.item.index()
              };

            },
            receive: function (event, ui) {
              ui.item.sortable.received = true;
            },
            update: function (event, ui) {
              //$log.log(elm);
              $log.log('update');

              var scopeItem = ui.item.scope();

              //-- Get new list index. Index in array not in html list
              var newIdx = ui.item.index(); 

              if (ui.item.sortable.received){

                $log.log('received');

                ui.sender.sortable('cancel');
                ui.item.sortable.received = false;
                //ui.item.sortable.doremove = true;

                scope.$apply(function(){

                  //-- Add to target list
                  if (newIdx >= scope.list.length) {
                    scope.list.push(scopeItem.obj);
                  }
                  else {
                    $log.log(newIdx);
                    scope.list.splice(newIdx, 0, scopeItem.obj);
                  }

                  ui.item.removeClass('selectedSortListItem').addClass('sortListItem');

                });
              }
              else {
                //-- Sort list
                if (ui.item.sortable.startIndex != newIdx){

                  $log.log('sort list');

                  scope.$apply(function(){

                    var idx = scope.list.indexOf(scopeItem.obj);

                    //-- end destroy
                    if (idx > -1){
                      scope.list.splice(idx, 1);  
                    }    
                    //-- Add to the new position
                    scope.list.splice(newIdx, 0, scopeItem.obj);

                  });
                }
              }


            },
            remove: function( event, ui ) {
              var scopeItem = ui.item.scope();

              /* Do the normal node removal  */
              //-- Seek
              var idx = scope.list.indexOf(scopeItem.obj);

              //-- end destroy
              if (idx > -1){
                scope.list.splice(idx, 1);  
              }

              /*
               * Insert back ng-repeat comments to the html list to avoid braking
               * the angular directive.
               */
              if (elm.children("li:not('.ui-state-disabled')").length === 0 && angular.isDefined(comment)){
                $log.log('insert comment');
                $log.log(comment);
                elm.append(comment);
                //$log.log(elm);
              }

              //$log.log('I have childrens: ' + elm.children("li:not('.ui-state-disabled')").length);
              //$log.log('remove me please at:' + idx);
            },
            stop: function (event, ui) {
              $log.log('stop');
            }
        };            

        scope.$watch('list.length', function() {
          // Timeout to let ng-repeat modify the DOM
          $timeout(function() {
            $log.log('epa!');
            //-- need to unselect those selected, otherwise Vishal will go like: Leo there is an error.. what? what? what?
            elm.children("li.selectedSortListItem").toggleClass('selectedSortListItem').toggleClass('sortListItem');
            elm.sortable('refresh');
          });
        });

        //-- Apply jquery ui sortable plug-in to element
        elm.sortable({
            handle: ".handle",
            connectWith: '.sortColumnsConnect',
            dropOnEmpty: true,
            cancel: ".ui-state-disabled",
            helper: "clone",
            start: callback.start,
            receive: callback.receive,
            update: callback.update,
            stop: callback.stop,
            remove: callback.remove
        }).disableSelection();



    }
}

})

Take a look at the plunker to understand how the directive is invoked and the purpose of it. I might have some stuff that forgot to remove after so many re-factories.. but seems like now is doing the right thing.. at least it's not braking like it was before.

Thanks.