从控制器内触发 jQuery DOM 操作的正确方法是什么? [英] What's the correct way to trigger jQuery DOM Manipulation from within a controller?

查看:14
本文介绍了从控制器内触发 jQuery DOM 操作的正确方法是什么?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

所以我一直读到在控制器中操作 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);如果(idx !== -1){thisElem.destroy('removeItem('+idx+')');}});}$scope.removeItem = 函数(idx){$scope.projects.splice(idx, 1);}}app.directive('fadeOnDestroy', function() {返回函数(范围,元素){scope.destroy = 函数(funcComplete){elem.fadeOut({完成:函数(){范围.$应用(funcComplete)}});}}});

这在几个方面与兰登的回答不同.我想避免向 ngClick 回调添加参数,所以我将它存储在 thisProject 中.此外,该示例和我的代码需要从 $http 成功回调中调用 destroy,而不是不再相关的 this,我'm 将被点击的元素存储在 thisElem 中.

更新 2:

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

解决方案

Angular 的处理方式是通过指令.我找到了一个完美的例子来涵盖你在下面提出的问题,尽管它不像我想要的那么干净.这个想法是您创建一个指令以用作 HTML 属性.当元素绑定到控制器的作用域时,会触发 link 函数.该函数将元素淡入(完全可选)并公开一个 destroy 方法供您的控制器稍后调用.

更新:根据评论修改以实际影响范围.对解决方案不满意,甚至更笨拙,因为原作者在他的销毁回调中调用了 complete.apply(scope),但在回调函数中没有使用 this.

更新 2:由于指令是使回调异步的指令,因此在那里使用 scope.$apply 可能是一个更好的主意,但请记住,如果您曾经使用过,这可能会变得很奇怪指令中的独立作用域.

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

HTML:

<ul><li ng-repeat="项目中的项目" fadey="500">{{物品}}<a ng-click="clearItem(item)">X</a><小时/><button ng-click="items.push(items.length)">添加项目</button>

JavaScript:

var myApp = angular.module('myApp', []);//myApp.directive('myDirective', function() {});//myApp.factory('myService', function() {});函数 MyCtrl($scope) {$scope.items = [0, 1, 2];$scope.clearItem = function(item) {var idx = $scope.items.indexOf(item);如果(idx !== -1){//通过fadey指令注入中继器作用域this.destroy(函数(){$scope.items.splice(idx, 1);});}};}myApp.directive('fadey', function() {返回 {限制:'A',//限制指令的使用(将其用作属性)link: function(scope, elm, attrs) {//当元素被创建并链接到父控制器的作用域时触发var 持续时间 = parseInt(attrs.fadey);如果(isNaN(持续时间)){持续时间 = 500;}榆树 = jQuery(榆树);榆树.隐藏();elm.fadeIn(持续时间)scope.destroy = 函数(完成){elm.fadeOut(持续时间,功能(){范围.$应用(函数(){完成.$应用(范围);});});};}};});

至于为什么,我认为这只是为了分离关注点和可用性.您的控制器应该关注数据流和业务逻辑,而不是接口操作.理想情况下,您的指令应该是为了可用性而编写的(就像这里的 fadey 一样 - ed.注意:我不会称它为 fadey ;)).

So I keep reading that jQuery manipulation from within a Controller is bad practice, but I'm not clear on why, or how to correct.

Below is code from a Youtube tutorial which even the video creator comments is a bad idea, but doesn't explain why and continues to use the bad behavior anyway.

From 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();
    });
};

SOLUTION:

Based on Langdon's answer below, I've arrived at the following working code for my own work, which derives slightly from the example code above:

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)
                }
            });
        }
    }
});

This differs from Langdon's answer in a few ways. I wanted to avoid adding a parameter to the ngClick callback, so I'm storing it in thisProject. Also, the example and my code needs to call destroy from within a $http success callback so instead of this which is no longer relevant, I'm storing the clicked element in thisElem.

UPDATE 2:

Updated my solution further to reflect that funcComplete was not actually modifying the original $scope.

解决方案

The Angular way to handle this is through a directive. I found a perfect example to cover what you're asking below, although it's not as clean as I'd like. The idea is that you create a directive to be used as an HTML attribute. When the element gets bound to the scope of your controller, the link function is fired. The function fades the element in (totally optional) and exposes a destroy method for your controller to call later.

Update: Modified based on comments to actually affect the scope. Not thrilled with the solution, and it's even jankier because the original author called complete.apply(scope) in his destroy callback, but doesn't use this inside the callback function.

Update 2: Since the directive is the one making the callback asynchronous, it's probably a better idea to use scope.$apply there, but keep in mind that that might get weird if you ever use isolated scope in your directive.

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);
                    });
                });
            };
        }
    };
});

As for why, I think it's simply for separation of concerns and perhaps usability. Your controller should be concerned with data flow and business logic, not interface manipulation. You directives should ideally be written for usability (as in the case of fadey here -- ed. note: I wouldn't call it fadey ;)).

这篇关于从控制器内触发 jQuery DOM 操作的正确方法是什么?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