„to” a $ zakres w kontrolerach AngularJS


1026

W sekcji „Tworzenie komponentów” na stronie głównej AngularJS znajduje się ten przykład:

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

Zwróć uwagę, w jaki selectsposób metoda jest dodawana $scope, ale addPanemetoda jest dodawana do this. Jeśli zmienię to na $scope.addPane, kod się psuje.

Dokumentacja mówi, że tak naprawdę jest różnica, ale nie wspomina o różnicy:

Poprzednie wersje Angulara (wcześniej 1.0 RC) pozwalały na używanie thiszamiennie z tą $scopemetodą, ale już tak nie jest. Wewnątrz metod zdefiniowanych w zakresie thisi $scopesą one wymienne (zestawy kątowe thisdo $scope), ale nie inaczej wewnątrz konstruktora kontrolera.

Jak działa thisi $scopedziała w kontrolerach AngularJS?


Uważam to również za mylące. Gdy widok określa kontroler (np. Ng-controller = '...'), zakres $ powiązany z tym kontrolerem wydaje się pochodzić z nim, ponieważ widok może uzyskać dostęp do właściwości $ scope. Ale kiedy dyrektywa wymaga innego kontrolera (a następnie używa go w funkcji łączenia), zakres $ związany z tym innym kontrolerem nie jest z nim zgodny?
— Mark Rajcok,

Czy to mylący cytat o „poprzednich wersjach ...” został już usunięty? Więc może aktualizacja byłaby na miejscu?
— Dmitri Zaitsev,

W przypadku testów jednostkowych, jeśli użyjesz „tego” zamiast „$ scope”, nie możesz wstrzyknąć kontrolerowi fałszywego zakresu, a więc nie możesz wykonać testów jednostkowych. Nie sądzę, że dobrą praktyką jest używanie tego.
— abentan

Odpowiedzi:


999

„Jak działa thisi $scopedziała w kontrolerach AngularJS?”

Krótka odpowiedź :

  • this
    • Gdy wywoływana jest funkcja konstruktora kontrolera, thisto kontroler.
    • Gdy $scopewywoływana thisjest funkcja zdefiniowana w obiekcie, „zasięg obowiązuje, gdy funkcja została wywołana”. Może to być (lub nie!) $scopeFunkcja zdefiniowana na tej funkcji. Więc wewnątrz funkcji thisi $scopemoże nie być taka sama.
  • $scope
    • Każdy kontroler ma powiązany $scopeobiekt.
    • Funkcja kontrolera (konstruktora) jest odpowiedzialna za ustawianie właściwości modelu i funkcji / zachowania na powiązanej z nim funkcji $scope.
    • Tylko metody zdefiniowane na tym $scopeobiekcie (i obiektach nadrzędnych, jeśli w grę wchodzi dziedziczenie prototypowe) są dostępne z widoku HTML /. Np. Z ng-clickfiltrów, itp.

Długa odpowiedź :

Funkcja kontrolera to funkcja konstruktora JavaScript. Gdy funkcja konstruktora jest wykonywana (np. Gdy ładuje się widok), this(tzn. „Kontekst funkcji”) jest ustawiany na obiekt kontrolera. Tak więc w funkcji konstruktora kontrolera „tabs”, gdy tworzona jest funkcja addPane

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

jest tworzony na obiekcie kontrolera, a nie na $ scope. Widoki nie widzą funkcji addPane - mają dostęp tylko do funkcji zdefiniowanych w $ scope. Innymi słowy, w HTML to nie zadziała:

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

Po wykonaniu funkcji konstruktora kontrolera „tabs” otrzymujemy:

po funkcji konstruktora kontrolera tabulatorów

Przerywana czarna linia wskazuje na dziedzictwo prototypowe - zakres izolowany prototypowo dziedziczy po Scope . (Nie dziedziczy prototypowo z zakresu obowiązującego w przypadku napotkania dyrektywy w HTML).

