$scope vs. this in angularjs

I'm trying to learn angularjs, Version 1.X and using this instead of $scope, doing var vm = this;. It generally works correctly, but some times, with some functions, I'm forced to use $scope to get my code to work.

I Use for this, the tutorial of thinkster: Building web applications with Django and AngularJS and the book of SitePoint, AngularJS: novice to ninja .

While trying to adapt the book Code to the one used in the thinkster tutorial, I find that I can't always use the variable vm, for example:

In the book the code for an example function is:

angular.module('myApp.controllers', []);

// código eliminado para ahorrar espacio

angular.module('myApp.controllers').controller('StatsController', function($scope) {
  $scope.name = 'World';
  $scope.status = 'Connected';
  $scope.statusColor='green';
  $scope.$on('EVENT_NO_DATA', function(event, data) {
    console.log('received broadcasted event');
    $scope.status = data;
    $scope.statusColor='red';
    $scope.$emit('EVENT_RECEIVED');
}); });

I adapt this code ( according to my understanding), like this:

(function(){
  'use strict';

  angular.module('myEmit', [
    'myEmit.controllers',
  ]);

  angular.module('myEmit.controllers', [])
    .controller('MessageController', MessageController)
    .controller('StatController', StatController);

  function MessageController($scope, $timeout) {
    var vm = this;
    vm.messages = [{
      sender: 'user1',
        text: 'Mensaje 1'
    }];
    var timer;
    var count = 0;
    vm.loadMessages = function(){
      count++;
      vm.messages.push({
        sender: 'user1',
          text: 'Mensaje aleatorio ' + count
      });
      timer = $timeout(vm.loadMessages, 2000);
      if(count == 3){
        $scope.$broadcast('EVENT_NO_DATA', 'Not Connected');
        $timeout.cancel(timer);
      }
    };
    timer = $timeout(vm.loadMessages, 2000);
    $scope.$on('EVENT_RECEIVED', function(){
      console.log('Received emitted event');
    });
  };

  function StatController($scope) {
    var vm = this;

    vm.name = 'World';
    vm.status = 'Connected';
    vm.statusColor = 'green';
    $scope.$on('EVENT_NO_DATA', function(event, data){
      console.log('Received broadcast event');
      vm.status = data;
      vm.statusColor = 'red';
      $scope.$emit('EVENT_RECEIVED');
    });
  };
  
})();
<!DOCTYPE html>
<html ng-app='myEmit'>
  <head>
    <meta charset="utf-8">
    <title>AngularJS Events</title>
  </head>
  <body ng-controller="MessageController as mc">
    <h4>Mensajes:</h4>
    <ul>
      <li ng-repeat="mensaje in mc.messages">
        {{ mensaje.text }}
      </li>
    </ul>
    <div class="" ng-controller="StatController as sc">
      <h4>Estadísticas: </h4>
      <div class="">
        {{ mc.messages.length}} Mensaje(s)
      </div>
      <div class="">
        Estado: <span style="color: {{ sc.statusColor}}">
          {{ sc.status }}
        </span>
      </div>
    </div>
  </body>
  <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
</html>

Problem

When I try to use this code,

  function StatController($scope) {
    var vm = this;

    vm.name = 'World';
    vm.status = 'Connected';
    vm.statusColor = 'green';
    vm.$on('EVENT_NO_DATA', function(event, data){
      console.log('Received broadcast event');
      vm.status = data;
      vm.statusColor = 'red';
      $scope.$emit('EVENT_RECEIVED');
    });
  };

El controller StatController does not work because it gives the following error

Error: vm.$on is not a function. (In 'vm.$on', 'vm.$on' is undefined)

That is, that vm.$on is not a function or is indefinite.

Question

Apparently when I use var vm = this; it doesn't transfer the $scope to the vm variable and therefore functions like $on are not accessible in my vm variable.

¿is there a way to pass the function $scope.on to the variable vm? could you help me understand how it works this vs $scope?

 12
Author: devconcept, 2016-05-30

2 answers

Answering your question directly

Is there a way to pass the function $scope.on to the variable vm?

No. They're different things. The function $on is a property of scopes used to emit events, it has nothing to do with the value of this in a controller.

I'll explain.

The reason for your confusion is because you think that $scope and this have some kind of direct relationship and this is not so, in actually this is just one more property of the $scope associated with your controller (just like $on and $emit are).

thisvsscope

Reviewing:

Every time you create a controller, a new $scope it is created internally using the $scopepadre.new() method, this happens because ng-controller it is a directive that is created with the following configuration

var ngControllerDirective = [function() {
  return {
    restrict: 'A',
    scope: true,
    controller: '@',
    priority: 500
  };
}];

The party that says scope: true is responsible for informing the service $compile that a new $scope should be created.

