2013-04-01 2 views
7

Итак, я продолжаю читать, что манипуляция jQuery изнутри контроллера - это плохая практика, но я не понимаю, почему, или как исправить.Каков правильный способ запуска jQuery DOM Manipulation из контроллера?

Ниже приведен код из учебника Youtube, в котором даже комментарии создателя видео являются плохими идеями, но не объясняют причину и продолжают использовать плохое поведение в любом случае.

От https://www.youtube.com/watch?v=ilCH2Euobz0#t=553s:

$scope.delete = function() { 
    var id = this.todo.Id; 
    Todo.delete({id: id}, function() { 
     $('todo_' + id).fadeOut(); 
    }); 
}; 

РЕШЕНИЕ:

На основании ответа Лэнгдона ниже, я прибыл на следующий рабочий код для моей собственной работы, которая вытекает несколько из примера кода выше :

var ProjectListCtrl = function ($scope, Project) { 
    $scope.projects = Project.query(); 
    $scope.delete = function() { 
     var thisElem = this; 
     var thisProject = thisElem.project; 
     var id = thisProject.id; 
     Project.delete({id: id}, function() { 
      var idx = $scope.projects.indexOf(thisProject); 
      if (idx !== -1) { 
       thisElem.destroy('removeItem('+idx+')'); 
      } 
     }); 
    } 

    $scope.removeItem = function(idx) { 
     $scope.projects.splice(idx, 1); 
    } 

} 

app.directive('fadeOnDestroy', function() { 
    return function(scope, elem) { 
     scope.destroy = function(funcComplete) { 
      elem.fadeOut({ 
       complete: function() { 
        scope.$apply(funcComplete) 
       } 
      }); 
     } 
    } 
}); 

Это отличается от ответа Лэнгдона несколькими путями. Я хотел избежать добавления параметра в обратный вызов ngClick, поэтому я храню его в thisProject. Кроме того, пример и мой код необходимо вызвать destroy из $http успешным обратным вызовом, поэтому вместо this, который больше не уместен, я сохраняю элемент с щелчком в thisElem.

UPDATE 2:

Обновленный мое решение в дальнейшем, чтобы отразить, что funcComplete фактически не модифицируя исходный $ объем.

+1

Там нет правильный способ сделать DOM манипуляции в угловых контроллерах. Для этого нужны директивы. См. [Как мне «думать в AngularJS», если у меня есть фон jQuery?] (Http://stackoverflow.com/a/15012542/1095616). – Stewie

ответ

9

Угловой способ справиться с этим через директиву. Я нашел прекрасный пример, чтобы освежить то, о чем вы спрашиваете ниже, хотя это не так чисто, как хотелось бы. Идея заключается в том, что вы создаете директиву, которая будет использоваться как атрибут HTML. Когда элемент привязывается к области вашего контроллера, запускается функция link. Функция затухает элемент (полностью необязательно) и предоставляет метод destroy для последующего вызова вашего контроллера.

Обновление: Изменено на основе комментариев, чтобы реально повлиять на область действия. Не в восторге от решения, и это даже более ужасно, потому что оригинальный автор назвал complete.apply(scope) в своем обратном вызове destroy, но не использует this внутри функции обратного вызова.

Обновление 2: Поскольку директива является тем, что делает обратный вызов асинхронным, вероятно, лучше использовать scope.$apply, но имейте в виду, что это может стать странным, если вы когда-либо используете изолированную область действия в своей директиве.

http://jsfiddle.net/langdonx/K4Kx8/114/

HTML:

<div ng-controller="MyCtrl"> 
    <ul> 
     <li ng-repeat="item in items" fadey="500"> 
      {{item}} 
      <a ng-click="clearItem(item)">X</a> 
     </li> 
    </ul> 
    <hr /> 
    <button ng-click="items.push(items.length)">Add Item</button>  
</div> 

JavaScript:

var myApp = angular.module('myApp', []); 

//myApp.directive('myDirective', function() {}); 
//myApp.factory('myService', function() {}); 

function MyCtrl($scope) { 
    $scope.items = [0, 1, 2]; 

    $scope.clearItem = function(item) { 
     var idx = $scope.items.indexOf(item); 
     if (idx !== -1) { 
      //injected into repeater scope by fadey directive 
      this.destroy(function() { 
       $scope.items.splice(idx, 1); 
      }); 
     } 
    }; 
} 

myApp.directive('fadey', function() { 
    return { 
     restrict: 'A', // restricts the use of the directive (use it as an attribute) 
     link: function(scope, elm, attrs) { // fires when the element is created and is linked to the scope of the parent controller 
      var duration = parseInt(attrs.fadey); 
      if (isNaN(duration)) { 
       duration = 500; 
      } 
      elm = jQuery(elm); 
      elm.hide(); 
      elm.fadeIn(duration) 

      scope.destroy = function(complete) { 
       elm.fadeOut(duration, function() { 
        scope.$apply(function() { 
         complete.$apply(scope); 
        }); 
       }); 
      }; 
     } 
    }; 
}); 

А почему, я думаю, что это просто для разделения проблем и, возможно, удобство использования. Ваш контроллер должен быть связан с потоком данных и бизнес-логикой, а не с интерфейсом. В идеале вы должны указывать директивы для удобства использования (как в случае fadey здесь - прим. Примечание: я бы не назвал это fadey;)).

