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

Aleksiej Boronina
źródło
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.

Mark Rajcok
źródło
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ć.

Andrew Joslin
źródło
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.

Derek
źródło
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.

Liran Brimer
źródło
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>
Sandro
źródło
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'

Kamil Szot
źródło
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>
Aniket Jha
źródło