Teraz funkcja linkowania dyrektywy panelu chce komunikować się z dyrektywą tabs (co tak naprawdę oznacza, że ​​musi wpływać na tabs w jakiś sposób izolować $ scope). Zdarzenia mogą być użyte, ale innym mechanizmem jest ustawienie dyrektywy requiretabulatora na kontroler kart. (Wygląda na to, że nie ma mechanizmu dyrektywy panelu requirew zakresie $ tabs.)

Nasuwa się więc pytanie: jeśli mamy dostęp tylko do kontrolera kart, w jaki sposób uzyskujemy dostęp do kart izolujących $ scope (czego naprawdę chcemy)?

Cóż, czerwona kropkowana linia jest odpowiedzią. „Zasięg” funkcji addPane () (odnoszę się tutaj do zakresu / zamknięć funkcji JavaScript) daje tej funkcji dostęp do zakładek izolujących $ scope. Tj. AddPane () ma dostęp do „zakładek IsolateScope” na powyższym diagramie z powodu zamknięcia, które zostało utworzone, gdy zdefiniowano addPane (). (Jeśli zamiast tego zdefiniowaliśmy addPane () na obiekcie tabs $ scope, dyrektywa pane nie miałaby dostępu do tej funkcji, a zatem nie miałaby możliwości komunikowania się z tabs $ scope.)

Aby odpowiedzieć na drugą część pytania how does $scope work in controllers?:

W ramach funkcji zdefiniowanych w $ scope, thisustawiono na „zakres $, który obowiązuje gdzie / kiedy funkcja została wywołana”. Załóżmy, że mamy następujący kod HTML:

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

I ParentCtrl(wyłącznie) ma

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

Kliknięcie pierwszego linku pokaże to thisi $scopejest takie samo, ponieważ „ zakres obowiązujący w momencie wywołania funkcji ” to zakres powiązany z ParentCtrl.

Kliknięcie drugiego linku ujawni this i $scopeto nie to samo, gdyż „ zakres w efekcie, gdy funkcja została wywołana ” jest zakres związany z ChildCtrl. Więc tutaj thisjest ustawiony na ChildCtrl„s $scope. Wewnątrz metody $scopenadal ParentCtrlznajduje się zakres $.

Skrzypce

Staram się nie używać thiswewnątrz funkcji zdefiniowanej w $ scope, ponieważ staje się mylące, którego $ zakres ma wpływ, szczególnie biorąc pod uwagę, że wszystkie powtórzenia, włączanie, przełączanie i przełączanie mogą tworzyć własne zakresy potomne.


6
@tamakisquare, uważam, że pogrubiony tekst, który zacytowałeś, ma zastosowanie, gdy wywoływana jest funkcja konstruktora kontrolera - tj. kiedy kontroler jest tworzony = powiązany z zakresem $. Nie ma zastosowania później, gdy dowolny kod JavaScript wywoła metodę zdefiniowaną na obiekcie $ scope.
— Mark Rajcok

79
Zauważ, że teraz można wywołać funkcję addPane () bezpośrednio w szablonie, nazywając kontroler: „MyController as myctrl”, a następnie myctrl.addPane (). Zobacz docs.quarejs.org/guide/concepts#controller
— Christophe Augier

81
Zbyt duża złożoność.
— Inanc Gumus

11
To bardzo pouczająca odpowiedź, ale kiedy wróciłem z praktycznym problemem ( jak wywołać $ scope. $ Apply () w metodzie kontrolera zdefiniowanej za pomocą 'this' ), nie mogłem tego rozwiązać. Chociaż jest to wciąż przydatna odpowiedź, zaskakuje mnie „nieodłączna złożoność”.
— dumbledad

11
JavaScript - dużo lin [powiesić się].
— AlikElzin-kilaka

55

Powodem tego jest przypisanie „addPane” z powodu <pane>dyrektywy.

paneDyrektywa robi require: '^tabs', co stawia kontrolera wypustki obiekt z dyrektywy macierzystego do funkcji łącza.

