$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
?
2 answers
Answering your question directly
Is there a way to pass the function
$scope.on
to the variablevm
?
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).
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
Http://www.egghead.io/video/DTx23w4z6Kc
Https://github.com/johnpapa/angular-styleguide/blob/master/a1/README.md#style-y030
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