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 = 'foo@bar.com';
I am seeking guidance on why so I can better understand.
Code
Service (Working)
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;
};
});
Controller
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',
'asdf@asdf.com',
'Test User',
new Date()
);
UserService.setCurrentUser(createdUser);
});
View
<div ng-controller="DashboardCtrl as dashboard">
<span bind="dashboard.user.fullName">Loading</span>
</div>
Result
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)
Breaking Scenario
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):
Service (Broken)
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;
};
});
Result
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.