Search code examples
angularjsjsonhttpdirective2-way-object-databinding

angular object transferred via $http differs from beforehand output unless timeout is used


I created a nested directive structure with 2-way-object-databinding. Everything worked like a charm on js side, until I connected it to my services which send the data to my api. Because in the post request the object has old values, it always ignores the latest change. But the real problem is... outputting the object right before I pass it to the request it has the correct updated values.

service.updatePerson = function(person) {
        console.log(person); //this outputs the correct up to date data
        $http.post(Routing.generate('update_person'), person); // this works not

        //After trying some more (see update below) - this works as well
        $timeout(function() {
            $http.post(Routing.generate('update_person'), person);
        }, 1000);
};

(I am using the fos routing bundle for symfony)

Inspecting the request with my developer tools I can see that the submitted object has old values. But its not like the values never change, its always the last but not the current change.

I'm quite new to angular and might overlook something special about the $http service - I did not enable cache, so that should not be the problem. Also the problem occurs only with the updated objects, not if I am sending a complete new entity.

The only difference I can see and I am suspecting to cause the issue is the depth of the nesting. I have a directive called 'collapsedTable' which takes different data. Not just the data also the update, add and delete method are passed to the directive.

This is the usage of the directive:

<collapsed-table ng-if="formModel" ng-if="persons" view-data="persons" form-model="formModel" add-object="newPerson" add-item="addPerson(item)"  update-item="updatePerson(item)" delete-item="deletePerson(item)"></collapsed-table>

Adding and deleting items happens directly in the directive:

<button href="#" class="btn btn-success pull-right" ng-click="addItem({item : newItem})">
          Add
</button>

(delete button looks the same, but calls the delete function)

But updating data does not happen via one button, it is bound directly to the form fields and the form fields are its own directive:

<form-field ng-if="column.type" form-field-bind="item[column.name]" form-field-action="updateItem({item:item})" form-field-action-param="item" form-select-options="column.data" form-template="{{column.type}}"></form-field>

(I know, generic shit going on here)

And within the directive (editableTextarea for an example):

<a href="#" onaftersave="formFieldAction({item : formFieldActionParam})" editable-textarea="$parent.formFieldBind">{{formFieldBind || 'eintragen'}}</a>

(using the x-editable module for angular here)

I think posting the whole content of my directives is going too far. But I think the scope settings are relevant to unterstand how functions and variables are passed.

collapsedTableDirective.js

.directive('collapsedTable',['$filter',function($filter) {
    return {
        restrict: 'E',
        templateUrl: 'templates/collapsedTableView.html',
        scope: {
            data: "=viewData",
            newItem: '=addObject',
            formModel: '=',
            deleteItem: '&',
            updateItem: '&',
            addItem: '&'
        }
    }
}]);

formFieldDirective.js

.directive('formField',['formTemplateFactory', '$filter',function(formTemplateFactory, $filter) {
    return {
        restrict: 'E',
        scope: {
            formFieldBind: '=',
            formFieldActionParam: '=',
            formFieldAction: '&',
            formTemplate: '@',
            formSelectOptions: '='
        },
        transclude: true,
        template: '<div ng-include="getTemplate()"></div>'
    }
}]);

I'm also showing that form field templates are pulled via ng-include, which creates a new scope and which is the reason why the bound var is referenced with "parent" inside the form field template.

But with all questions that can be possibly discussed here, do not forget:

console.log outputs the correct data within the service. Only inside the request we have old data. Thats also the reason ist kinda difficult to debug... from the debugger side my objects always look fine.

Any help appreciated!

UPDATE - with $timeout it works

After trying some stuff out, I've got the idea to try a timeout (from a completely unrelated post). And yeah, a timeout does the trick. But I am not answering the question because I really do not have an explanation for this behaviour. The console.log does not need a tiemout, though it should be executed before the post request. Also there is probably a more apropriate solution than using a timeout.


Solution

  • person is not updated at the time when service.updatePerson runs, and it looks like it is triggered by non-Angular code which isn't adapted to digest cycles. In this case this

        $scope.$apply();
        // or $rootScope.$apply() if this is a service and not a controller
        $http.post(Routing.generate('update_person'), person);
    

    may help, though doing $apply in caller function and not callee is always a better habit, so external JS should trigger a wrapper instead:

    scope.externalAction = function (obj) {
      scope.$apply(function () {
        scope.formFieldAction(obj);
      };
    };
    

    console.log doesn't output the data that was actual at that moment because it was supplied with object reference, and it may reflect the changes that took place after console.log was called. This is a useful feature if taken into account, otherwise it's harmful.

    To log the current object state always use

    console.log(JSON.stringify(person));