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

TransformRequest in $http is changing scope #12468

Closed

Description

@Gregoirevda

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

Activity

  1. caitp commented on Jul 30, 2015

    @caitp
    Contributor

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

  2. caitp commented on Jul 30, 2015

    @caitp
    Contributor

    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.

  3. added this to the 1.4.x milestone on Jul 30, 2015
  4. Gregoirevda commented on Jul 30, 2015

    @Gregoirevda
    Author

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

    Which TransformData?

  5. Narretz commented on Aug 7, 2015

    @Narretz
    Contributor
  6. modified the milestones: Purgatory, 1.4.x on Aug 7, 2015
  7. Gregoirevda commented on Aug 10, 2015

    @Gregoirevda
    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.

  8. 1 remaining item

  9. Narretz commented on Jan 14, 2016

    @Narretz
    Contributor

    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.

  10. Narretz commented on Feb 5, 2016

    @Narretz
    Contributor

    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.

  11. petebacondarwin commented on Feb 22, 2016

    @petebacondarwin
    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?

  12. Narretz commented on Feb 22, 2016

    @Narretz
    Contributor

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

  13. self-assigned this
    on Feb 22, 2016
  14. Gregoirevda commented on Feb 22, 2016

    @Gregoirevda
    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?

  15. petebacondarwin commented on Feb 22, 2016

    @petebacondarwin
    Contributor

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

  16. added 2 commits that reference this issue on Feb 24, 2016
    263b434
    0749eb4
  17. added 2 commits that reference this issue on Feb 24, 2016
    18f055e
    129783a
  18. petebacondarwin commented on Feb 25, 2016

    @petebacondarwin
    Contributor

    Thanks @Narretz

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

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions