Recently I was working with Azure and handling the asynchronous request callback using an Angular promise. This presented me with a couple of difficulties:
- When calling .resolve on the deferral, it was never firing the .when statement on the code that was holding the promise so I never got may data loaded correctly from my factory
- Inside the deferral callback code in a class, any calls to the classes properties using the 'this' operator were failing with an undefined or null reference error
Calling code:
this.dataAccess.BeginGetRegistrations().then(function (result: Classes.RegistrationDTO[]) {
for (var i in result) {
var registrationDTO: Classes.RegistrationDTO = result[i];
var registration: Registration = new Registration(registrationDTO.Id, this.dataAccess);
registration.LoadRegistration(registrationDTO.ScreenName, registrationDTO.Email, registrationDTO.Zip, registrationDTO.Gender, registrationDTO.BirthDate);
this.Add(registration);
}
});
Asynchronous function in my data access class:
public BeginGetRegistrations(): ng.IPromise<Classes.RegistrationDTO[]>{
var deferral = this.service.defer<Classes.RegistrationDTO[]>();
var registrations = DataAccess.client.getTable('Registration');
registrations.read().done(function (results) {
var returnValue: Classes.RegistrationDTO[] = [];
var registration: Classes.RegistrationDTO;
for (var i in results) {
var result = results[i];
registration = current.LoadResult(result);
returnValue.push(registration);
}
deferral.resolve(returnValue);
}, function(err) {
throw err.toString();
});
return deferral.promise;;
}
I did a little digging around it seemed obvious. The call to resolve on the deferral needs to happen in a context that Angular is aware of. In this case, calling resolve inside $apply method on the $rootScope should solve it so the .when statement on the calling code fires. Since I was way down in the bowls of a class inside a module I had Angular inject the $rootScope into my factory which in turn set it inside my data access class on creation (constructor). Now my data access class has a reference to the $rootScope in a class level variable called scope.
I rewrote my code hoping to get the .when to fire like this:
public BeginGetRegistrations(): ng.IPromise<Classes.RegistrationDTO[]>{
var deferral = this.service.defer<Classes.RegistrationDTO[]>();
var registrations = DataAccess.client.getTable('Registration');
registrations.read().done(function (results) {
var returnValue: Classes.RegistrationDTO[] = [];
var registration: Classes.RegistrationDTO;
for (var i in results) {
var result = results[i];
registration = current.LoadResult(result);
returnValue.push(registration);
}
this.scope.$apply(deferral.resolve(returnValue));
}, function(err) {
throw err.toString();
});
return deferral.promise;;
}
As soon as I did this, the second problem raised its head. That is, I couldn't get access to the 'this' keyword and was failing with an undefined or null reference. This problem manifested in both the BeginGetRegistration method and the calling code. The solution was simple. In each of these methods set a local variable equal to the class level 'this' variable so the callback function has access to it and all was well with the world. My new corrected and working functions:
Calling code:
var current: any = this;
this.dataAccess.BeginGetRegistrations().then(function (result: Classes.RegistrationDTO[]) {
for (var i in result) {
var registrationDTO = result[i];
var registration: Registration = new Registration(registrationDTO.Id, current.dataAccess);
registration.LoadRegistration(registrationDTO.ScreenName, registrationDTO.Email, registrationDTO.Zip, registrationDTO.Gender, registrationDTO.BirthDate);
current.Add(registration);
}
});
Asynchronous function in my data access class:
public BeginGetRegistrations(): ng.IPromise<Classes.RegistrationDTO[]>{
var deferral = this.service.defer<Classes.RegistrationDTO[]>();
var registrations = DataAccess.client.getTable('Registration');
var current: any = this;
registrations.read().done(function (results) {
var returnValue: Classes.RegistrationDTO[] = [];
var registration: Classes.RegistrationDTO;
for (var i in results) {
var result = results[i];
registration = current.LoadResult(result);
returnValue.push(registration);
}
current.scope.$apply(deferral.resolve(returnValue));
}, function(err) {
throw err.toString();
});
return deferral.promise;;
}
With these fixes in place my Azure mobile code was loading beautifully, asynchronously and Angular binding was handling the data coming back and displaying on the UI exactly as it should.