You can think of the scope and its properties as the data that is used to render the view. The scope is the only source of truth for all things sight-related.

How does this get into all this?

Your controllers are always created as a kind of class (this is why they must be conventionally capitalized), like this

new MyCtrl();

Yes read how the operator worksnew in javascript and what its impact on the value of this (check Step 2) you will notice that when you create an object like this any property you add to this you are adding it to the "class instance" as such. This is purely a comparison since in javascript there are no classes but I think it illustrates the point quite well.

What does all of the above have to do with this in angular?

Because when you use the option controllerAs in this way 'MyController as foo' the only thing you do is tell the angular to create a new property called 'foo' in the asociado scope associated with the element and to assign to that property the instance of the newly created controller.

The latter is important to understand well, scopes are not a feature of controllers, scopes are associated with DOM elements, the most notable example of this is the directive ng-repeat that creates a new $scope for each item in a collection.

Let's compare the traditional notation with the controllerAs notation so you can see what the differences are

angular.module('app', [])
  // Notación tradicional
  .controller('Sample1Ctrl', function($scope) {
    $scope.vm = {};
    $scope.vm.mensaje = 'Instancia tradicional';

    $scope.$on('event', function() {
      //haz algo en este evento
    });
  })
  // Notación controller as
  .controller('Sample2Ctrl', function($scope) {
    var vm = this;
    vm.mensaje = 'Instancia controllerAs';

    $scope.$on('event', function() {
      //haz algo en este evento
    });
  });
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app">
  <div ng-controller="Sample1Ctrl">
    {{vm.mensaje}}
  </div>
  <div ng-controller="Sample2Ctrl as vm">
    {{vm.mensaje}}
  </div>
</div>

In this example in both controllers a property called vm is created with an empty object and it is added properties that are bound in the view. In the traditional mode you have to do all these steps manually while in the controllerAs this does not it is necessary since when instantiating objects in javascript a new empty object is created (Step 1). The property in the $scope is created in the same statement as the ng-controller="SampleCtrl as vm"

And what is this for?

Because angular is javascript it is sometimes necessary to use a . (or what is known as "dot notation") to reference some objects since in javascript these references can be lost due to how inheritance works prototypical in angular (and in javascript in general). In this way we make sure that we are referencing the correct object.

In the following example you are not using "dot notation" so when you edit the property in the child controller it works as you expect but when you try to do it in its parent it does nothing.

angular.module('app', [])
  .controller('PadreCtrl', function($scope) {
    $scope.prop1 = 'Padre';
  })
  .controller('HijoCtrl', function($scope) {
    $scope.prop2 = 'Hijo'
  });
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app">
  <div ng-controller="PadreCtrl">
    {{prop1}}
    <div ng-controller="HijoCtrl">
      {{prop2}}
      <div>
        <input ng-model="prop1">

      </div>
      <div>
        <input ng-model="prop2">
      </div>
    </div>
  </div>
</div>

This would be the right way to do it

angular.module('app', [])
  .controller('PadreCtrl', function() {
    // La variable que uses aqui es irrelevante pero por comodidad
    // debe corresponder con la que usaste en la vista
    var foo = this;
    foo.prop1 = 'Padre';
  })
  .controller('HijoCtrl', function($scope) {
    var vm = this;
    vm.prop2 = 'Hijo'
  });
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app">
  <div ng-controller="PadreCtrl as padre">
    {{padre.prop1}}
    <div ng-controller="HijoCtrl as hijo">
      {{hijo.prop2}}
      <div>
        <input ng-model="padre.prop1">

      </div>
      <div>
        <input ng-model="hijo.prop2">
      </div>
    </div>
  </div>
</div>

Summarizing controllerAs assures us that we do not fall into traps like that you just have to understand how it works. The $scope continues to have the same responsibilities as always (do $watch, $emit, $broadcast, etc) and the this is not intended in any way to replace its functionality. It simply guarantees us good practices and sometimes saves us from having to inject $scope if it is not necessary.

Here are some articles (in English unfortunately) in which you can find more details of the topic in question

Https://stackoverflow.com/questions/14049480/what-are-the-nuances-of-scope-prototypal-prototypical-inheritance-in-angularjs

Http://www.egghead.io/video/DTx23w4z6Kc

Https://github.com/johnpapa/angular-styleguide/blob/master/a1/README.md#style-y030

 16
Author: devconcept, 2017-05-23 12:39:20

You should do something like this:

var vm = this;
vm.$scope = $scope
vm.$scope.$on('EVENT_NO_DATA', function(event, data){

I recommend you also read something about ES6 since you can generate classes objects and methods with angular programming will make it easier

 4
Author: JackNavaRow, 2017-01-19 15:11:25