'this' vs $ scope nei controller AngularJS


1027

Nella sezione "Crea componenti" della homepage di AngularJS , c'è questo esempio:

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

Si noti come selectviene aggiunto il metodo $scope, ma addPaneviene aggiunto il metodo this. Se lo cambio in $scope.addPane, il codice si rompe.

La documentazione afferma che in realtà c'è una differenza, ma non menziona quale sia la differenza:

Le versioni precedenti di Angular (pre 1.0 RC) ti consentivano di utilizzare in modo thisintercambiabile con il $scopemetodo, ma non è più così. All'interno dei metodi definiti nell'ambito thise $scopesono intercambiabili (angolari impostati thissu $scope), ma non all'interno del costruttore del controller.

Come funziona thise $scopefunziona nei controller AngularJS?


Lo trovo confuso anche. Quando una vista specifica un controller (ad esempio, ng-controller = '...'), l'ambito $ associato a quel controller sembra venire con esso, poiché la vista può accedere alle proprietà $ scope. Ma quando una direttiva "richiede un altro controller (e quindi lo utilizza nella sua funzione di collegamento), l'ambito $ associato a quell'altro controller non viene con esso?
Mark Rajcok,

La citazione confusa su "Versioni precedenti ..." è stata rimossa ormai? Quindi forse l'aggiornamento sarebbe in atto?
Dmitri Zaitsev,

Per i test unitari, se si utilizza "this" anziché "$ scope", non è possibile iniettare il controller con un ambito beffardo e quindi non è possibile eseguire test unitari. Non credo sia una buona pratica usare "questo".
abentan,

Risposte:


999

"Come funziona thise $scopefunziona nei controller AngularJS?"

