Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

TransformRequest in $http is changing scope #12468

Closed
Gregoirevda opened this issue Jul 30, 2015 · 12 comments · Fixed by #14122
Closed

TransformRequest in $http is changing scope #12468

Gregoirevda opened this issue Jul 30, 2015 · 12 comments · Fixed by #14122

Comments

@Gregoirevda
Copy link

I'm sending scope data trough $http and change that data with TransformRequest before it reaches the API.
When the data is send, an $digest cycle occurs and changes also the scope data.
Note that there is a watcher on scope.value

Here is a basic example of how I change the data to send:

var config = {
  method : 'POST',
  url : "APIurl",
  data : 3,
  transformRequest : function(data){

    return data * 2;
  }
};

$http(config)
.then(function result(){
//Nothing is done is here
}, function(error){
//Nothing is done is here
});

scope that is send:

scope.value = 3
send scope.value and transform it (multiply by 2)
scope.value = 6 //without applying a change on the scope in the result function
//scope.value should stay equal to 3

Is this a normal behaviour?
Is there a way to transform the data I send trough http, but keep the scope as it is?

When debugging in chrome, I see the scope is changed when this piece of angularJS code is triggered:

I set /* Comments */ to comment code

traverseScopesLoop:
do { // "traverse the scopes" loop
if ((watchers = current.$$watchers)) {
  // process our watches
  length = watchers.length;
  while (length--) { /* **I assume it passes trough all watchers** */
    try {
      watch = watchers[length];
      // Most common watches are on primitives, in which case we can short
      // circuit it with === operator, only when === fails do we use .equals
      if (watch) {
        if ((value = watch.get(current)) !== (last = watch.last) &&
            !(watch.eq /* **watch.equal gets triggered. I think it is false** */
                ? equals(value, last)
                : (typeof value === 'number' && typeof last === 'number'
                   && isNaN(value) && isNaN(last)))) {
          dirty = true;
          lastDirtyWatch = watch;
          watch.last = watch.eq ? copy(value, null) : value; /* **watch.eq is false, value is applied**  */
          watch.fn(value, ((last === initWatchVal) ? value : last), current); /* **Goes trough this 2 times, the 2nd time the scope is changed** */
          if (ttl < 5) { /* **It tries this condition and goes back to watch.fn** */
            logIdx = 4 - ttl;
            if (!watchLog[logIdx]) watchLog[logIdx] = [];
            watchLog[logIdx].push({
              msg: isFunction(watch.exp) ? 'fn: ' + (watch.exp.name || watch.exp.toString()) : watch.exp,
              newVal: value,
              oldVal: last
            });
          }
        } else if (watch === lastDirtyWatch) {
          // If the most recently dirty watcher is now clean, short circuit since the remaining watchers
          // have already been tested.
          dirty = false;
          break traverseScopesLoop;
        }
      }
    } catch (e) {
      $exceptionHandler(e);
    }
  }
}   
@caitp
Copy link
Contributor

caitp commented Jul 30, 2015

Can you show us what you're doing with TransformData? Thanks.

@caitp
Copy link
Contributor

caitp commented Jul 30, 2015

Are you saying "this is changing scopes without triggering a digest and that's bad"? I'm not quite sure what you're saying with this one.

@caitp caitp added this to the 1.4.x milestone Jul 30, 2015
@Gregoirevda
Copy link
Author

changes made in TransformRequest are changing the scope in my view, because of an $digest cycle, and this is bad.

Which TransformData?

@Narretz
Copy link
Contributor

Narretz commented Aug 7, 2015

@Gregoirevda I cannot reproduce your example: http://plnkr.co/edit/l44pMzhehCxm1of2xj7X?p=preview

@Narretz Narretz modified the milestones: Purgatory, 1.4.x Aug 7, 2015
@Gregoirevda
Copy link
Author

Here is a Plunker showing the problem:
http://plnkr.co/edit/l7OGmHjBkXbAYwOjkla6?p=preview

I managed to NOT change the scope inside the transformRequest by making a copy of the data parameter.
This is done automaticaly when data parameter is passed by value, but not when it is passed by reference (an object)

transformRequest : function(data){
var copy = JSON.parse(JSON.stringify(data));
copy.id = null;
return copy;
}

I found it strange a copy isn't done automaticaly, if passed by value OR reference.

@Narretz
Copy link
Contributor

Narretz commented Jan 14, 2016

I think this is a real bug. The transformResponse fn makes a copy of the response, so the whole thing is cacheable. The only problem is a certain performance overhead, I assume.

@Narretz
Copy link
Contributor

Narretz commented Feb 5, 2016

On the other hand ... if you are transforming your data, shouldn't you know where this data is currently referenced and then make a copy yourself in the transformer? Copying by default is not the best idea, since our copy isn't equipped for all special cases (although the same cases might be an issue for toJson). It might also be a problem for large payloads.

@petebacondarwin
Copy link
Contributor

I believe that we should not make the copy by default.

In general it is bad practice to mutate an object that has been passed to you. Since you don't know who else relies upon that object. So I would expect that transformRequest functions to take responsibility for copying an object if they intend to mutate it.

@Narretz I don't suppose you have time to update the $http docs to make this clear?

@petebacondarwin petebacondarwin modified the milestones: Ice Box, Purgatory Feb 22, 2016
@Narretz
Copy link
Contributor

Narretz commented Feb 22, 2016

@petebacondarwin That shouldn't be a problem - I'll update the docs.

@Narretz Narretz self-assigned this Feb 22, 2016
@Gregoirevda
Copy link
Author

I thought it was strange I hadn't access to a angular.copy() of the data in the transform functions. Because the purpose of the functions (Request and Response) is to have a filter when data is going in and out of the application, to keep data IN and OUT separate. This way I can work with 2 data structures and have immediate access to the good structured data.

If, when data goes OUT, the filter affects also application data (IN)... it makes no sense.
But I might understand if data gets copied by default users won't be able to access their reference and change it also. But still... this is not the place to change the reference, only the copy to send to the server?

@petebacondarwin
Copy link
Contributor

Think of filters as being pure functions. They should have no side effects, which includes mutating the properties of the data passed in.

@petebacondarwin
Copy link
Contributor

Thanks @Narretz

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.