addPanejest przypisany do, thisaby panefunkcja link mogła go zobaczyć. Następnie w panefunkcji link addPanejest tylko właściwościątabs kontrolera i jest to po prostu tabsControllerObject.addPane. Tak więc funkcja łączenia dyrektywy panelu może uzyskać dostęp do obiektu kontrolera kart, a zatem uzyskać dostęp do metody addPane.

Mam nadzieję, że moje wyjaśnienie jest wystarczająco jasne ... trudno to wyjaśnić.


3
Dziękuję za wyjaśnienie. Dokumenty sprawiają, że wydaje się, że kontroler jest tylko funkcją, która ustawia zakres. Dlaczego kontroler jest traktowany jak obiekt, jeśli cała akcja dzieje się w zakresie? Dlaczego nie po prostu przekazać zakresu nadrzędnego do funkcji łączenia? Edycja: Aby lepiej sformułować to pytanie, jeśli metody kontrolera i metody zakresu działają na tej samej strukturze danych (zakresie), dlaczego nie umieścić ich wszystkich w jednym miejscu?
— Alexei Boronine

Wygląda na to, że zakres nadrzędny nie jest przekazywany do funkcji INK z powodu chęci obsługi „komponentów wielokrotnego użytku, które nie powinny przypadkowo czytać ani modyfikować danych w zakresie nadrzędnym”. Ale jeśli dyrektywa naprawdę chce / musi odczytać lub zmodyfikować NIEKTÓRE SZCZEGÓŁOWE dane w zakresie nadrzędnym (podobnie jak dyrektywa „panel”), wymaga pewnego wysiłku: „wymaga” kontrolera, w którym znajduje się żądany zakres nadrzędny, a następnie zdefiniuj metoda na tym kontrolerze (użyj „this” nie $ scope), aby uzyskać dostęp do określonych danych. Ponieważ żądany zakres nadrzędny nie jest wstrzykiwany do funkcji INK, przypuszczam, że jest to jedyny sposób, aby to zrobić.
— Mark Rajcok,

1
Cześć, w rzeczywistości łatwiej jest zmodyfikować zakres dyrektywy. Możesz po prostu użyć funkcji link jsfiddle.net/TuNyj
— Andrew Joslin,

3
Dzięki @Andy za skrzypce. W twoim skrzypcach dyrektywa nie tworzy nowego zakresu, więc mogę zobaczyć, jak funkcja link może bezpośrednio uzyskać dostęp do zakresu kontrolera tutaj (ponieważ istnieje tylko jeden zakres). Dyrektywy tab i okienka używają zakresów izolujących (tzn. Tworzone są nowe zakresy potomne, które nie dziedziczą prototypowo z zakresu nadrzędnego). W przypadku zakresu izolowanego wydaje się, że zdefiniowanie metody na kontrolerze (użycie „tego”) jest jedynym sposobem, aby umożliwić innej dyrektywie uzyskanie (pośredniego) dostępu do innego (izolowanego) zakresu.
— Mark Rajcok

27

Właśnie przeczytałem całkiem interesujące wyjaśnienie różnicy między nimi oraz rosnącą preferencję dołączania modeli do kontrolera i aliasu kontrolera w celu powiązania modeli z widokiem. http://toddmotto.com/digging-into-angulars-controller-as-syntax/ to artykuł.
Nie wspomina o tym, ale jeśli definiujesz dyrektywy, jeśli musisz coś współdzielić między wieloma dyrektywami i nie chcesz usługi (istnieją uzasadnione przypadki, gdy usługi są kłopotliwe), to dołącz dane do kontrolera dyrektywy nadrzędnej.

$scopeUsługa zapewnia wiele przydatnych rzeczy, $watchjest najbardziej oczywiste, ale jeśli wszystko czego potrzebujesz do danych wiążą się z widzenia, używając zwykłego kontrolera i „kontroler jako” w szablonie jest w porządku i bez wątpienia korzystne.


20

Polecam przeczytać następujący post: AngularJS: „Controller as” lub „$ scope”?

