AngularJS - 如何强制更新作用域(数组项的 Promise)

7
我已经在Angular中创建了一个控制器,代码如下(为了简洁起见进行了编辑):

function AppCtrl($scope, $http, $location, $dataService) {
    $scope.projects = $dataService.data.projects;
}

如何正确从我的 $dataService 服务中加载 $scope.projects 的 promise。

app.service('$dataService', function($q, $http, $location, $rootScope) {
    var dataService = this; //Provides access 'this' inside functions below
    var projectsDeferred = $q.defer();

    $http.get('/api').success(function(data, status, headers, config) {
        projectsDeferred.resolve(data.projects);
    }).error(function(err) {
        projectsDeferred.reject(err);
    });

    this.data = {projects: projectsDeferred.promise};

    //UPDATE FUNCTION
    function updateObjectInArray(array, object, newData) {
        for(i in array) {
            if(array[i] == object) {
                if(newData != undefined) {
                    array[i] = newData;
                } else {
                    return array[i];
                }
            }
        }
        return undefined;
    }


    this.updateProject = function(project, updateData) {
        $http.put('/api/projects/' + project._id, updateData)
            .success(function(data, status, headers, config) {
            updateObjectInArray(dataService.data.projects.$$v, project, data);
        }).error(function(data, status, headers, config) {});
    };

});

我创建了另一个控制器,它看起来像这样,基于当前URL从项目数组中选择单个项目:

function ProjectCtrl($scope, $route) {
    //Getting the current project from the array of projects
    $scope.project = $scope.projects.then(function(projects) {
        for(i in projects) {
            if(projects[i]._id == $route.current.params.projectId) {
                return projects[i];
            }
        }
    });
}

当我尝试运行我的updateObjectInArray()函数(在我的$http.put()请求成功后),我的AppCtrl中的$scope.projects正确更新(项目数组),但是我的ProjectCtrl中的$scope.project没有更新。我可以在updateObjectInArray()函数内记录array[i],并且它将记录我所期望的内容,我可以记录AppCtrl中的$scope.projects,它将相应地更新,但是当我尝试在ProjectCtrl控制器中记录$scope.project时,它没有相应地更新。
我认为原因是在我更新updateObjectInArray()中的array[i]对象后,我必须调用$rootScope.$apply()$rootScope.$digest(),然而,我得到错误提示$digest is already in progress
我需要做什么才能确保在我的ProjectCtrl中更新数组中的$scope.project项目?我需要解决一个新的Promise吗?

你在哪里调用updateProject?你确定这是在解决 $dataService.data.projects 之前吗?尝试在几个函数中添加console.logs。 - asgoth
不,我想在解析$dataService.data.projects之后调用它。预期的功能是在页面首次加载时从服务器获取数据,用户可以执行一个操作将更新发送到服务器,如果成功,则使用从服务器返回的新数据更新作用域。 - winduptoy
我是指之后 :) 现在还太早了。 - asgoth
当用户点击按钮时,我在控制器中调用它。是的,我确定它已经解决了。正如我所说,更新对象后,我可以记录array[i],并且它已经按照我的期望进行了更新,但记录我的作用域显示它没有被更新为$dataService.projects现在相等的值。 - winduptoy
我想输入 $dataService.data.projects - winduptoy
2个回答

14
一旦加载了项目(projects),您使用数组projects [i]中的一个项目,让我们理论上假设它在内存中是对象 0x1 。问题在于,当加载新项目(假设为 0x2 )并更新数组时,您更改了驻留在数组位置的对象( 0x1 ),但 $scope.project 仍然引用已被放弃的对象 0x1 。您不能只在数组中更改它,您必须修改它,这样就不需要重新绑定 $scope.project 变量。
最佳解决方案是将 array [i] = newData 更改为 angular.extend(array [i],newData)。这样,您将仅更改数组[i]的所有属性,由于 $scope.project 指向同一内存中的对象,因此它将得到更新。
无论如何,我还认为您可能想要将 if(array [i] == object){ 更改为 if(array [i] ._id === object._id){ ,并且将您的等式比较( == )更改为严格等式比较( === )。这个if更适合您的情况,因为新创建的对象与旧对象不同。

非常好的回答!非常感谢!我以前从未听说过 angular.extend(),但它非常有效。相比于调用 $scope.watch('projects', ...,这个方法更高效吗?因为调用 $watch 意味着 Angular 必须做更多的工作?严格相等的建议很棒,我会使用它。 - winduptoy
这可能是性能最好的选择,因为只有在对象发生变化时才会合并。每个注册的$watch都会在__任何__作用域的脏检查周期中运行。你必须注意在哪里使用它,特别是当你需要递归$watch时,这将是你的情况。angular.extends基本上是基于jQuery.extend的。随时可以。 - Caio Cunha
哥们,你无法想象你所做的好事。我在绑定和扩展变量方面一直感到困惑不解。非常感谢你为我澄清了情况。万分感谢。 - Jimmy Kane
对我来说,问题似乎是,如果新对象没有保存要覆盖的值,旧属性将不会被清除。 - beku8

3
你是否在绑定中使用了$scope.projects?或者在代码的其他地方使用了它?问这个是因为你正在返回,而一个promise永远不会被它的结果更改,尽管你可以将promise直接绑定到HTML并期望它的内容。这是因为在内部,Angular似乎会监视它的结果。所以,如果你在别处使用了这个变量,那么你需要使用$q.when($scope.projects)。你将得到一个新的promise,如果它已经被解析,它将立即解决。

我相信我已经将问题隔离到从数组中提取它这一事实上。看起来我简化了我的代码太多了。我已经相应地更新了我的问题。请注意,在AppCtrl中,$scope.projects总是正确更新而无需任何其他工作。但是,当我尝试从该数组中提取单个项目时(在ProjectCtrl中),它没有被更新。顺便说一下,我将其绑定到HTML并且当我传递整个数组时它可以正常工作。非常感谢您的帮助,您真棒。 - winduptoy
请看我的回答。我创建了一个新的回答,因为它太大了无法在评论中发布。 - Caio Cunha

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接