Reputation: 45
I have discovered a perplexing circumstance in which the $scope.watch is not triggering as I would expect. I am hoping some of you AngularJS buffs out there can help shed some light on why Angular is behaving so peculiarly with $scope.watch and Service objects. As a note this was discovered while I was messing with the Ionic framework.
Below is an example of what I am running, and it works as expected (to be used to maintain state information during the session.)
TL;DR: Watch doesn't fire on the controller when I update currentUser with a new userObject (currentUser = new userObject();) in my service. It does fire if I instead update each individual attribute of the object.
currentUser.name = 'Foo';
currentUser.email = '[email protected]';
I am seeking guidance on why so I can better understand.
angular.module('app.services')
.service( 'UserService', function() {
var userObject = function(){
return {
id: null,
username: null,
email: null,
fullName: null,
modified: null
}
};
var currentUser = new userObject();
var createUser = function(id, username, email, fullName, modified ){
var newUserObject = new userObject();
newUserObject.id = id;
newUserObject.username = username;
newUserObject.email = email;
newUserObject.fullName = fullName;
newUserObject.modified = (modified) ? modified : new Date();
return newUserObject;
};
var setCurrentUser = function(userObj){
console.log('First');
console.dir(currentUser);
setUserId(userObj.id);
setUsername(userObj.username);
setEmail(userObj.email);
setFullName(userObj.fullName);
setModifiedDate(userObj.modified);
console.log('Second');
console.dir(currentUser);
return currentUser;
};
});
angular.module('app.controllers')
.controller('DashboardCtrl', function ($scope, UserService) {
var dashboard = this;
dashboard.user = UserService.currentUser;
$scope.$watch('UserService.currentUser', function(newVal, oldVal) {
if (newVal !== oldVal ){
dashboard.user = newVal;
}
});
var createdUser = UserService.createUser(
1,
'user1',
'[email protected]',
'Test User',
new Date()
);
UserService.setCurrentUser(createdUser);
});
<div ng-controller="DashboardCtrl as dashboard">
<span bind="dashboard.user.fullName">Loading</span>
</div>
WAHOO! Loading is replaced on init with '' (null) and immediately after it is updated by the watch with 'Test User' (faster than the human eye unless debugging)
The code above works great. However I wanted to try and reduce code by reducing repetition of effort by creating a new UserObject and then setting that object as 'Current User'. The change I made was as follows (view and controller were unchanged):
angular.module('app.services')
.service( 'UserService', function() {
var userObject = function(){
return {
id: null,
username: null,
email: null,
fullName: null,
modified: null
}
};
var currentUser = new userObject();
var createUser = function(id, username, email, fullName, modified ){
var newUserObject = new userObject();
newUserObject.id = id;
newUserObject.username = username;
newUserObject.email = email;
newUserObject.fullName = fullName;
newUserObject.modified = (modified) ? modified : new Date();
return newUserObject;
};
var setCurrentUser = function(userObj){
console.log('First');
console.dir(currentUser);
// Set original 'currentUser' with a new UserObject passed by controller
currentUser = userObj;
console.log('Second');
console.dir(currentUser);
return currentUser;
};
});
Loading is replaced with '' on init (null) and $scope.watch never triggers in this scenario. My expectation is that the watch should be doing a deep watch on the object, and when I replace the object with a new one it should trigger that the object changed and trigger the watch.
The only thing I can figure, is that when I replace the currentUser object with a new object is that the delegates for $watch are also lost on that object. Does anyone have insight on how I can tell?
Phew.
Upvotes: 2
Views: 852
Reputation: 136144
When you give a string to the $watch
it checks that variable inside angular function, As you don't have UserService.currentUser
inside your scope then that watcher function will never get fired.
For making your watch working, you need to use function instead of string
, then that function will return an expression. By adding it in watcher function, so that it will get evaluated on each digest cycle & will perform dirty checking. If value gets change it will fire the watcher function
Code
$scope.$watch(function(){
return UserService.currentUser;
}, function(newVal, oldVal) {
if (newVal !== oldVal ){
dashboard.user = newVal;
}
});
Upvotes: 2