+2

, если вы используете jQuery, включите его перед angular.js так, чтобы угловой затем использовал полную библиотеку jQuery, а не собственную ** [jQlite] (http://docs.angularjs.org/api/angular.element) **. Делая это, 'elm' в директиве уже является объектом jQuery и не нужно его обертывать в' jQuery (elm) 'http://jsfiddle.net/ADukg/2222/ – charlietfl

+0

Я использую console.log (это) в строке 10 http://jsfiddle.net/K4Kx8/112/ говорится, что это объект «Ребенок». Зачем? что это за класс? –

+0

Все это работает нормально, однако в моем первоначальном примере есть обратный вызов с вызовом Object.delete, в котором 'this' больше не ссылается на элемент, который был нажат, поэтому я не уверен, какой объект мне нужно вызвать' destroy() ' на? (также, извинения, я принял ответ преждевременно, пока не понял, что это, похоже, не охватывает пример выше) – DanH

0

Код, показанный на этом посту, был очень полезен для меня, чтобы понять контроллер отношений - директиву, но он выбрасывал ошибку js.

TypeError: Object function (scope) { 
    $scope.items.splice(idx, 1); 
    console.log($scope.items) 
} has no method '$apply' 

Я немного обновил директиву, и теперь он работает на меня:

function MyCtrl($scope) { 
    $scope.items = [0, 1, 2, 3, 4, 5]; 

    $scope.clearItem = function(item) { 
     var idx = $scope.items.indexOf(item); 
     if (idx !== -1) { 
      //injected into repeater scope by fadey directive 
      this.destroy(function(scope) { 

       $scope.items.splice(idx, 1); 

       //this now shows the expected results 
       console.log($scope.items) 
      }); 
     } 
    }; 
} 

myApp.directive('fadey', function() { 
    return { 
     restrict: 'A', // restricts the use of the directive (use it as an attribute) 
     // fires when the element is created and is linked to the scope of the parent controller 
     link: function(scope, elm, attrs) { 
      var duration = parseInt(attrs.fadey); 
      if (isNaN(duration)) { 
       duration = 500; 
      } 
      elm = jQuery(elm); 
      elm.hide(); 
      elm.fadeIn(duration) 

      scope.destroy = function(complete) { 
       elm.fadeOut(duration, function() { 
        scope.$apply(function() { 
         //note the change here 
         complete(scope); 
        }); 
       }); 
      }; 
     } 
    }; 
}); 
Смежные вопросы