angular.copy()不创建对象的独立副本

时间:2014-04-08 19:50:26

标签: javascript angularjs object

我有一个AngularJS应用程序,它使用工厂将JSON数据加载到对象中。请注意,以下示例是真实应用程序的非常精简版本。用户对象使用getter / setter方法来访问其属性,因为真实应用程序必须处理比简单属性赋值更多的逻辑。这意味着我不能简单地绑定到视图上的ngModel - 我必须使用自定义指令。当我想对对象进行编辑时,我在控制器中创建对象的副本,让用户在视图中对其进行更改,最后在控制器中保存或取消这些更改。但是,当我调用angular.copy()时,这两个对象似乎仍然是链接的,因为在一个对象中更改数据也会更改另一个对象中的数据。这是为什么?

这是我的JSFiddle:http://jsfiddle.net/SqUu3/4/ 以下是我的观点:

<div ng-app="foo" ng-controller="ctrl">
    <div ng-repeat="(userID, user) in users">
        <span ng-if="inEditMode(userID)">
            <input type="text"
            ng-model="$name"
            ng-model-getter="editUsers[userID].getName()"
            ng-model-setter="editUsers[userID].setName($value)" />
            <button ng-click="saveChanges(userID)">Save</button>
            <button ng-click="setEditMode(editUsers[userID].getID(), false)">Cancel</button>
        </span>
        <span ng-if="!inEditMode(userID)">
            {{user.getName()}}
            <button ng-click="setEditMode(userID, true)">Edit</button>
        </span>
    </div>
</div>

这是我的控制器:

angular.module('foo', [])
.controller('ctrl', function($scope, UserFactory)
{
    // Maps user IDs to user objects
    // Using hash instead of array for fast access by ID
    $scope.users = UserFactory.load();
    // Maps IDs of users to copies of the respective user objects, used for editing
    $scope.editUsers = {};

    // Return whether or not we're editing the user
    $scope.inEditMode = function(userID)
    {
        return $scope.editUsers.hasOwnProperty(userID);
    };

    // Copy the changes made to the actual user object
    $scope.saveChanges = function(userID)
    {
        $scope.users[userID] = angular.copy($scope.editUsers[userID]);
        // Don't need the edit-copy, so get rid of it
        delete $scope.editUsers[userID];
    };

    // Turn edit mode on/off
    $scope.setEditMode = function(userID, inEditMode)
    {
        if(inEditMode)
        {
            // IN THEORY, this should create two independent copies of the same object
            $scope.editUsers[userID] = angular.copy($scope.users[userID]);

            /**
             * PROOF THESE ARE THE SAME OBJECTS:
             * This shouldn't affect the edit-copy in the view, but it does
             * Note that I am only doing this next call to prove that angular.copy() 
             * isn't giving me a new, independent copy of the user object
             */
            $scope.users[userID].setName("WHY IS THIS THE SAME");
        }
        else
        {
            // We are effecively canceling the changes we've made
            delete $scope.editUsers[userID];
        }
    };
})

有趣的部分是IN THEORY...行(我的JSFiddle中的第30行)。这不是创建一个独立的对象。

2 个答案:

答案 0 :(得分:1)

nameid属性在User工厂中作为私有属性被错误限制,因此在视图中无法访问name(我认为它已经破坏了)双向约束)。

将它们绑定到工厂对象(使用this),它应该被解析。

