1054

In the "Create Components" section of AngularJS's homepage, there is this example:

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

Notice how the select method is added to $scope, but the addPane method is added to this. If I change it to $scope.addPane, the code breaks.

The documentation says that there in fact is a difference, but it doesn't mention what the difference is:

Previous versions of Angular (pre 1.0 RC) allowed you to use this interchangeably with the $scope method, but this is no longer the case. Inside of methods defined on the scope this and $scope are interchangeable (angular sets this to $scope), but not otherwise inside your controller constructor.

How does this and $scope work in AngularJS controllers?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Alexei Boronine
  • 11,061
  • 4
  • 19
  • 14
  • 1
    I find this confusing also. When a view specifies a controller (e.g., ng-controller='...'), the $scope associated with that controller seems to come along with it, because the view can access $scope properties. But when a directive 'require's another controller (and then uses it in its linking function), the $scope associated with that other controller doesn't come along with it? – Mark Rajcok Sep 13 '12 at 03:20
  • Is that confusing quote about "Previous versions..." been removed by now? Then maybe update would be in place? – Dmitri Zaitsev Dec 07 '15 at 15:09
  • For unit testing, if you use 'this' instead of '$scope', you can not inject the controller with a mocked scope, and so you can not do unit testing. I don't think it is a good practice to use 'this'. – abentan Jan 31 '18 at 21:53

7 Answers7

1014

"How does this and $scope work in AngularJS controllers?"

Short answer:

  • this
    • When the controller constructor function is called, this is the controller.
    • When a function defined on a $scope object is called, this is the "scope in effect when the function was called". This may (or may not!) be the $scope that the function is defined on. So, inside the function, this and $scope may not be the same.
  • $scope
    • Every controller has an associated $scope object.
    • A controller (constructor) function is responsible for setting model properties and functions/behaviour on its associated $scope.
    • Only methods defined on this $scope object (and parent scope objects, if prototypical inheritance is in play) are accessible from the HTML/view. E.g., from ng-click, filters, etc.

Long answer:

A controller function is a JavaScript constructor function. When the constructor function executes (e.g., when a view loads), this (i.e., the "function context") is set to the controller object. So in the "tabs" controller constructor function, when the addPane function is created

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

it is created on the controller object, not on $scope. Views cannot see the addPane function -- they only have access to functions defined on $scope. In other words, in the HTML, this won't work:

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

After the "tabs" controller constructor function executes, we have the following:

after tabs controller constructor function

The dashed black line indicates prototypal inheritance -- an isolate scope prototypically inherits from Scope. (It does not prototypically inherit from the scope in effect where the directive was encountered in the HTML.)

Now, the pane directive's link function wants to communicate with the tabs directive (which really means it needs to affect the tabs isolate $scope in some way). Events could be used, but another mechanism is to have the pane directive require the tabs controller. (There appears to be no mechanism for the pane directive to require the tabs $scope.)

So, this begs the question: if we only have access to the tabs controller, how do we get access to the tabs isolate $scope (which is what we really want)?

Well, the red dotted line is the answer. The addPane() function's "scope" (I'm referring to JavaScript's function scope/closures here) gives the function access to the tabs isolate $scope. I.e., addPane() has access to the "tabs IsolateScope" in the diagram above because of a closure that was created when addPane() was defined. (If we instead defined addPane() on the tabs $scope object, the pane directive would not have access to this function, and hence it would have no way to communicate with the tabs $scope.)

To answer the other part of your question: how does $scope work in controllers?:

Within functions defined on $scope, this is set to "the $scope in effect where/when the function was called". Suppose we have the following 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>

And the ParentCtrl (Solely) has

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

Clicking the first link will show that this and $scope are the same, since "the scope in effect when the function was called" is the scope associated with the ParentCtrl.

Clicking the second link will reveal this and $scope are not the same, since "the scope in effect when the function was called" is the scope associated with the ChildCtrl. So here, this is set to ChildCtrl's $scope. Inside the method, $scope is still the ParentCtrl's $scope.

Fiddle

I try to not use this inside of a function defined on $scope, as it becomes confusing which $scope is being affected, especially considering that ng-repeat, ng-include, ng-switch, and directives can all create their own child scopes.

Andrew Tobilko
  • 48,120
  • 14
  • 91
  • 142
