'this' vs $ scope in AngularJS-Controllern

1027

Im Abschnitt "Komponenten erstellen" der AngularJS-Homepage gibt es dieses Beispiel:

controller: function($scope, $element) {
  var panes = $scope.panes = [];
  $scope.select = function(pane) {
    angular.forEach(panes, function(pane) {
      pane.selected = false;
    });
    pane.selected = true;
  }
  this.addPane = function(pane) {
    if (panes.length == 0) $scope.select(pane);
    panes.push(pane);
  }
}

Beachten Sie, wie die selectMethode hinzugefügt wird $scope, aber die addPaneMethode hinzugefügt wird this. Wenn ich es ändere $scope.addPane, bricht der Code.

Die Dokumentation besagt, dass es tatsächlich einen Unterschied gibt, aber es wird nicht erwähnt, was der Unterschied ist:

In früheren Versionen von Angular (vor 1.0 RC) konnten Sie thisdie $scopeMethode austauschbar verwenden. Dies ist jedoch nicht mehr der Fall. Innerhalb von Methoden, die im Bereich definiert sind thisund $scopeaustauschbar sind (Winkelsätze thisauf $scope), aber nicht anders in Ihrem Controller-Konstruktor.

Wie funktioniert thisund $scopefunktioniert in AngularJS-Controllern?

Alexei Boronine
quelle
Ich finde das auch verwirrend. Wenn eine Ansicht einen Controller angibt (z. B. ng-controller = '...'), scheint der diesem Controller zugeordnete $ scope damit einherzugehen, da die Ansicht auf $ scope-Eigenschaften zugreifen kann. Aber wenn eine Direktive einen anderen Controller benötigt (und ihn dann in ihrer Verknüpfungsfunktion verwendet), kommt der diesem anderen Controller zugeordnete $ scope nicht mit?
Mark Rajcok
Wurde dieses verwirrende Zitat über "Frühere Versionen ..." inzwischen entfernt? Dann wäre vielleicht ein Update vorhanden?
Dmitri Zaitsev
Wenn Sie für Unit-Tests 'this' anstelle von '$ scope' verwenden, können Sie dem Controller keinen verspotteten Scope hinzufügen, sodass Sie keine Unit-Tests durchführen können. Ich denke nicht, dass es eine gute Praxis ist, "dies" zu verwenden.
Abentan

Antworten:

999

"Wie funktioniert thisund $scopefunktioniert in AngularJS-Controllern?"

Kurze Antwort :

  • this
    • Wenn die Controller-Konstruktorfunktion aufgerufen wird, thisist dies der Controller.
    • Wenn eine für ein $scopeObjekt definierte Funktion aufgerufen wird, thisist der "Gültigkeitsbereich gültig, als die Funktion aufgerufen wurde". Dies kann (oder auch nicht!) Sein, auf dem $scopedie Funktion definiert ist. Also, innerhalb der Funktion thisund $scopemöglicherweise nicht gleich.
  • $scope
    • Jeder Steuerung ist ein $scopeObjekt zugeordnet.
    • Eine Controller- (Konstruktor-) Funktion ist dafür verantwortlich, Modelleigenschaften und -funktionen / -verhalten für die zugehörigen Funktionen festzulegen $scope.
    • Über $scopeHTML / view kann nur auf Methoden zugegriffen werden, die für dieses Objekt definiert sind (und übergeordnete Bereichsobjekte, wenn die prototypische Vererbung im Spiel ist). ZB von ng-click, Filtern usw.

Lange Antwort :

Eine Controller-Funktion ist eine JavaScript-Konstruktorfunktion. Wenn die Konstruktorfunktion ausgeführt wird (z. B. wenn eine Ansicht geladen wird), this(dh der "Funktionskontext"), wird das Controller-Objekt festgelegt. Also in der Controller-Konstruktorfunktion "tabs", wenn die addPane-Funktion erstellt wird

