web-dev-qa-db-fra.com

'this' vs $ scope dans les contrôleurs AngularJS

Dans la section "Create Components" de la page d'accueil d'AngularJS , vous trouverez cet exemple:

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

Notez comment la méthode select est ajoutée à $scope, alors que la méthode addPane est ajoutée à this. Si je le change en $scope.addPane, le code est cassé.

La documentation indique qu'il y a effectivement une différence, mais ne mentionne pas la différence:

Les versions précédentes de Angular (avant la version 1.0 RC) vous permettaient d'utiliser this de manière interchangeable avec la méthode $scope, mais ce n'est plus le cas. À l'intérieur des méthodes définies dans les champs this et $scope sont interchangeables (les ensembles angulaires this à $scope), mais pas à l'intérieur du constructeur de votre contrôleur.

Comment this et $scope fonctionnent-ils dans les contrôleurs AngularJS?

987
Alexei Boronine

"Comment this et $scope fonctionnent-ils dans les contrôleurs AngularJS?"

Réponse courte:

  • this
    • Lorsque la fonction constructeur du contrôleur est appelée, this est le contrôleur.
    • Lorsqu'une fonction définie sur un objet $scope est appelée, this est "l'étendue en vigueur lorsque la fonction a été appelée". Cela peut (ou non) être le $scope sur lequel la fonction est définie. Donc, dans la fonction, this et $scope peuvent ne pas être identiques.
  • $scope
    • Chaque contrôleur a un objet $scope associé.
    • Une fonction contrôleur (constructeur) est responsable de la définition des propriétés du modèle et des fonctions/comportements sur son $scope associé.
    • Seules les méthodes définies sur cet objet $scope (et les objets de portée parents, si un héritage prototype est en cours de lecture) sont accessibles à partir de la vue HTML. Par exemple, à partir de ng-click, filtres, etc.

Longue réponse:

Une fonction de contrôleur est une fonction constructeur JavaScript. Lorsque la fonction constructeur s'exécute (par exemple, lors du chargement d'une vue), this (c'est-à-dire le "contexte de la fonction") est défini sur l'objet contrôleur. Donc, dans la fonction constructeur du contrôleur "onglets", lorsque la fonction addPane est créée

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

il est créé sur l'objet contrôleur, pas sur $ scope. Les vues ne peuvent pas voir la fonction addPane - elles ont uniquement accès aux fonctions définies sur $ scope. En d'autres termes, cela ne fonctionnera pas dans le code HTML:

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

Après que la fonction constructeur du contrôleur "tabs" soit exécutée, voici ce qui suit:

after tabs controller constructor function

La ligne noire en pointillés indique l'héritage du prototype - une portée isolée hérite de manière prototype de Scope . (Il n'hérite pas de manière prototypique de la portée en vigueur où la directive a été rencontrée dans le code HTML.)

