You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
This repository was archived by the owner on Apr 12, 2024. It is now read-only.
Repository navigation
This repository was archived by the owner on Apr 12, 2024. It is now read-only.
TransformRequest in $http is changing scope #12468
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);
}
}
}
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)
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.
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.
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?
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?
Activity
caitp commentedon Jul 30, 2015
Can you show us what you're doing with TransformData? Thanks.
caitp commentedon 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.
Gregoirevda commentedon Jul 30, 2015
changes made in TransformRequest are changing the scope in my view, because of an $digest cycle, and this is bad.
Which TransformData?
Narretz commentedon Aug 7, 2015
@Gregoirevda I cannot reproduce your example: http://plnkr.co/edit/l44pMzhehCxm1of2xj7X?p=preview
Gregoirevda commentedon Aug 10, 2015
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.
1 remaining item
Narretz commentedon 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 commentedon 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 commentedon Feb 22, 2016
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
$httpdocs to make this clear?Narretz commentedon Feb 22, 2016
@petebacondarwin That shouldn't be a problem - I'll update the docs.
Gregoirevda commentedon Feb 22, 2016
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 commentedon Feb 22, 2016
Think of filters as being pure functions. They should have no side effects, which includes mutating the properties of the data passed in.
docs($http): add a note about modifying data in transformRequest
docs($http): add a note about modifying data in transformRequest
docs($http): add a note about modifying data in transformRequest
docs($http): add a note about modifying data in transformRequest
petebacondarwin commentedon Feb 25, 2016
Thanks @Narretz