'this' vs $ scope nos controladores AngularJS

1027

Na seção "Criar componentes" da página inicial do AngularJS , existe este exemplo:

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

Observe como o selectmétodo é adicionado $scope, mas o addPanemétodo é adicionado this. Se eu mudar para $scope.addPane, o código será quebrado.

A documentação diz que de fato há uma diferença, mas não menciona qual é a diferença:

As versões anteriores do Angular (pré 1.0 RC) permitiam o uso thisintercambiável com o $scopemétodo, mas esse não é mais o caso. Dentro dos métodos definidos no escopo thise $scopesão intercambiáveis ​​(conjuntos angulares thispara $scope), mas não dentro do construtor do controlador.

Como funciona thise $scopefunciona nos controladores AngularJS?

Alexei Boronine
fonte
Acho isso confuso também. Quando uma visão especifica um controlador (por exemplo, ng-controller = '...'), o escopo $ associado a esse controlador parece acompanhá-lo, porque a visão pode acessar as propriedades de $ scope. Mas quando uma diretiva 'exige outro controlador (e o usa em sua função de vinculação), o escopo $ associado a esse outro controlador não o acompanha?
Mark Rajcok
Essa citação confusa sobre "Versões anteriores ..." foi removida agora? Então talvez a atualização esteja no lugar?
Dmitri Zaitsev
Para teste de unidade, se você usar 'this' em vez de '$ scope', não poderá injetar no controlador um escopo simulado e, portanto, não poderá fazer testes de unidade. Não acho que seja uma boa prática usar 'this'.
abentan

Respostas:

999

"Como funciona thise $scopefunciona nos controladores AngularJS?"

Resposta curta :

  • this
    • Quando a função construtora do controlador é chamada, thisé o controlador.
    • Quando uma função definida em um $scopeobjeto é chamada, thisé o "escopo em vigor quando a função foi chamada". Isso pode (ou não!) Ser o $scopeque a função está definida. Portanto, dentro da função, thise $scopepode não ser o mesmo.
  • $scope
    • Todo controlador tem um $scopeobjeto associado .
    • Uma função do controlador (construtor) é responsável por definir propriedades do modelo e funções / comportamento em seus associados $scope.
    • Somente métodos definidos nesse $scopeobjeto (e objetos de escopo pai, se houver herança prototípica em jogo) são acessíveis a partir do HTML / view. Por exemplo, de ng-click, filtros, etc.

Resposta longa :

Uma função do controlador é uma função do construtor JavaScript. Quando a função construtora é executada (por exemplo, quando uma exibição é carregada), this(ou seja, o "contexto da função") é definido como o objeto do controlador. Portanto, na função construtora do controlador "tabs", quando a função addPane é criada

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

ele é criado no objeto do controlador, não no $ scope. As visualizações não podem ver a função addPane - elas só têm acesso às funções definidas no $ scope. Em outras palavras, no HTML, isso não funcionará:

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

Após a função construtora do controlador "tabs" ser executada, temos o seguinte:

após a função de construtor do controlador de guias

A linha preta tracejada indica herança prototípica - um escopo isolado herda prototipicamente do Scope . (Não herda prototipicamente do escopo em vigor onde a diretiva foi encontrada no HTML.)

Agora, a função de link da diretiva de painel deseja se comunicar com a diretiva de guias (o que realmente significa que precisa afetar as guias de isolar o escopo $ de alguma forma). Eventos podem ser usados, mas outro mecanismo é ter a diretiva de painel requireno controlador de guias. (Parece não haver mecanismo para a diretiva de painel requirenas guias $ scope.)

Então, isso levanta a questão: se apenas temos acesso ao controlador de guias, como obtemos acesso às guias isolamos $ scope (que é o que realmente queremos)?

