What's the correct way to trigger jQuery DOM Manipulation from within a controller?

前端 未结 2 854
青春惊慌失措
青春惊慌失措 2020-12-30 13:14

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 tuto

相关标签:
2条回答
  • 2020-12-30 13:42

    The code showed in this post was very helpful for me to understand the relationship controller - directive, but it was throwing a js error.

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

    I have updated the directive slightly, and now it works to me:

    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);
                        });
                    });
                };
            }
        };
    });
    
    0 讨论(0)
  • 2020-12-30 13:47

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

    0 讨论(0)
提交回复
热议问题