Maintenant, la fonction link de la directive de volet veut communiquer avec la directive tabs (ce qui signifie vraiment qu'elle doit affecter les isolements de tabulation $ scope d'une manière ou d'une autre). Les événements peuvent être utilisés, mais un autre mécanisme consiste à utiliser la directive de volet require comme contrôleur d'onglets. (Il semble n'y avoir aucun mécanisme pour la directive de volet pour require les onglets $ scope.)

Cela pose donc la question suivante: si nous n’avons accès qu’au contrôleur des onglets, comment pouvons-nous avoir accès aux onglets isolate $ scope (ce que nous voulons vraiment)?

Eh bien, la ligne pointillée rouge est la réponse. La "portée" de la fonction addPane () (je fais ici référence à la fonction portée/fermetures de JavaScript) donne à la fonction l'accès aux onglets isolate $ scope. Par exemple, addPane () a accès aux "onglets IsolateScope" dans le diagramme ci-dessus en raison d'une fermeture créée lors de la définition de addPane (). (Si nous définissions plutôt addPane () sur l'objet tabs $ scope, la directive du volet n'aurait pas accès à cette fonction et n'aurait donc aucun moyen de communiquer avec les tabs $ scope.)

Pour répondre à l’autre partie de votre question: how does $scope work in controllers?:

Dans les fonctions définies sur $ scope, this est défini sur "le $ scope en vigueur où/quand la fonction a été appelée". Supposons que nous ayons le code HTML suivant:

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

Et la ParentCtrl (uniquement) a

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

En cliquant sur le premier lien, vous verrez que this et $scope sont identiques, puisque "la portée en vigueur lors de l'appel de la fonction _" est la portée associée à la ParentCtrl.

En cliquant sur le deuxième lien, vous découvrirez que this et $scope sont pas identiques, puisque "la portée en vigueur lors de l'appel de la fonction" est la portée associée à la ChildCtrl. Donc, ici, this est défini sur ChildCtrl 's $scope. Dans la méthode, $scope est toujours la portée de la variable ParentCtrl.

Fiddle

J'essaie de ne pas utiliser this à l'intérieur d'une fonction définie sur $ scope, car cela crée de la confusion quant à la portée de $ scope, en particulier en considérant que ng-repeat, ng-include, ng-switch et des directives peuvent tous créer leurs propres portées enfants. .

971
Mark Rajcok

La raison pour laquelle 'addPane' est attribué à cela est due à la directive <pane>.

La directive pane fait require: '^tabs', ce qui place l'objet contrôleur des tabulations d'une directive parent dans la fonction link.

addPane est affecté à this afin que la fonction de lien pane puisse le voir. Ensuite, dans la fonction de lien pane, addPane est simplement une propriété du contrôleur tabs et il ne s'agit que de tabsControllerObject.addPane. Ainsi, la fonction de liaison de la directive de volet peut accéder à l'objet contrôleur tabs et donc à la méthode addPane.

J'espère que mon explication est assez claire… c'est un peu difficile à expliquer.

53
Andrew Joslin

Je viens de lire une explication assez intéressante sur la différence entre les deux, et une préférence croissante pour attacher des modèles au contrôleur et alias le contrôleur pour lier les modèles à la vue. http://toddmotto.com/digging-into-angulars-controller-as-syntax/ est l'article. Il n'en parle pas, mais lors de la définition des directives, si vous avez besoin de partager quelque chose entre plusieurs directives et que vous ne voulez pas de service (il existe des cas légitimes où les services sont compliqués), attachez les données au contrôleur de la directive mère. Le service $ scope fournit beaucoup de choses utiles, $ watch étant le plus évident, mais si vous avez juste besoin de lier des données à la vue, utiliser le contrôleur brut et le "contrôleur en tant que" dans le modèle est correct et peut-être préférable.

26
Derek

Je vous recommande de lire le post suivant: AngularJS: "Controller as" ou "$ scope"?

Il décrit très bien les avantages d'utiliser "Controller as" pour exposer des variables sur "$ scope".

Je sais que vous avez spécifiquement posé des questions sur les méthodes et non sur les variables, mais je pense qu'il est préférable de s'en tenir à une technique et d'être cohérent avec elle. 

Donc, à mon avis, en raison du problème des variables abordé dans l'article, il est préférable d'utiliser simplement la technique du "contrôleur en tant que" et de l'appliquer également aux méthodes.

18
Liran Brimer

Dans ce cours ( https://www.codeschool.com/courses/shaping-up-with-angular-js ), ils expliquent comment utiliser "ceci" et beaucoup d'autres choses.

Si vous ajoutez une méthode au contrôleur via "cette" méthode, vous devez l'appeler dans la vue avec le nom du contrôleur "point" votre propriété ou méthode.

Par exemple, en utilisant votre contrôleur dans la vue, vous pouvez avoir un code comme celui-ci:

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

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

    </div>
15
Sandro

Les versions précédentes de Angular (avant la version 1.0 RC) vous permettaient de l'utiliser de manière interchangeable avec la méthode $ scope, mais ce n'est plus le Cas. À l'intérieur des méthodes définies sur les portées this et $ scope sont interchangeable (angular définit ceci à $ scope), mais pas autrement à l'intérieur de votre constructeur de contrôleur.

Pour ramener ce comportement (quelqu'un sait-il pourquoi a-t-il été modifié?), Vous pouvez ajouter:

return angular.extend($scope, this);

à la fin de votre fonction de contrôleur (à condition que $ scope ait été injecté à cette fonction de contrôleur).

Cela a un effet positif d'avoir accès à la portée parent via un objet contrôleur que vous pouvez obtenir dans l'enfant avec require: '^myParentDirective'

3
Kamil Szot

Ainsi, $ scope a un 'ceci' différent du contrôleur 'this'. Ainsi, si vous placez un console.log (this) dans le contrôleur, il vous donne un objet (contrôleur) et this.addPane () ajoute la méthode addPane à l’objet du contrôleur. Mais $ scope a une portée différente et toutes les méthodes de sa portée doivent être accédées par $ scope.methodName () .this.methodName() à l'intérieur du contrôleur signifie d'ajouter des méthos à l'intérieur de l'objet contrôleur .$scope.functionName() est en HTML et à l'intérieur 

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

Collez ce code dans votre éditeur et ouvrez la console pour voir ...

 <!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>
0
Aniket Jha