Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AngularJS - how to force a scope to update (promise for array item)

Tags:

angularjs

I've created a controller in Angular that looks like this (edited for brevity):

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

Which correctly loads the $scope.projects promise from my $dataService service.

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) {});
    };

});

I've created another controller that looks like this, that selects a single project from the array of projects based on the current 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];
            }
        }
    });
}

When I try to run my updateObjectInArray() function (on the success of my $http.put() request), my $scope.projects in AppCtrl is correctly updated (the array of projects) but my $scope.project in ProjectCtrl is not updated. I can log array[i] inside the updateObjectInArray() function and it will log exactly what I expect, and I can log $scope.projects in AppCtrl and it will update accordingly, but when I try to log $scope.project in my ProjectCtrl controller, it isn't updated accordingly.

I thought the reason was because I had to call $rootScope.$apply() or $rootScope.$digest() after I updated the array[i] object in updateObjectInArray(), however, I get the error that $digest is already in progress.

What do I need to do to make sure my $scope.project item in the array gets updated in my ProjectCtrl? Do I need to resolve a new promise for it?

like image 968
winduptoy Avatar asked Jan 14 '13 05:01

winduptoy


People also ask

What is q defer() in AngularJS?

Simply put you can use $q. defer() to create a Promise. A Promise is a function that returns a single value or error in the future. So whenever you have some asynchronous process that should return a value or an error, you can use $q. defer() to create a new Promise.

What does$ q do in AngularJS?

$q is an angular defined service. It's the same as new Promise(). But $q takes things to the next level by enhancing additional feature that developers can use to perform complex tasks more simply. resolve(value) – resolves the derived promise with the value.

Why we use$ q in AngularJS?

$q is integrated with the $rootScope. Scope Scope model observation mechanism in AngularJS, which means faster propagation of resolution or rejection into your models and avoiding unnecessary browser repaints, which would result in flickering UI. Q has many more features than $q, but that comes at a cost of bytes.


1 Answers

As soon as the projects are loaded, you're using one of the items from the array projects[i], let's theoretically assume it's the object 0x1 in memory. The problem is that when a new item is loaded (let's assume 0x2), and you update the array, you're changing the object that resides in the array position (the 0x1), but $scope.project still references the now abandoned object 0x1. You can't just change it in the array, you have to modify it, so you don't need to rebind the $scope.project variable.

The best solution is to change array[i] = newData to angular.extend(array[i], newData). This way, you gonna just change all properties of array[i], and as your $scope.project points to the same object in memory it will be updated.

Anyway, I also believe you may want to change if(array[i] == object) { to if(array[i]._id === object._id) { and your equality comparisons (==) to strictly equality comparisons (===). This if suits better your cause, as the new created object is not the same as the old one.

like image 60
Caio Cunha Avatar answered Sep 20 '22 16:09

Caio Cunha