2

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.

4

1 回答 1

2

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;
    }
});
于 2015-08-17T20:16:24.660 回答