Reputation: 2137
How to subscribe on property change when using controller as
syntax?
controller('TestCtrl', function ($scope) {
this.name = 'Max';
this.changeName = function () {
this.name = new Date();
}
// not working
$scope.$watch("name",function(value){
console.log(value)
});
});
<div ng-controller="TestCtrl as test">
<input type="text" ng-model="test.name" />
<a ng-click="test.changeName()" href="#">Change Name</a>
</div>
Upvotes: 157
Views: 54227
Reputation: 355
Here is how you do this without $scope (and $watch!) Top 5 Mistakes - Abusing watch
If you are using "controller as" syntax, it's better and cleaner to avoid using $scope.
Here is my code in JSFiddle. (I am using a service to hold the name, otherwise the ES5 Object.defineProperty's set and get methods cause infinite calls.
var app = angular.module('my-module', []);
app.factory('testService', function() {
var name = 'Max';
var getName = function() {
return name;
}
var setName = function(val) {
name = val;
}
return {getName:getName, setName:setName};
});
app.controller('TestCtrl', function (testService) {
var vm = this;
vm.changeName = function () {
vm.name = new Date();
}
Object.defineProperty(this, "name", {
enumerable: true,
configurable: false,
get: function() {
return testService.getName();
},
set: function (val) {
testService.setName(val);
console.log(vm.name);
}
});
});
Upvotes: -2
Reputation: 1297
You can use $onChanges angular component lifecycle.
see documentation here: https://docs.angularjs.org/guide/component under Component-based application section
Upvotes: 1
Reputation: 5668
you can actually pass in a function as the first argument of a $watch():
app.controller('TestCtrl', function ($scope) {
this.name = 'Max';
// hmmm, a function
$scope.$watch(function () {}, function (value){ console.log(value) });
});
Which means we can return our this.name reference:
app.controller('TestCtrl', function ($scope) {
this.name = 'Max';
// boom
$scope.$watch(angular.bind(this, function () {
return this.name; // `this` IS the `this` above!!
}), function (value) {
console.log(value);
});
});
Read an interesting post about controllerAs topic https://toddmotto.com/digging-into-angulars-controller-as-syntax/
Upvotes: 2
Reputation: 4834
AngularJs 1.5 supports the default $ctrl for the ControllerAs structure.
$scope.$watch("$ctrl.name", (value) => {
console.log(value)
});
Upvotes: 12
Reputation: 5729
Writing a $watch in ES6 syntax wasn't as easy as I expected. Here's what you can do:
// Assuming
// controllerAs: "ctrl"
// or
// ng-controller="MyCtrl as ctrl"
export class MyCtrl {
constructor ($scope) {
'ngInject';
this.foo = 10;
// Option 1
$scope.$watch('ctrl.foo', this.watchChanges());
// Option 2
$scope.$watch(() => this.foo, this.watchChanges());
}
watchChanges() {
return (newValue, oldValue) => {
console.log('new', newValue);
}
}
}
Upvotes: 0
Reputation: 3411
Just bind the relevant context.
$scope.$watch(angular.bind(this, function () {
return this.name;
}), function (newVal) {
console.log('Name changed to ' + newVal);
});
Example: http://jsbin.com/yinadoce/1/edit
UPDATE:
Bogdan Gersak's answer is actually kind of equivalent, both answers try binding this
with the right context. However, I found his answer cleaner.
Having that said, first and foremost, you have to understand the underlying idea behind it.
UPDATE 2:
For those who use ES6, by using arrow function
you get a function with the right context OOTB.
$scope.$watch(() => this.name, function (newVal) {
console.log('Name changed to ' + newVal);
});
Upvotes: 161
Reputation: 1887
I usually do this:
controller('TestCtrl', function ($scope) {
var self = this;
this.name = 'Max';
this.changeName = function () {
this.name = new Date();
}
$scope.$watch(function () {
return self.name;
},function(value){
console.log(value)
});
});
Upvotes: 139
Reputation: 1096
NOTE: This doesn't work when View and Controller are coupled in a route or through a directive definition object. What's shown below only works when there's a "SomeController as SomeCtrl" in the HTML. Just like Mark V. points out in the comment below, and just as he says it's better to do like Bogdan does it.
I use: var vm = this;
in the beginning of the controller to get the word "this" out of my way. Then vm.name = 'Max';
and in the watch I return vm.name
. I use the "vm" just like @Bogdan uses "self". This var, be it "vm" or "self" is needed since the word "this" takes on a different context inside the function. (so returning this.name wouldn't work) And yes, you need to inject $scope in your beautiful "controller as" solution in order to reach $watch. See John Papa's Style Guide: https://github.com/johnpapa/angularjs-styleguide#controllers
function SomeController($scope, $log) {
var vm = this;
vm.name = 'Max';
$scope.$watch('vm.name', function(current, original) {
$log.info('vm.name was %s', original);
$log.info('vm.name is now %s', current);
});
}
Upvotes: -1
Reputation: 131
Similar to using the "test" from "TestCtrl as test", as described in another answer, you can assign "self" your scope:
controller('TestCtrl', function($scope){
var self = this;
$scope.self = self;
self.name = 'max';
self.changeName = function(){
self.name = new Date();
}
$scope.$watch("self.name",function(value){
console.log(value)
});
})
In this way, you are not tied to the name specified in the DOM ("TestCtrl as test") and you also avoid the need to .bind(this) to a function.
...for use with the original html specified:
<div ng-controller="TestCtrl as test">
<input type="text" ng-model="test.name" />
<a ng-click="test.changeName()" href="#">Change Name</a>
</div>
Upvotes: 13
Reputation: 6962
You can use:
$scope.$watch("test.name",function(value){
console.log(value)
});
This is working JSFiddle with your example.
Upvotes: 23