this.addPane = function(pane) { ... }

Es wird auf dem Controller-Objekt erstellt, nicht auf $ scope. Ansichten können die Funktion addPane nicht sehen - sie haben nur Zugriff auf Funktionen, die in $ scope definiert sind. Mit anderen Worten, im HTML funktioniert dies nicht:

<a ng-click="addPane(newPane)">won't work</a>

Nachdem die Controller-Konstruktorfunktion "tabs" ausgeführt wurde, haben wir Folgendes:

nach Registerkarten Controller-Konstruktorfunktion

Die gestrichelte schwarze Linie zeigt die prototypische Vererbung an - ein isolierter Bereich erbt prototypisch von Scope . (Es erbt nicht prototypisch von dem Gültigkeitsbereich, in dem die Direktive im HTML gefunden wurde.)

Jetzt möchte die Link-Funktion der Pane-Direktive mit der Tabs-Direktive kommunizieren (was wirklich bedeutet, dass sie die Tabs in irgendeiner Weise beeinflussen muss, um den Bereich zu isolieren). Ereignisse könnten verwendet werden, aber ein anderer Mechanismus besteht darin, dass die Fensteranweisung requireden Registerkarten-Controller enthält. (Es scheint keinen Mechanismus für die Fensteranweisung zu requireden Registerkarten $ scope zu geben.)

Dies wirft also die Frage auf: Wenn wir nur Zugriff auf den Tabs-Controller haben, wie erhalten wir Zugriff auf die Tabs, die $ scope isolieren (was wir wirklich wollen)?

Nun, die rot gepunktete Linie ist die Antwort. Der "Bereich" der Funktion addPane () (ich beziehe mich hier auf den Funktionsumfang / die Schließungen von JavaScript) ermöglicht der Funktion den Zugriff auf die Registerkarten, die $ scope isolieren. Dh addPane () hat Zugriff auf die "Registerkarten IsolateScope" im obigen Diagramm aufgrund eines Abschlusses, der beim Definieren von addPane () erstellt wurde. (Wenn wir stattdessen addPane () für das tabs $ scope-Objekt definieren würden, hätte die pane-Direktive keinen Zugriff auf diese Funktion und hätte daher keine Möglichkeit, mit den tabs $ scope zu kommunizieren.)

Zur Beantwortung des anderen Teils Ihrer Frage: how does $scope work in controllers?:

In Funktionen, die für $ scope definiert sind, thiswird "der Gültigkeitsbereich von $ festgelegt, in dem / wann die Funktion aufgerufen wurde". Angenommen, wir haben den folgenden HTML-Code:

<div ng-controller="ParentCtrl">
   <a ng-click="logThisAndScope()">log "this" and $scope</a> - parent scope
   <div ng-controller="ChildCtrl">
      <a ng-click="logThisAndScope()">log "this" and $scope</a> - child scope
   </div>
</div>

Und das hat ParentCtrl(allein)

$scope.logThisAndScope = function() {
    console.log(this, $scope)
}

Wenn Sie auf den ersten Link klicken, wird dies angezeigt thisund $scopeist identisch, da " der Gültigkeitsbereich, der beim Aufruf der Funktion gültig war " der mit dem verknüpfte Gültigkeitsbereich ist ParentCtrl.

Den zweiten Link klicken , wird verraten thisund $scopesind nicht gleich, da „ der Umfang in Kraft , wenn die Funktion aufgerufen wurde “ ist der Umfang im Zusammenhang mit der ChildCtrl. Also hier thisist auf ChildCtrl's gesetzt $scope. Innerhalb der Methode $scopebefindet sich immer noch der Bereich ParentCtrl$.

Geige

Ich versuche, nicht thisinnerhalb einer in $ scope definierten Funktion zu verwenden, da es verwirrend wird, welcher $ scope betroffen ist, insbesondere wenn man bedenkt, dass ng-repeat, ng-include, ng-switch und Direktiven alle ihre eigenen untergeordneten Bereiche erstellen können.