Mark Rajcok
  • 362,217
  • 114
  • 495
  • 492
  • 1
    _When a function defined on a $scope object is called, this is the "scope in effect when the function was called". This may (or may not!) be the $scope that the function is defined on. So, inside the function, this and $scope may not be the same._ But the AngularJS official documentation states, **Inside of methods defined on the scope this and $scope are interchangeable (angular sets this to $scope), but not otherwise inside your controller constructor.** – tamakisquare Feb 19 '13 at 19:18
  • 6
    @tamakisquare, I believe the bolded text you quoted applies to when the controller constructor function is called -- i.e., when the controller is created = associated with a $scope. It does not apply later, when arbitrary JavaScript code calls a method defined on a $scope object. – Mark Rajcok Feb 19 '13 at 20:04
  • Thanks Mark. I re-read the part of the official doc a few more times; [The sixth bullet following "A Spicy Controller Example"](http://docs.angularjs.org/guide/dev_guide.mvc.understanding_controller). I still think the doc is saying that `this` and `$scope` are interchangeable when used inside of methods defined on the scope. It doesn't mention that the interchangeability depends on where the methods are called, so I would assume that shouldn't matter. But, obviously, your provided example has proved otherwise. – tamakisquare Feb 20 '13 at 01:14
  • I would like to suggest you to open up a ticket with AngularJS's github mentioning the ambiguity/inaccuracy of the official doc, along with your example in fiddle. – tamakisquare Feb 20 '13 at 01:15
  • @tamakisquare, I should re-read my own answer! "When the controller constructor function is called, `this` is the controller", not the $scope. So, I suppose that quote from the official docs only applies if you call a $scope method from someplace where that same $scope is in effect. – Mark Rajcok Feb 20 '13 at 03:16
  • 79
    Note that is it now possible to call the addPane() function directly in the template by naming the controller: "MyController as myctrl" and then myctrl.addPane(). See http://docs.angularjs.org/guide/concepts#controller – Christophe Augier Nov 29 '13 at 21:44
  • I don't understand , so why here (http://jsbin.com/EduxOkuX/2/edit) both lines are the same ? – Royi Namir Jan 24 '14 at 07:55
  • @RoyiNamir, "Only methods defined on the $scope object (and parent scope objects, if prototypical inheritance is in play) are accessible from the HTML/view" -- this includes using curly braces in the view, e.g., `{{someScopeVariable}}`. The view can't see the variables you assigned to `this` in the ChildController. – Mark Rajcok Jan 24 '14 at 18:07
  • "It does not prototypically inherit from the scope in effect where the directive was encountered in the HTML." This seems to contradict the following definitive account of scope... https://github.com/angular/angular.js/wiki/Understanding-Scopes - "In AngularJS, a child scope normally prototypically inherits from its parent scope." – Ian Warburton May 02 '14 at 20:27
  • 6
    so is it the case that you should avoid `this` and just use scope? or am I missing something? – ErichBSchulz May 17 '14 at 05:57
  • 85
    Too much inherent complexity. – Inanc Gumus Jun 07 '14 at 08:33
  • It it possible to invoke a function defined on the controller form the browsers console? – martinoss Aug 01 '14 at 08:21
  • I want to add a link to this fiddle -> http://jsfiddle.net/sbZw7/241/ - Here the logThisAndScope function is added to the child controller. You'll now see that it returns the same object for this and $scope. This is expected because the ng-click looks for the function on it's local $scope then moves up the scope chain. –  Oct 10 '14 at 03:08
  • 11
    This is a very informative answer, but when I came back with a practical problem ([how to invoke $scope.$apply() in a controller method defined using 'this'](http://stackoverflow.com/questions/27490197/what-is-the-this-equivalent-of-scope-apply-in-angular-js)) I could not work it out. So while this is still a useful answer, I am finding the "inherent complexity" baffling. – dumbledad Dec 15 '14 at 18:10
  • "Inside the method, $scope is still the ParentCtrl's $scope"...can you explain this please? Why is $scope not the child controllers $scope? – smackenzie Jan 13 '15 at 01:19
  • @smackenzie, when function logThisAndScope() is created, the ParentCtrl's `$scope` becomes part of the closure associated with the function. So when the function accesses variable `$scope`, it finds it in the closure. – Mark Rajcok Jan 13 '15 at 04:41
  • 12
    Javascript - lots of rope [to hang yourself]. – AlikElzin-kilaka Jun 01 '15 at 07:09
  • Thanks for the clarification, just one more why when Clicking the second link will reveal, the $scope is still the ParentCtrl's $scope.? – user2499325 Aug 28 '15 at 02:41
  • 2
    @user2499325, function `logThisAndScope` creates a closure, and variable `$scope` (the one associated with ParentCtrl) is part of that closure. When the second link is clicked, there is no `logThisAndScope` function defined on the ChildCtrl's `$scope`, so prototypical inheritance kicks in and the parent `$scope` (the one associated with the ParentCtrl) is examined. A `logThisAndScope` function is found on that `$scope` and it is executed. When that function logs `$scope` it is the `$scope` associated with the closure, hence the one associated with ParentCtrl. I hope that helps. – Mark Rajcok Sep 01 '15 at 04:15
  • How does `this` work when the controller function is called via the $controller service? More specifically, would [ctrlInstance](https://github.com/angular-ui/bootstrap/blob/master/src/modal/modal.js#L655) have any properties that were defined in the controller function using `this.property = value`? ($scope is passed in via the locals) My guess is no. – Tahsis Claus Oct 28 '15 at 15:26
  • 2
    I also found this https://toddmotto.com/digging-into-angulars-controller-as-syntax/ helpful – vinesh Jan 13 '16 at 10:46
  • @AlikElzin-kilaka eh, this is more of an issue with Angular, not JavaScript. – Dan Sep 15 '16 at 09:46
  • 1
    don't use "this", avoid it, unless you are doing absolutely pure OOP. "this" is an OOP construct, it's better to use JS in a more functional style because of nested functions. that is all. I will not be using "this" in place of $scope in any of my angular code, thank you very much. – Alexander Mills Mar 17 '17 at 16:43
  • @MarkRajcok "Begs the question" does not mean what you think it means. Rather like scope nearly everyone misunderstands it and tries to use it incorrectly. When used correctly the phrase refers to the logical fallacy of using premises in an argument that depend on an assumed conclusion, a form of circular reasoning. – Peter Wone May 18 '20 at 14:22
57

The reason 'addPane' is assigned to this is because of the <pane> directive.

The pane directive does require: '^tabs', which puts the tabs controller object from a parent directive, into the link function.

addPane is assigned to this so that the pane link function can see it. Then in the pane link function, addPane is just a property of the tabs controller, and it's just tabsControllerObject.addPane. So the pane directive's linking function can access the tabs controller object and therefore access the addPane method.

I hope my explanation is clear enough.. it's kind of hard to explain.

Mon Calamari
  • 4,403
  • 3
  • 26
  • 44
Andrew Joslin
  • 43,033
  • 21
  • 100
  • 75
  • 3
    Thanks for the explanation. The docs make it seem that the controller is just a function that sets up the scope. Why does the controller get treated like an object if all the action happens in the scope? Why not just pass the parent scope into the linking function? Edit: To better phrase this question, if controller methods and scope methods both operate on the same data structure (the scope), why not put them all in one place? – Alexei Boronine Jul 24 '12 at 00:23
  • It seems the parent scope isn't passed into the lnk func because of the desire to support "reusable components, which should not accidentally read or modify data in the parent scope." But if a directive really does want/need to read or modify SOME SPECIFIC data in the parent scope (like the 'pane' directive does), it requires some effort: 'require' the controller where the desired parent scope is, then define a method on that controller (use 'this' not $scope) to access specific data. Since the desired parent scope is not injected into the lnk func, I suppose this is the only way to do it. – Mark Rajcok Sep 13 '12 at 03:42
  • 1
    Hey mark, it's actually easier to modify the directive's scope. You can just use the link function http://jsfiddle.net/TuNyj/ – Andrew Joslin Sep 13 '12 at 13:28
  • 3
    Thanks @Andy for the fiddle. In your fiddle, the directive is not creating a new scope, so I can see how the link function can directly access the controller's scope here (since there is only one scope). The tabs and pane directives use isolate scopes (i.e., new child scopes are created that do not prototypically inherit from the parent scope). For the isolate scope case, it seems that defining a method on a controller (using 'this') is the only way to allow another directive to get (indirect) access to the other (isolated) scope. – Mark Rajcok Jan 01 '13 at 16:56
28

I just read a pretty interesting explanation on the difference between the two, and a growing preference to attach models to the controller and alias the controller to bind models to the view. http://toddmotto.com/digging-into-angulars-controller-as-syntax/ is the article.

NOTE: The original link still exists, but changes in formatting have made it hard to read. It's easier to view in the original.

He doesn't mention it but when defining directives, if you need to share something between multiple directives and don't want a service (there are legitimate cases where services are a hassle) then attach the data to the parent directive's controller.

The $scope service provides plenty of useful things, $watch being the most obvious, but if all you need to bind data to the view, using the plain controller and 'controller as' in the template is fine and arguably preferable.

ruffin
  • 16,507
  • 9
  • 88
  • 138
Derek
  • 722
  • 1
  • 6
  • 16
19

I recommend you to read the following post: AngularJS: "Controller as" or "$scope"?

It describes very well the advantages of using "Controller as" to expose variables over "$scope".

I know you asked specifically about methods and not variables, but I think that it's better to stick to one technique and be consistent with it.

So for my opinion, because of the variables issue discussed in the post, it's better to just use the "Controller as" technique and also apply it to the methods.

ofthelit
  • 1,341
  • 14
  • 33
Liran Brimer
  • 3,418
  • 1
  • 28
  • 23
16

In this course(https://www.codeschool.com/courses/shaping-up-with-angular-js) they explain how to use "this" and many other stuff.

If you add method to the controller through "this" method, you have to call it in the view with controller's name "dot" your property or method.

For example using your controller in the view you may have code like this:

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

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

    </div>
Sandro
  • 211
  • 2
  • 4
  • 6
    After going through the course, I was immediately confused by code using `$scope`, so thanks for mentioning it. – Matt Montag Dec 11 '14 at 03:24
  • 16
    That course does not mention $scope at all, they just use `as` and `this` so how can it help explain the difference? – dumbledad Dec 11 '14 at 14:52
  • 10
    My first touch with Angular was from the mentioned course, and as `$scope` was never referred, I learned to use just `this` in the controllers. The problem is that when you start to have tohandle promises in your controller, you have a lot of references problem to `this` and have to start doing things like `var me = this` to reference the model in `this` from within the promise return function. So because of that, I'm still very confused about which method should me used, `$scope` or `this`. – Bruno Finger Mar 11 '15 at 11:12
  • @BrunoFinger Unfortunately, you'll need `var me = this` or `.bind(this)` whenever you do Promises, or other closure-heavy stuff. It has nothing to do with Angular. – Dzmitry Lazerka Mar 18 '16 at 05:28
  • @DzmitryLazerka You're absolutely right but if we are careful enough we can avoid writing such ugly code. In fact re-reading my comment today, I'm even embarrassed to say something as handle promises within the controller. This kind of logic belongs somewhere else in the application. I'm graviting towards having them in another layer of service interfacing the `$scope`. This makes complex logics much more reusable. – Bruno Finger Mar 21 '16 at 09:48
  • 2
    The important thing is to know that `ng-controller="MyCtrl as MC"` is equivalent to putting `$scope.MC = this` in the controller itself -- it defines an instance (this) of MyCtrl on the scope for use in the template via `{{ MC.foo }}` – William B Nov 25 '16 at 18:07
3

Previous versions of Angular (pre 1.0 RC) allowed you to use this interchangeably with the $scope method, but this is no longer the case. Inside of methods defined on the scope this and $scope are interchangeable (angular sets this to $scope), but not otherwise inside your controller constructor.

To bring back this behaviour (does anyone know why was it changed?) you can add:

return angular.extend($scope, this);

at the end of your controller function (provided that $scope was injected to this controller function).

This has a nice effect of having access to parent scope via controller object that you can get in child with require: '^myParentDirective'

Kamil Szot
  • 17,436
  • 6
  • 62
  • 65
  • 7
    [This article](http://toddmotto.com/digging-into-angulars-controller-as-syntax/) provides a good explanation of why this and $scope are different. – Robert Martin Jan 08 '15 at 00:35
1

$scope has a different 'this' then the controller 'this'.Thus if you put a console.log(this) inside controller it gives you a object(controller) and this.addPane() adds addPane Method to the controller Object. But the $scope has different scope and all method in its scope need to be accesed by $scope.methodName(). this.methodName() inside controller means to add methos inside controller object.$scope.functionName() is in HTML and inside

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

Paste this code in your editor and open console to see...

 <!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
  • 1,751
  • 11
  • 13