从控制器中触发jQuery DOM Manipulation的正确方法是什么?

时间:2013-04-01 13:42:30

标签: javascript angularjs angularjs-controller

所以我一直在阅读控制器中的jQuery操作是不好的做法,但我不清楚为什么或如何纠正。

以下是来自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不再相关,我将点击的元素存储在{{1 }}

更新2

进一步更新了我的解决方案,以反映funcComplete实际上并未修改原始$ scope。

2 个答案:

答案 0 :(得分:9)

处理此问题的Angular方法是通过指令。我找到了一个完美的例子来涵盖你在下面提到的内容,虽然它并不像我想的那样干净。我们的想法是创建一个用作HTML属性的指令。当元素绑定到控制器的范围时,将触发link函数。该函数淡化元素(完全可选)并为您的控制器公开一个destroy方法以便稍后调用。

更新:根据评论修改实际影响范围。对解决方案并不感到兴奋,因为原作者在他的destroy回调中调用了complete.apply(scope),但在回调函数中没有使用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;))。

答案 1 :(得分: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);
                    });
                });
            };
        }
    };
});