Mark Rajcok
quelle
6
@tamakisquare, ich glaube, der fettgedruckte Text, den Sie zitiert haben, gilt, wenn die Controller-Konstruktorfunktion aufgerufen wird - dh wenn der Controller erstellt wird = einem $ -Bereich zugeordnet. Dies gilt später nicht mehr, wenn beliebiger JavaScript-Code eine für ein $ scope-Objekt definierte Methode aufruft.
Mark Rajcok
79
Beachten Sie, dass es jetzt möglich ist, die Funktion addPane () direkt in der Vorlage aufzurufen, indem Sie den Controller benennen: "MyController as myctrl" und dann myctrl.addPane (). Siehe docs.angularjs.org/guide/concepts#controller
Christophe Augier
81
Zu viel inhärente Komplexität.
Inanc Gumus
11
Dies ist eine sehr informative Antwort, aber als ich auf ein praktisches Problem zurückkam ( wie man $ scope aufruft. $ Apply () in einer Controller-Methode, die mit 'this' definiert wurde ), konnte ich es nicht herausfinden. Obwohl dies immer noch eine nützliche Antwort ist, finde ich die "inhärente Komplexität" verwirrend.
Dumbledad
11
Javascript - viel Seil [um sich aufzuhängen].
AlikElzin-kilaka
55

Der Grund, warum 'addPane' diesem zugewiesen wird, liegt in der <pane>Direktive.

Die paneDirektive tut require: '^tabs', die das Registerkarten-Controller-Objekt von einer übergeordneten Direktive in die Verknüpfungsfunktion einfügt.

addPanewird zugewiesen, thisdamit die paneLink-Funktion es sehen kann. Dann ist in der paneLink-Funktion addPanenur eine Eigenschaft des tabsControllers und es ist nur tabsControllerObject.addPane. Die Verknüpfungsfunktion der Pane-Direktive kann also auf das Registerkarten-Controller-Objekt und damit auf die addPane-Methode zugreifen.

Ich hoffe, meine Erklärung ist klar genug. Es ist schwer zu erklären.

Andrew Joslin
quelle
3
Danke für die Erklärung. Die Dokumente lassen den Eindruck entstehen, dass der Controller nur eine Funktion ist, die den Bereich festlegt. Warum wird der Controller wie ein Objekt behandelt, wenn die gesamte Aktion im Bereich ausgeführt wird? Warum nicht einfach den übergeordneten Bereich an die Verknüpfungsfunktion übergeben? Bearbeiten: Um diese Frage besser zu formulieren: Wenn sowohl Controller-Methoden als auch Scope-Methoden mit derselben Datenstruktur (dem Scope) arbeiten, warum nicht alle an einem Ort platzieren?
Alexei Boronine
Es scheint, dass der übergeordnete Bereich nicht an die lnk-Funktion übergeben wird, da "wiederverwendbare Komponenten unterstützt werden sollen, die nicht versehentlich Daten im übergeordneten Bereich lesen oder ändern sollten". Wenn eine Direktive jedoch tatsächlich EINIGE SPEZIFISCHE Daten im übergeordneten Bereich lesen oder ändern möchte (wie dies bei der Direktive 'pane' der Fall ist), ist ein gewisser Aufwand erforderlich: 'erfordern' Sie den Controller, in dem sich der gewünschte übergeordnete Bereich befindet, und definieren Sie dann a Methode auf diesem Controller (verwenden Sie 'this' not $ scope), um auf bestimmte Daten zuzugreifen. Da der gewünschte übergeordnete Bereich nicht in die lnk-Funktion eingefügt wird, ist dies vermutlich der einzige Weg, dies zu tun.
Mark Rajcok
1
Hey Mark, es ist tatsächlich einfacher, den Geltungsbereich der Richtlinie zu ändern. Sie können einfach die Link-Funktion jsfiddle.net/TuNyj
Andrew Joslin
3
Danke @Andy für die Geige. In Ihrer Geige erstellt die Direktive keinen neuen Bereich, sodass ich sehen kann, wie die Verknüpfungsfunktion hier direkt auf den Bereich des Controllers zugreifen kann (da es nur einen Bereich gibt). Die Registerkarten und Fensteranweisungen verwenden isolierte Bereiche (dh es werden neue untergeordnete Bereiche erstellt, die nicht prototypisch vom übergeordneten Bereich erben). Für den Fall des isolierten Bereichs scheint es, dass die Definition einer Methode auf einem Controller (unter Verwendung von 'this') die einzige Möglichkeit ist, einer anderen Direktive den (indirekten) Zugriff auf den anderen (isolierten) Bereich zu ermöglichen.
Mark Rajcok
27