angular.module('foo', [])
.controller('ctrl', function($scope, UserFactory)
{
    // Maps user IDs to user objects
    // Using hash instead of array for fast access by ID
    $scope.users = UserFactory.load();
    // Maps IDs of users to copies of the respective user objects, used for editing
    $scope.editUsers = {};
    
    // Return whether or not we're editing the user
    $scope.inEditMode = function(userID)
    {
        return $scope.editUsers.hasOwnProperty(userID);
    };
    
    // Copy the changes made to the actual user object
    $scope.saveChanges = function(userID)
    {
        $scope.users[userID] = angular.copy($scope.editUsers[userID]);
        // Don't need the edit-copy, so get rid of it
        delete $scope.editUsers[userID];
    };

    // Turn edit mode on/off
    $scope.setEditMode = function(userID, inEditMode)
    {
        if(inEditMode)
        {
            // IN THEORY, this should create two independent copies of the same object
            $scope.editUsers[userID] = angular.copy($scope.users[userID]);
            
            /**
             * PROOF THESE ARE THE SAME OBJECTS:
             * This shouldn't affect the edit-copy in the view, but it does
             */
            $scope.users[userID].setName("WHY IS THIS THE SAME");
        }
        else
        {
            // We are effecively canceling the changes we've made
            delete $scope.editUsers[userID];
        }
    };
})
.factory('UserFactory', function(User)
{
    return {
        load: function()
        {
            // Simulate a JSON response with user data
            var rawUserData = [
                {id: 1, name: "Dave"},
                {id: 2, name: "Brian"}
            ];
            var userIDsToObjects = {};
            
            for(var userIter = 0;userIter < rawUserData.length;userIter++)
            {
                userIDsToObjects[rawUserData[userIter].id] = new User(rawUserData[userIter].id, rawUserData[userIter].name);
            }
            
            return userIDsToObjects;
        }
    }
})
.factory('User', function()
{
    return function(newID, newName)
    {
        this.getID = function()
        {
            return this.id;
        };
        
        this.getName = function()
        {
            return this.name;
        };
        
        this.setID = function(newID)
        {
            this.id = +newID;
        };
        
        this.setName = function(newName)
        {
            this.name = newName;
        };
        
        var self = this;
        // var id;
        // var name;
        
        (function()
         {
             self.setID(newID);
             self.setName(newName);
         })();
    }
})
.directive('ngModelGetter', function()
{
    return {
        require: "ngModel",
        //controller: "ctrl",
        link:  function(scope, element, attrs, ngModelCtrl)
        {
            var getExpression = attrs.ngModelGetter;
            
            function updateViewValue(newValue, oldValue)
            {
                if(newValue != ngModelCtrl.$viewValue)
                {
                    ngModelCtrl.$setViewValue(newValue);
                    ngModelCtrl.$render();
                }
                
                var updateExpression = attrs.ngModel + "=" + getExpression;
                scope.$eval(updateExpression);
            }
            
            updateViewValue();
            
            scope.$watch(getExpression, updateViewValue);
        }
    };
})
.directive('ngModelSetter', function()
{
    return {
        require: "ngModel",
        //controller: "ctrl",
        link:  function(scope, element, attrs, ngModelCtrl)
        {
            var setExpression = attrs.ngModelSetter;
            
            function updateModelValue(e)
            {
                scope.$value = ngModelCtrl.$viewValue;
                scope.$eval(setExpression);
                delete scope.$value;
            }
            
            scope.$watch(attrs.ngModel, updateModelValue);
        }
    };
})
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.19/angular.min.js"></script>
<div ng-app="foo" ng-controller="ctrl">
    <div ng-repeat="(userID, user) in users">
        <span ng-if="inEditMode(userID)">
            <input type="text"
            ng-model="$name"
            ng-model-getter="editUsers[userID].getName()"
            ng-model-setter="editUsers[userID].setName($value)" />
            <button ng-click="saveChanges(userID)">Save</button>
            <button ng-click="setEditMode(editUsers[userID].getID(), false)">Cancel</button>
        </span>
        <span ng-if="!inEditMode(userID)">
            {{user.getName()}}
            <button ng-click="setEditMode(userID, true)">Edit</button>
        </span>
    </div>
</div>

答案 1 :(得分:0)

查看angular.copy的源代码,如果未指定destination,则将其设置为source。

  if (!destination) {
    destination = source;

最后,它只是:

 return destination;