Bardzo dobrze opisuje zalety używania „Controller as” do eksponowania zmiennych w stosunku do „$ scope”.

Wiem, że pytałeś konkretnie o metody, a nie zmienne, ale myślę, że lepiej trzymać się jednej techniki i być z nią zgodnym.

Tak więc, moim zdaniem, ze względu na problem zmiennych omawiany w poście, lepiej jest po prostu użyć techniki „Controller as” i zastosować ją również do metod.


16

W tym kursie ( https://www.codeschool.com/courses/shaping-up-with-angular-js ) wyjaśniają, jak używać „tego” i wielu innych rzeczy.

Jeśli dodasz metodę do kontrolera za pomocą „tej” metody, musisz wywołać ją w widoku z nazwą kontrolera „kropka” swoją właściwością lub metodą.

Na przykład używając kontrolera w widoku, możesz mieć taki kod:

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

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

    </div>

6
Po przejściu kursu od razu pomyliłem się przy użyciu kodu $scope, więc dziękuję za jego wzmiankę.
— Matt Montag,

16
Ten kurs w ogóle nie wspomina o zakresie $, po prostu go używa, asa thiswięc jak może pomóc wyjaśnić różnicę?
— dumbledad

10
Mój pierwszy kontakt z Angularem odbył się na wspomnianym kursie i jak $scopenigdy nie wspomniano, nauczyłem się korzystać tylko thisz kontrolerów. Problem polega na tym, że kiedy zaczynasz obsługiwać obietnice w swoim kontrolerze, masz wiele problemów z referencjami thisi musisz zacząć robić takie rzeczy, jak var me = thisodwoływanie się do modelu thisz poziomu funkcji zwrotu obietnicy. Z tego powodu nadal jestem bardzo zdezorientowany, której metody powinienem użyć $scopelub this.
— Bruno Finger,

@BrunoFinger Niestety, trzeba var me = thisalbo .bind(this)gdy robisz obietnice lub inne zamknięcia ciężkich rzeczy. Nie ma to nic wspólnego z Angularem.
— Dzmitry Lazerka

1
Ważne jest, aby wiedzieć, że ng-controller="MyCtrl as MC"jest to równoważne z umieszczeniem $scope.MC = thissamego kontrolera - definiuje instancję (this) MyCtrl w zakresie zastosowania w szablonie przez{{ MC.foo }}
— William B

3

Poprzednie wersje Angulara (wcześniej niż 1.0 RC) pozwalały na stosowanie tego zamiennie z metodą $ scope, ale już tak nie jest. Wewnątrz metod zdefiniowanych w zakresie ten i $ scope są wymienne (kąt ustawia to na $ scope), ale nie inaczej wewnątrz konstruktora kontrolera.

Aby przywrócić to zachowanie (czy ktoś wie, dlaczego zostało zmienione?) Możesz dodać:

return angular.extend($scope, this);

na końcu funkcji kontrolera (pod warunkiem, że do tej funkcji kontrolera został wprowadzony zakres $).

Daje to przyjemny efekt dostępu do zakresu nadrzędnego za pośrednictwem obiektu kontrolera, z którym można uzyskać dziecko require: '^myParentDirective'


7
Ten artykuł zawiera dobre wyjaśnienie, dlaczego ten i $ zakres są różne.
— Robert Martin

1

$ scope ma inne „to” niż kontroler „to”. Jeśli więc umieścisz console.log (this) w kontrolerze, otrzymasz obiekt (kontroler), a this.addPane () doda metodę AddPane do obiektu kontrolera. Ale zakres $ ma inny zakres i wszystkie metody w jego zakresie muszą być dostępne za pomocą $ scope.methodName (). this.methodName()wewnątrz kontrolera oznacza dodanie metosu do obiektu kontrolera. $scope.functionName()jest w HTML i wewnątrz

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

Wklej ten kod w edytorze i otwórz konsolę, aby zobaczyć ...

 <!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>
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.