Ich habe gerade eine ziemlich interessante Erklärung über den Unterschied zwischen den beiden gelesen und eine wachsende Präferenz, Modelle an den Controller anzuhängen und den Controller als Alias ​​zu verwenden, um Modelle an die Ansicht zu binden. http://toddmotto.com/digging-into-angulars-controller-as-syntax/ ist der Artikel.
Er erwähnt es nicht, aber wenn Sie beim Definieren von Direktiven etwas zwischen mehreren Direktiven teilen müssen und keinen Dienst wünschen (es gibt legitime Fälle, in denen Dienste problematisch sind), hängen Sie die Daten an den Controller der übergeordneten Direktive an.

Der $scopeDienst bietet viele nützliche Dinge, $watchdie am offensichtlichsten sind. Wenn Sie jedoch nur Daten an die Ansicht binden müssen, ist die Verwendung des einfachen Controllers und des "Controllers als" in der Vorlage in Ordnung und wahrscheinlich vorzuziehen.

Derek
quelle
20

Ich empfehle Ihnen, den folgenden Beitrag zu lesen: AngularJS: "Controller as" oder "$ scope"?

Es beschreibt sehr gut die Vorteile der Verwendung von "Controller as", um Variablen gegenüber "$ scope" verfügbar zu machen.

Ich weiß, dass Sie speziell nach Methoden und nicht nach Variablen gefragt haben, aber ich denke, dass es besser ist, sich an eine Technik zu halten und mit dieser übereinzustimmen.

Meiner Meinung nach ist es aufgrund des im Beitrag behandelten Variablenproblems besser, einfach die Technik "Controller as" zu verwenden und sie auch auf die Methoden anzuwenden.

Liran Brimer
quelle
16

