SOLUTION FOUND
As it turns out the simplest solution ever was to use simply forgo any of the following code and do this
var posts = /*All the queries that will update your list*/
$q.all(posts).then(function(x) {
requeryData();
});
The reason it wasn't working for me was that the add requests were missing a return statement causing them to be undefined and allowing the .then() to fire before the response was actually received.
I am going to leave all my code snippets below just in cause anyone can learn from my mistake or how to use the getchanges endpoiunt.
DO NOT USE THE SOLUTION BELOW
I have build an angular web application in SP2013 which loads data in from a list and displays it nicely in a table and then allows users to manipulate that table through the app and then submit the changes after they are finished. Once they are done with the changes we don't want to close the app and want absolute confirmation that the items have been affected so we re-query the list and build the table again... Just like starting over. The problem is if they make more than 3 or 4 changes some of the changes don't get pulled in the requery. My guess is the query happens faster than the server can actually MAKE the changes.. If you wait a second and refresh the page it queries the changes just fine.. So I suppose its a timing issue. The server returns a status 200 when it receives the POST properly but by the nature of HTTP requests, there is no further communication after that so you can't tell the app when the server is finished its queue. (as far as I know)
I've been trying to devise some way to use SP.ChangeTokens.. But I honestly can't figure out quite how they work. Here is my function so far.
//Inside a listService angular service I wrote
this.getNewToken = function(guid) {
var ticks = ((new Date().getTime() * 10000) + 621355968000000000);
return {'__metadata':{'type':'SP.ChangeToken'},'StringValue':"1;3;"+guid+";"+ticks+";-1"};
}
this.tokenize = function(list, token) {
if(token.length==0){alert("SET TOKEN BEFORE CALLING TOKENIZE");return;}
var payload = {}
payload = {'query':{'__metadata':{'type':'SP.ChangeQuery'},'Add':'True','Item':'True','Update':'True',"DeleteObject": 'True','ChangeTokenStart':token}}
var promise = authService.setFormDigest()
.then(function(formDigest) {
var URLString = list.URL + "_api/web/lists/GetByTitle('" + list.Name + "')/getchanges";
var innerPromise = $http({
method: "POST",
url: URLString,
headers:
{
"Accept": "application/json;odata=verbose",
"content-type": "application/json;odata=verbose",
"X-RequestDigest": formDigest,
},
data: payload
})
.error(function(data, status, headers, config) {
alert("Error " + data.error.code + " - " + data.error.message.value);
})
.then(function(response) {
return response.data.d.results;
});
return innerPromise;
});
return promise;
}
Usage:
//Inside of testing controller I made
var list =
{
"Name":"EwadData",
"URL":"/bpa/eWAD/"
}
var token = ":)";
this.setToken = function()
{
var ticks = ((new Date().getTime() * 10000) + 621355968000000000);
token = {'__metadata':{'type':'SP.ChangeToken'},'StringValue':"1;3;ba8dbc6e-8b4e-4f2a-b6cc-a21e913a8c68;"+ticks+";-1"};
}
this.checkToken = function()
{
console.log(token);
}
this.getChanges = function()
{
console.log("getting changes...");
listService.tokenize(list, token).then(function(response){
console.log(response);
var test = {
'total':response.length,
'adds':response.filter(function(x){return x.ChangeType==1}).length,
'editss':response.filter(function(x){return x.ChangeType==2}).length,
'removes':response.filter(function(x){return x.ChangeType==3}).length
}
console.log(test);
});
}
HTML:
<!--Inside a testing view I made-->
<button type="button" ng-click="testing.setToken()">Set Token</button>
<button type="button" ng-click="testing.checkToken()">Check Token</button>
<button type="button" ng-click="testing.getChanges()">Get Changes</button>
OUTPUT:
//From setToken() response//
//From checkToken() response//
1;3;ba8dbc6e-****-4f2a-b6cc-a21e913a8c68;636065283318500000;240880
//From testing.getChanges() response immediately//
[]
//From testing.getChanges() response after adding 1 item, removing 1 item, editing 1 item
[Object, Object]
0:
ChangeType: 1
ItemId: 443
ListId: "ba8dbc6e-****-4f2a-b6cc-a21e913a8c68"
SiteId: "5ad6a2a2-****-40b1-9a1c-0b7e1cd91e1b"
Time: "2016-08-11T19:17:52Z"
WebId: "03aa3980-849a-4dd9-8ca9-3a73438d0e12"
__metadata: Object
1:
ChangeType:2
ItemId:99
ListId:"ba8dbc6e-8b4e-4f2a-b6cc-a21e913a8c68"
SiteId:"5ad6a2a2-d3b4-40b1-9a1c-0b7e1cd91e1b"
Time:"2016-08-11T19:32:33Z"
WebId:"03aa3980-849a-4dd9-8ca9-3a73438d0e12"
2:
ChangeType: 3
ItemId: 442
ListId: "ba8dbc6e-8b4e-4f2a-b6cc-a21e913a8c68"
SiteId: "5ad6a2a2-d3b4-40b1-9a1c-0b7e1cd91e1b"
Time: "2016-08-11T19:18:28Z"
WebId: "03aa3980-849a-4dd9-8ca9-3a73438d0e12"
__metadata: Object
add(1), edit(2), delete(3)
I figure this function needs to do a few things.. first get the most recent change token when you first open the program which is what I do with the first URLString.
Next you have to store all the changes you make in the UI. which I am doing outside of this function.
Then once the user submits you have to continuously call this function and pass it the stored token keep rechecking the changes since the you opened the app and compare them to your stored changes by comparing the ChangeType field..
Once the stored changes equal the response to the /getchanges query you can THEN move on and requery the whole table..
Lets say I queue up 10 POSTs using angular $q service like so..
var posts = {}
for(i in tenThings)
{
posts.push(/*Some POST Query here like a list update*/);
}
$q.all(posts).then(function(responses) {
console.log(responses);
var validator = $interval(function() {
if(/*Do your compare here*/)
$interval.cancel(validator);
requeryData();
}
}, 100);
});
it just seems unelegant to just keep re-querying..? How you do gracefully exit out of the infinite loop if something goes wrong? What if this get changes query takes more time than the server? It just seems... Messy!
I also tried tinkering with the...
/getlistitemchangessincetoken
endpoint with no success and much messier XML repsonses...
I would really appreciate any help solving this issue. Even just a push in the right direction.