Bem, a linha pontilhada vermelha é a resposta. O "escopo" da função addPane () (estou me referindo ao escopo / fechamento da função do JavaScript aqui) fornece à função acesso às guias que isolam $ scope. Ou seja, addPane () tem acesso às "guias IsolateScope" no diagrama acima devido a um fechamento criado quando addPane () foi definido. (Se definíssemos addPane () no objeto tabs $ scope, a diretiva painel não teria acesso a essa função e, portanto, não teria como se comunicar com o tabs $ scope.

Para responder à outra parte da sua pergunta how does $scope work in controllers?:

Nas funções definidas em $ scope, thisé definido como "o $ scope em vigor onde / quando a função foi chamada". Suponha que tenhamos o seguinte 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 o ParentCtrl(Solely) tem

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

Clicar no primeiro link mostrará isso thise $scopeé o mesmo, já que " o escopo em vigor quando a função foi chamada " é o escopo associado ao ParentCtrl.

Ao clicar no segundo link irá revelar thise $scopesão não o mesmo, uma vez que " o escopo em vigor quando a função foi chamada " é o escopo associado ao ChildCtrl. Então, aqui, thisestá definido como ChildCtrl's $scope. Dentro do método, $scopeainda está o ParentCtrlescopo de $.

Violino

Tento não usar thisdentro de uma função definida no $ scope, pois fica confuso qual $ scope está sendo afetado, especialmente considerando que as diretivas ng-repeat, ng-include, ng-switch e diretivas podem criar seus próprios escopos filhos.

Mark Rajcok
fonte
6
@tamakisquare, acredito que o texto em negrito que você citou se aplica a quando a função construtora do controlador é chamada - ou seja, quando o controlador é criado = associado a um escopo $. Ele não se aplica posteriormente, quando o código JavaScript arbitrário chama um método definido em um objeto $ scope.
Mark Rajcok
79
Observe que agora é possível chamar a função addPane () diretamente no modelo, nomeando o controlador: "MyController como myctrl" e depois myctrl.addPane (). Veja docs.angularjs.org/guide/concepts#controller
Christophe Augier
81
Muita complexidade inerente.
Inanc Gumus
11
Esta é uma resposta muito informativa, mas quando voltei com um problema prático ( como chamar $ scope. $ Apply () em um método de controlador definido usando 'this' ), não consegui resolver o problema. Portanto, embora essa ainda seja uma resposta útil, estou achando a "complexidade inerente" desconcertante.
dumbledad
11
Javascript - muita corda [para se enforcar].
AlikElzin-kilaka
55

A razão pela qual 'addPane' é atribuída a isso é por causa da <pane>diretiva.

A panediretiva does require: '^tabs', que coloca o objeto do controlador de guias de uma diretiva pai, na função link.

addPaneé atribuído para thisque a panefunção de link possa vê-lo. Então, na panefunção de link, addPaneé apenas uma propriedade do tabscontrolador e são apenas tabsControllerObject.addPane. Portanto, a função de vinculação da diretiva do painel pode acessar o objeto do controlador de guias e, portanto, acessar o método addPane.

Espero que minha explicação seja clara o suficiente .. é meio difícil de explicar.

Andrew Joslin
fonte
3
Obrigada pelo esclarecimento. Os documentos fazem parecer que o controlador é apenas uma função que configura o escopo. Por que o controlador é tratado como um objeto se toda a ação acontece no escopo? Por que não apenas passar o escopo pai para a função de vinculação? Editar: Para melhor formular esta pergunta, se os métodos do controlador e do escopo operam na mesma estrutura de dados (o escopo), por que não colocá-los todos em um só lugar?
Alexei Boronine
Parece que o escopo pai não foi passado para a função lnk devido ao desejo de oferecer suporte a "componentes reutilizáveis, que não devem ler ou modificar dados acidentalmente no escopo pai". Mas se uma diretiva realmente deseja / precisa ler ou modificar ALGUNS dados ESPECÍFICOS no escopo pai (como a diretiva 'painel'), isso exige algum esforço: 'requeira' o controlador onde está o escopo pai desejado e, em seguida, defina um método nesse controlador (use 'this' e não $ scope) para acessar dados específicos. Como o escopo pai desejado não é injetado na função lnk, suponho que essa seja a única maneira de fazê-lo.
precisa saber é o seguinte
1
Ei, marca, é realmente mais fácil modificar o escopo da diretiva. Você pode simplesmente usar a função de link jsfiddle.net/TuNyj
Andrew Joslin
3
Obrigado @ Andy pelo violino. No seu caso, a diretiva não está criando um novo escopo; portanto, posso ver como a função de link pode acessar diretamente o escopo do controlador aqui (já que existe apenas um escopo). As diretivas de guias e painéis usam escopos isolados (ou seja, novos escopos filhos são criados que não herdam prototipicamente do escopo pai). Para o caso de escopo isolado, parece que definir um método em um controlador (usando 'this') é a única maneira de permitir que outra diretiva obtenha acesso (indireto) ao outro escopo (isolado).
Mark Rajcok
27

Acabei de ler uma explicação bastante interessante sobre a diferença entre os dois, e uma preferência crescente por anexar modelos ao controlador e, aliás, o controlador para vincular modelos à visualização. http://toddmotto.com/digging-into-angulars-controller-as-syntax/ é o artigo.
Ele não menciona isso, mas ao definir diretivas, se você precisar compartilhar algo entre várias diretivas e não desejar um serviço (há casos legítimos em que os serviços são um aborrecimento), anexe os dados ao controlador da diretiva pai.

O $scopeserviço fornece muitas coisas úteis, $watchsendo as mais óbvias, mas se tudo o que você precisa para vincular dados à exibição, usar o controlador simples e 'controller as' no modelo é bom e sem dúvida preferível.

Derek
fonte
20

Eu recomendo que você leia a seguinte postagem: AngularJS: "Controller as" ou "$ scope"?

Ele descreve muito bem as vantagens de usar "Controller as" para expor variáveis ​​sobre "$ scope".

Sei que você perguntou especificamente sobre métodos e não variáveis, mas acho que é melhor seguir uma técnica e ser consistente com ela.

Então, na minha opinião, devido ao problema de variáveis ​​discutido no post, é melhor usar a técnica "Controller as" e também aplicá-la aos métodos.

Liran Brimer
fonte
16

Neste curso ( https://www.codeschool.com/courses/shaping-up-with-angular-js ), eles explicam como usar "this" e muitas outras coisas.

Se você adicionar método ao controlador através do método "this", precisará chamá-lo na exibição com o nome do controlador "dot" como sua propriedade ou método.

Por exemplo, usando seu controlador na visualização, você pode ter um código como este:

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

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

    </div>
Sandro
fonte
6
Após o curso, fiquei imediatamente confuso com o uso do código $scope, então obrigado por mencioná-lo.
Matt Montag
16
Esse curso não menciona o escopo $, eles apenas usam ase, thisportanto, como ele pode ajudar a explicar a diferença?
dumbledad
10
Meu primeiro contato com o Angular foi no curso mencionado e, como $scopenunca foi mencionado, aprendi a usar apenas thisnos controladores. O problema é que, quando você começa a lidar com promessas em seu controlador, tem muitos problemas de referência thise precisa começar a fazer coisas como var me = thisreferenciar o modelo a thispartir da função de retorno de promessa. Por isso, ainda estou muito confuso sobre qual método devo usar $scopeou this.
de Bruno Dedo
@BrunoFinger Infelizmente, você precisará var me = thisou .bind(this)sempre que fizer Promessas, ou outras coisas pesadas. Não tem nada a ver com angular.
Dzmitry Lazerka
1
O importante é saber que isso ng-controller="MyCtrl as MC"equivale a colocar $scope.MC = thisno próprio controlador - ele define uma instância (esta) do MyCtrl no escopo para uso no modelo via #{{ MC.foo }}
William B
3

As versões anteriores do Angular (pré 1.0 RC) permitiam que você usasse isso de forma intercambiável com o método $ scope, mas esse não é mais o caso. Dentro dos métodos definidos no escopo this e $ scope são intercambiáveis ​​(angular define isso para $ scope), mas não dentro do construtor do controlador.

Para recuperar esse comportamento (alguém sabe por que ele foi alterado?), Você pode adicionar:

return angular.extend($scope, this);

no final da função do seu controlador (desde que $ scope tenha sido injetado nessa função do controlador).

Isso tem um bom efeito de ter acesso ao escopo pai via objeto do controlador com o qual você pode entrar em filho require: '^myParentDirective'

Kamil Szot
fonte
7
Este artigo fornece uma boa explicação sobre por que este e $ scope são diferentes.
Robert Martin
1

$ scope tem um 'this' diferente do controller 'this'.Thus, se você colocar um console.log (this) dentro do controller, ele fornece um objeto (controller) e this.addPane () adiciona o método addPane ao controller Object. Mas o $ scope tem escopo diferente e todo método em seu escopo precisa ser acessado por $ scope.methodName (). this.methodName()controlador interno significa adicionar methos ao objeto controlador. $scope.functionName()está em HTML e dentro

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

Cole este código no seu editor e abra o console para ver ...

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