Friday, November 1, 2013

Angular Promises in TypeScript

If you're like me and love typed languages, TypeScript seems like a great thing and it is.  Though there are some complications to using it along with some of the existing frameworks that are out there.  A lot of this comes from using modules and classes in TypeScript, which you probably are if you are using typeScript in the first place.  When using TypeScript and Angular it can sometimes be hard to get access to things like Angular's $scope deep within the bowels of your classes.

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
What to do?  Here was my original non working code:

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.

No comments:

Post a Comment