Risposta breve :

  • this
    • Quando viene chiamata la funzione di costruzione del controller, thisè il controller.
    • Quando $scopeviene chiamata una funzione definita su un oggetto, thisè "l'ambito attivo quando è stata chiamata la funzione". Questo può (o no!) Essere quello su $scopecui è definita la funzione. Quindi, all'interno della funzione,this e $scopepotrebbe non essere la stessa.
  • $scope
    • Ogni controller ha un $scopeoggetto associato .
    • Una funzione controller (costruttore) è responsabile dell'impostazione delle proprietà del modello e delle funzioni / comportamento sui relativi associati $scope.
    • Solo i metodi definiti su questo $scope oggetto (e gli oggetti ambito genitore, se è in gioco l'eredità prototipica) sono accessibili dalla vista HTML /. Ad esempio, da ng-click, filtri, ecc.

Risposta lunga :

Una funzione controller è una funzione di costruzione JavaScript. Quando viene eseguita la funzione di costruzione (ad esempio, quando viene caricata una vista), this(ovvero, il "contesto della funzione") viene impostato sull'oggetto controller. Quindi nella funzione di costruzione del controller "tabs", quando viene creata la funzione addPane

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

viene creato sull'oggetto controller, non su $ scope. Le viste non possono vedere la funzione addPane: hanno accesso solo alle funzioni definite su $ scope. In altre parole, in HTML, questo non funzionerà:

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

Dopo l'esecuzione della funzione di costruzione del controller "tabs", si ottiene quanto segue:

dopo la funzione di costruzione del controller delle schede

La linea nera tratteggiata indica l'eredità prototipale - un ambito isolato eredita prototipicamente Scope . (Non eredita prototipicamente dall'ambito in vigore in cui la direttiva è stata rilevata nell'HTML.)

Ora, la funzione di collegamento della direttiva pane vuole comunicare con la direttiva tabs (il che significa davvero che deve influenzare in qualche modo $ scope $ scope). È possibile utilizzare gli eventi, ma un altro meccanismo consiste nell'avere la direttiva riquadro requirenel controller delle schede. (Sembra che non ci sia alcun meccanismo per la direttiva riquadro requirenelle schede $ scope.)

Quindi, questo pone la domanda: se abbiamo solo accesso al controller delle schede, come possiamo accedere alle schede isolare $ scope (che è ciò che vogliamo davvero)?

Bene, la linea tratteggiata rossa è la risposta. L '"ambito" della funzione addPane () (mi riferisco all'ambito / alle chiusure della funzione JavaScript qui) consente alla funzione di accedere alle schede isolare $ scope. Vale a dire, addPane () ha accesso alle "schede IsolateScope" nel diagramma sopra a causa di una chiusura creata quando è stato definito addPane (). (Se invece definissimo addPane () sull'oggetto $ scope tabs, la direttiva pane non avrebbe accesso a questa funzione, e quindi non avrebbe modo di comunicare con le schede $ scope.)

Per rispondere all'altra parte della tua domanda how does $scope work in controllers?:

All'interno delle funzioni definite su $ scope, thisè impostato su "l'ambito $ in effetti dove / quando è stata chiamata la funzione". Supponiamo di avere il seguente 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>

E il ParentCtrl(Solely) ha

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

Fare clic sul primo collegamento mostrerà che thise $scopesono gli stessi, poiché " l'ambito ha effetto quando è stata chiamata la funzione " è la portata associato al ParentCtrl.

Facendo clic sul secondo collegamento si rivelerà thise non$scope sono gli stessi, poiché " l'ambito in vigore quando è stata chiamata la funzione " è l'ambito associato al . Quindi qui, è impostato su 's . All'interno del metodo, c'è ancora l' ambito $ s.ChildCtrlthisChildCtrl$scope$scopeParentCtrl

Violino

Cerco di non usare thisall'interno di una funzione definita su $ scope, poiché diventa confuso quale $ scope sia interessato, specialmente considerando che ng-repeat, ng-include, ng-switch e direttive possono tutti creare i propri ambiti figlio.


6
@tamakisquare, credo che il testo in grassetto che hai citato si applichi quando viene chiamata la funzione di costruzione del controller, ovvero quando viene creato il controller = associato a un $ scope. Non si applica in seguito, quando un codice JavaScript arbitrario chiama un metodo definito su un oggetto $ scope.
Mark Rajcok,

79
Si noti che ora è possibile chiamare la funzione addPane () direttamente nel modello nominando il controller: "MyController come myctrl" e quindi myctrl.addPane (). Vedi docs.angularjs.org/guide/concepts#controller
Christophe Augier,

81
Troppa complessità intrinseca.
Inanc Gumus,

11
Questa è una risposta molto istruttiva, ma quando sono tornato con un problema pratico ( come invocare $ scope. $ Apply () in un metodo del controller definito usando 'this' ) non sono riuscito a risolverlo. Quindi, sebbene questa sia ancora una risposta utile, trovo sconcertante la "complessità intrinseca".
stupido il

11
Javascript: un sacco di corda [da appendere].
AlikElzin-Kilaka

55

Il motivo 'addPane' è assegnato a questo è a causa della <pane>direttiva.

La panedirettiva fa require: '^tabs', che mette l'oggetto controller di schede da una direttiva padre, nella funzione link.

addPaneè assegnato in thismodo che la panefunzione di collegamento possa vederlo. Quindi, nella panefunzione di collegamento, addPaneè solo una proprietà del tabscontroller ed è solo tabsControllerObject.addPane. Quindi la funzione di collegamento della direttiva pane può accedere all'oggetto controller schede e quindi accedere al metodo addPane.

Spero che la mia spiegazione sia abbastanza chiara ... è difficile da spiegare.


3
Grazie per la spiegazione. I documenti sembrano far sembrare che il controller sia solo una funzione che imposta l'ambito. Perché il controller viene trattato come un oggetto se tutte le azioni si verificano nell'ambito? Perché non passare semplicemente l'ambito padre nella funzione di collegamento? Modifica: per formulare meglio questa domanda, se i metodi del controller e i metodi dell'ambito operano entrambi sulla stessa struttura di dati (l'ambito), perché non metterli tutti in un unico posto?
Alexei Boronine,

Sembra che l'ambito genitore non sia passato alla funzione lnk a causa del desiderio di supportare "componenti riutilizzabili, che non dovrebbero leggere o modificare accidentalmente i dati nell'ambito genitore". Ma se una direttiva vuole davvero / ha bisogno di leggere o modificare alcuni dati SPECIFICI nell'ambito del genitore (come fa la direttiva "riquadro"), richiede un certo sforzo: "richiede" il controller in cui si trova l'ambito genitore desiderato, quindi definisci un metodo su quel controller (usa 'this' non $ scope) per accedere a dati specifici. Poiché l'ambito genitore desiderato non viene iniettato nella funzione lnk, suppongo che questo sia l'unico modo per farlo.
Mark Rajcok,

1
Ehi mark, in realtà è più facile modificare il campo di applicazione della direttiva. Puoi semplicemente usare la funzione di collegamento jsfiddle.net/TuNyj
Andrew Joslin

3
Grazie @Andy per il violino. Nel tuo violino, la direttiva non sta creando un nuovo ambito, quindi posso vedere come la funzione di collegamento può accedere direttamente all'ambito del controller qui (poiché esiste un solo ambito). Le direttive di schede e riquadro utilizzano ambiti isolati (ovvero, vengono creati nuovi ambiti figlio che non ereditano prototipicamente dall'ambito padre). Per il caso dell'ambito isolato, sembra che la definizione di un metodo su un controller (usando "this") sia l'unico modo per consentire a un'altra direttiva di ottenere l'accesso (indiretto) all'altro ambito (isolato).
Mark Rajcok,

27

Ho appena letto una spiegazione piuttosto interessante sulla differenza tra i due e una crescente preferenza per collegare i modelli al controller e alias il controller per associare i modelli alla vista. http://toddmotto.com/digging-into-angulars-controller-as-syntax/ è l'articolo.
Non lo menziona ma quando si definiscono le direttive, se è necessario condividere qualcosa tra più direttive e non si desidera un servizio (ci sono casi legittimi in cui i servizi sono una seccatura) quindi allegare i dati al controllore della direttiva principale.

Il $scopeservizio fornisce molte cose utili, $watchessendo il più ovvio, ma se tutto ciò che serve per associare i dati alla vista, usare il controller semplice e "controller as" nel modello va bene e probabilmente preferibile.


20

Ti consiglio di leggere il seguente post: AngularJS: "Controller as" o "$ scope"?

Descrive molto bene i vantaggi dell'utilizzo di "Controller as" per esporre variabili su "$ scope".

So che hai chiesto in modo specifico i metodi e non le variabili, ma penso che sia meglio attenersi a una tecnica ed essere coerenti con essa.

Quindi, a mio parere, a causa del problema delle variabili discusso nel post, è meglio usare solo la tecnica "Controller as" e applicarla anche ai metodi.


16

In questo corso ( https://www.codeschool.com/courses/shaping-up-with-angular-js ) spiegano come usare "questo" e molte altre cose.

Se si aggiunge il metodo al controller tramite il metodo "this", è necessario chiamarlo nella vista con il nome del controller "dot" come proprietà o metodo.

Ad esempio, usando il controller nella vista potresti avere un codice come questo:

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

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

    </div>

6
Dopo aver seguito il corso, sono stato immediatamente confuso dal codice usando $scope, quindi grazie per averlo menzionato.
Matt Montag,

16
Quel corso non menziona affatto $ scope, lo usano solo ase thisquindi come può aiutare a spiegare la differenza?
Silente,

10
Il mio primo tocco con Angular è stato dal corso menzionato e, come $scopenon è mai stato riferito, ho imparato a usare solo thisnei controller. Il problema è che quando inizi a gestire le promesse nel tuo controller, hai molti problemi di riferimento thise devi iniziare a fare cose come var me = thisfare riferimento al modello thisdall'interno della funzione di ritorno della promessa. Quindi, a causa di ciò, sono ancora molto confuso su quale metodo dovrei usare, $scopeo this.
Bruno Finger,

@BrunoFinger Sfortunatamente, avrai bisogno var me = thiso .bind(this)ogni volta che farai promesse o altre cose pesanti di chiusura. Non ha nulla a che fare con Angular.
Dzmitry Lazerka,

1
L'importante è sapere che ng-controller="MyCtrl as MC"equivale a inserire $scope.MC = thisil controller stesso: definisce un'istanza (questa) di MyCtrl sull'ambito da utilizzare nel modello tramite{{ MC.foo }}
William B,

3

Le versioni precedenti di Angular (pre 1.0 RC) ti consentivano di usarlo in modo intercambiabile con il metodo $ scope, ma non è più così. All'interno dei metodi definiti nell'ambito questo e $ scope sono intercambiabili (angolare lo imposta su $ scope), ma non altrimenti all'interno del costruttore del controller.

Per ripristinare questo comportamento (qualcuno sa perché è stato modificato?) È possibile aggiungere:

return angular.extend($scope, this);

al termine della funzione del controller (a condizione che $ scope sia stato iniettato in questa funzione del controller).

Questo ha un buon effetto di avere accesso all'ambito genitore tramite un oggetto controller con cui puoi entrare in gioco require: '^myParentDirective'


7
Questo articolo fornisce una buona spiegazione del perché questo e $ scope sono diversi.
Robert Martin,

1

$ scope ha un 'questo' diverso dal controller 'questo'. Quindi, se si inserisce un console.log (questo) all'interno del controller, si ottiene un oggetto (controller) e this.addPane () aggiunge il metodo addPane all'oggetto controller. Ma l'ambito $ ha un ambito diverso e tutti i metodi nel suo ambito devono essere acceduti da $ scope.methodName (). this.methodName()controller interno significa aggiungere methos all'interno del controller oggetto. $scope.functionName()è in HTML e dentro

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

Incolla questo codice nel tuo editor e apri la console per vedere ...

 <!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>
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.