In diesem Kurs ( https://www.codeschool.com/courses/shaping-up-with-angular-js ) erklären sie, wie man "dies" und viele andere Dinge verwendet.

Wenn Sie dem Controller über "diese" Methode eine Methode hinzufügen, müssen Sie diese in der Ansicht mit dem Namen des Controllers "dot" für Ihre Eigenschaft oder Methode aufrufen.

Wenn Sie beispielsweise Ihren Controller in der Ansicht verwenden, haben Sie möglicherweise folgenden Code:

    <div data-ng-controller="YourController as aliasOfYourController">

       Your first pane is {{aliasOfYourController.panes[0]}}

    </div>
Sandro
quelle
6
Nachdem ich den Kurs durchlaufen hatte, war ich sofort durch die Verwendung von Code verwirrt. Vielen $scopeDank, dass Sie ihn erwähnt haben.
Matt Montag
16
Das natürlich nicht erwähnt $ Umfang überhaupt, sie nur nutzen asund thiswie kann es helfen , den Unterschied zu erklären?
Dumbledad
10
Meine erste Berührung mit Angular war aus dem erwähnten Kurs, und wie $scopenie erwähnt, habe ich gelernt, nur thisin den Controllern zu verwenden. Das Problem ist, dass Sie, wenn Sie anfangen, Versprechen in Ihrem Controller zu behandeln, viele Referenzprobleme thishaben und anfangen müssen, Dinge wie var me = thisdas Referenzieren des Modells thisinnerhalb der Versprechen-Rückgabefunktion zu tun . Aus diesem Grund bin ich immer noch sehr verwirrt darüber, welche Methode ich verwenden soll $scopeoder this.
Bruno Finger
@BrunoFinger Leider brauchst du var me = thisoder .bind(this)wann immer du Versprechen machst oder andere Dinge, die den Verschluss schwer machen. Es hat nichts mit Angular zu tun.
Dzmitry Lazerka
1
Wichtig ist , zu wissen , dass ng-controller="MyCtrl as MC"äquivalent zu setzen $scope.MC = thissich in der Steuerung - es eine Instanz (diese) von MyCtrl auf dem Rahmen für die Verwendung in der Vorlage definiert über{{ MC.foo }}
William B
3

In früheren Versionen von Angular (vor 1.0 RC) konnten Sie dies austauschbar mit der $ scope-Methode verwenden, dies ist jedoch nicht mehr der Fall. Innerhalb von Methoden, die für den Bereich definiert sind, sind this und $ scope austauschbar (Angle setzt dies auf $ scope), nicht jedoch innerhalb Ihres Controller-Konstruktors.

Um dieses Verhalten wiederherzustellen (weiß jemand, warum es geändert wurde?), Können Sie Folgendes hinzufügen:

return angular.extend($scope, this);

am Ende Ihrer Controller-Funktion (vorausgesetzt, $ scope wurde in diese Controller-Funktion eingefügt).

Dies hat einen schönen Effekt, wenn Sie über ein Controller-Objekt, mit dem Sie untergeordnet werden können, auf den übergeordneten Bereich zugreifen können require: '^myParentDirective'

Kamil Szot
quelle
7
Dieser Artikel bietet eine gute Erklärung, warum dies und $ scope unterschiedlich sind.
Robert Martin
1

$ scope hat ein anderes 'this' als der Controller 'this'. Wenn Sie also ein console.log (this) in den Controller einfügen, erhalten Sie ein Objekt (controller) und this.addPane () fügt dem Controller-Objekt die addPane-Methode hinzu. Der Bereich $ hat jedoch einen anderen Bereich, und alle Methoden in seinem Bereich müssen von $ scope.methodName () aufgerufen werden. this.methodName()Inside Controller bedeutet, Methos innerhalb des Controller-Objekts hinzuzufügen. $scope.functionName()ist in HTML und drinnen

$scope.functionName(){
    this.name="Name";
    //or
    $scope.myname="myname"//are same}

Fügen Sie diesen Code in Ihren Editor ein und öffnen Sie die Konsole, um zu sehen ...

 <!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>this $sope vs controller</title>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.7/angular.min.js"></script>
    <script>
        var app=angular.module("myApp",[]);
app.controller("ctrlExample",function($scope){
          console.log("ctrl 'this'",this);
          //this(object) of controller different then $scope
          $scope.firstName="Andy";
          $scope.lastName="Bot";
          this.nickName="ABot";
          this.controllerMethod=function(){

            console.log("controllerMethod ",this);
          }
          $scope.show=function(){
              console.log("$scope 'this",this);
              //this of $scope
              $scope.message="Welcome User";
          }

        });
</script>
</head>
<body ng-app="myApp" >
<div ng-controller="ctrlExample">
       Comming From $SCOPE :{{firstName}}
       <br><br>
       Comming from $SCOPE:{{lastName}}
       <br><br>
       Should Come From Controller:{{nickName}}
       <p>
            Blank nickName is because nickName is attached to 
           'this' of controller.
       </p>

       <br><br>
       <button ng-click="controllerMethod()">Controller Method</button>

       <br><br>
       <button ng-click="show()">Show</button>
       <p>{{message}}</p>

   </div>

</body>
</html>
Aniket Jha
quelle