2013-04-01 5 views
7

Also ich lese, dass jQuery Manipulation von einem Controller ist eine schlechte Praxis, aber ich bin nicht klar, warum, oder wie zu korrigieren.Was ist der richtige Weg, um jQuery DOM Manipulation aus einem Controller auszulösen?

Unten ist Code von einem Youtube-Tutorial, das sogar die Videokünstler Kommentare ist eine schlechte Idee, aber nicht erklären, warum und weiterhin das schlechte Verhalten sowieso.

Von 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:

unten auf Langdon Antwort Basierend, ich habe für meine eigene Arbeit am folgenden Arbeits Code angekommen, die über Code leicht aus dem Beispiel leitet :

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

Dies unterscheidet sich von Langdons Antwort in einigen Punkten. Ich wollte vermeiden, einen Parameter zu dem ngClick Callback hinzuzufügen, also speichere ich es in thisProject. Außerdem muss das Beispiel und mein Code destroy innerhalb eines $http Erfolgsrückrufs aufrufen, so dass ich anstelle von this, das nicht mehr relevant ist, das geklickte Element in thisElem speichern.

UPDATE 2:

meine Lösung weiter aktualisiert, um widerzuspiegeln, dass funcComplete nicht tatsächlich die ursprünglichen $ Umfang modifiziert wurde.

+1

Es gibt keine richtige Art und Weise DOM-Manipulationen in Angular-Controller zu tun. Dafür gibt es Richtlinien. Siehe [Wie kann ich in AngularJS denken, wenn ich einen jQuery-Hintergrund habe?] (Http://stackoverflow.com/a/15012542/1095616). – Stewie

Antwort

9

Der Winkelweg, um dies zu handhaben, ist durch eine Richtlinie. Ich habe ein perfektes Beispiel gefunden, um zu beschreiben, was Sie unten fragen, obwohl es nicht so sauber ist, wie ich es gerne hätte. Die Idee ist, dass Sie eine Anweisung erstellen, die als HTML-Attribut verwendet wird. Wenn das Element an den Bereich Ihres Controllers gebunden wird, wird die link-Funktion ausgelöst. Die Funktion blendet das Element ein (ganz optional) und stellt eine Destroy-Methode für den Controller bereit, um sie später aufzurufen.

Update: Geändert basierend auf Kommentaren, um den Bereich tatsächlich zu beeinflussen. Nicht begeistert von der Lösung, und es ist noch jankier, weil der ursprüngliche Autor complete.apply(scope) in seinem Rückruf zu zerstören, aber this innerhalb der Callback-Funktion nicht verwendet.

Update 2: Da die Direktive den Rückruf asynchron macht, ist es wahrscheinlich eine bessere Idee, dort scope.$apply zu verwenden, aber bedenken Sie, dass das komisch werden kann, wenn Sie in Ihrer Direktive isolierten Bereich verwenden.

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

Was, warum, ich denke einfach für die Trennung von Bedenken, es ist vielleicht die Benutzerfreundlichkeit. Ihr Controller sollte sich mit dem Datenfluss und der Geschäftslogik befassen, nicht mit der Handhabung von Schnittstellen. Direktiven sollten idealerweise für die Usability geschrieben werden (wie im Fall von fadey hier. Hinweis: Ich würde es nicht fadey nennen;)).

+2

Wenn Sie jQuery verwenden, schließen Sie es vor angular.js ein, damit angle dann die vollständige jQuery-Bibliothek an Stelle ihrer eigenen ** [jQlite] (http://docs.angularjs.org/api/angular.element) ** annimmt. Auf diese Weise ist 'elm' in der Direktive bereits ein jQuery-Objekt und muss nicht in' jQuery (elm) eingebunden werden '' http://jsfiddle.net/ADukg/2222/ – charlietfl

+0

Ich benutze console.log (this) In Zeile 10 http://jsfiddle.net/K4Kx8/112/ heißt es, dies ist ein "Kind" -Objekt. Warum? Was ist diese Klasse? –

+0

Das alles scheint gut zu funktionieren, aber mein ursprüngliches Beispiel enthält einen Object.delete success Callback, innerhalb dessen "this" nicht mehr auf das angeklickte Element verweist, also bin ich mir nicht sicher, welches Objekt ich 'destroy()' aufrufen muss auf ? (Entschuldigung, ich habe die Antwort vorzeitig angenommen, bis ich merkte, dass es das obige Beispiel nicht zu erfassen scheint) – DanH

0

Der Code in diesem Beitrag war sehr hilfreich für mich zu verstehen, die Beziehung Controller - Richtlinie, aber es war ein JS Fehler werfen.

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

Ich habe die Richtlinie etwas aktualisiert, und jetzt funktioniert es mir:

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