No more automatic $promise unwrapping

Like any Philadelphia sports fan says, "BOOOOO!!"

In AngularJS, I liked the cleanliness of the code:

$scope.objects = ObjectAdmin.query();

And as it automatically unwraps the promise, the data updates correctly on the view side, and everything works fine. But I started playing with Angular 1.3 and it no longer does this. Now you have to call:

ObjectAdmin.query().$promise.then(function(data){ $scope.objects = data; })

Which is just ugly. But I guess the team at Angular made the decision for a good reason, but I have to update all of my code. Booooo!!!

This became painfully obvious when attempting to group a multiple select options by a property that is bound as a foreign key, after the promise has resolved.

You can imagine this data structure:

object: {   name: "Test", objectClass: "a_foreign_key" }

objectClass: { key: "a_foreign_key", name: "Object Type" }

Probably easier to think about in a non abstract way (although, in my current project, "Object" is the actual name of the item I'm dealing with).

person: { id: 1, name: "Jason", professionId: 1 }

profession: { id: 1, name: "Software Developer" }

So, when I get the list of "persons", and the list of "profession", I would grab the reference to the profession referenced by the professionId in the person, then assign it to a new property on each person, $professionRef.

so person 1 would like like this:  { id: 1, name: "Jason", professionId: 1, $professionRef: { id: 1, name: "Software Developer" } }

THEN my select list would use the ng-options to this effect:

ng-options="person.name group by person.$professionRef.name for person in persons track by person.id" ng-model="office.bestSmellingDeveloper"

(The use of $professionRef and $resource no longer automatically removing $ properties on POST/PUT is another pain point for me :)

So at the time the $promise resolves, the select list is like F#@% YEAH DATA!!! And it binds itself, but $professionRef isn't updated yet. So it's not grouping by anything and you have a bland list of things. I use the chosen jquery plugin which just makes these look beautiful with bootstrap of course, and I get bummed when it looks fugly.

I realize this wouldn't have worked as is, even with $promise unwrapping, it all depends on when the SELECT chooses to bind its data, and it would typically be immediately after the promise resolves. Really where this affected the code the most was in the chosen plugin I wrote, which looks like this now that I'm not binding promises to it anymore.

    module.directive("ngChosen", function($parse, $timeout){
        return {
            restrict: "A",
            require: "ngModel",
            link: function(scope, element, attrs, ngModel){

                scope.$watch(attrs["ngChosen"], function(){    
                    $timeout(function(){ element.trigger("chosen:updated"); });
                });

                scope.$watch(attrs["ngModel"], function(){
                    $timeout(function(){ element.trigger("chosen:updated"); })
                });
                element.chosen();
            }
        }
    });

But used to look like this:

    module.directive("ngChosen", function($parse, $timeout){
        return {
            restrict: "A",
            require: "ngModel",
            link: function(scope, element, attrs, ngModel){
                var chosen = scope.$eval(attrs["ngChosen"]);

                if (chosen.$promise) {
                    chosen.$promise.then(function(){ $timeout(function(){ element.trigger("chosen:updated"); }); })
                }
                else {
                    scope.$watch(attrs["ngChosen"], function(){    
                        $timeout(function(){ element.trigger("chosen:updated"); });
                     });
                }

                scope.$watch(attrs["ngModel"], function(){
                    $timeout(function(){ element.trigger("chosen:updated"); })
                });
                element.chosen();
            }
        }
    });

But now that Angular is no longer automatically unwrapping promises... well, I guess I could keep the promise unwrapping in the chosen plugin, just in case I have a simple case that I need to bind to it (hardly ever the case), but since I won't be binding $promise objects to select lists most of the time, I can just say I won't bind any even though it's easy. Because I'll be used to unwrapping them manually to handle cases like the aforementioned.

Enjoy!

Doing is the best form of learning

REST.  It's one of those things that most of us developers typically deal with on the client side of things, or if we deal with them on the server side, we're using an already written API which "REST-ifies" our data. It was somewhat of a mystery to me, and a challenge, to know how to implement an abstract RESTful interface. I had written the webserver I use in node.js which supports all kinds of things, but hadn't yet added REST support. Until last night!

It wasn't too much of an undertaking, I guess I implemented the webserver in such a way that could be easily extended with REST. That's good, go "past Jason".

Basically the server gets the request, attempts to rewrite the URL given the site's configuration of URL Rewrites (which I wrote), then it gets the content (server side processing), and writes the content on the response stream, along with doing cookie and header processing, g-zipping, cache headers, etc. Nearly everything a mature web server should do.  It's been around since March 2011 and I always go back to making it better.

I started adding REST to it last night around 10pm, and by 12:30 I was writing code against it using AngularJS.  The REST format is inspired by AngularJS, in that you can provide parameters expected in the format :id   (colon - name).

I always start with how I want to write code.  This, I find, is a very important aspect of how I architect things.  I don't want to write a whole bunch of code that I'll have to write each time I want to use the new feature I am adding. So I START with the code that I'll be writing to use the new feature. If this hasn't yet sunk in as to how important I think this is, let me add this sentence... There. It's very important to me and how I architect.

I know the architecture of my web server, and know that you can't just reference things inside of it that haven't been given a public interface. It really has no public interface. The web applications implement the interface to work within the web server. I am looking at a way to decouple them but for now, this is how it is. The "params" array was added in that way because of this.

this.get = {
    path: "/:id",
    handler: function(db, id, qs, callback){
            engine.getObject(db, id, callback);
        },
    method: "GET",
    params: [
        function(context){ return context.db; },
        "id"
    ]
}

 

So in my site code, I'm registering a "get" method that takes the id of an object, looks in the database, and returns it. A URL for the call may look like this: /resource/objects/53f810db8a8cda084e000001

Here's some node.js code that comes from my "resource" module. Within the resource module, you register resources and later check the URL and http method against the current resources, to see if this is a resource / REST call.

this.registerResource = function(domain, name, resource){
    var r = /:([a-zA-Z0-9]+)/g;    
    var m = null;
    for (var i in resource){
        var res = resource[i];
        var regexString = globalPart + name + res.path;
        while ((m = r.exec(res.path))!=null){
            regexString = regexString.replace(":" + m[1], "(.*?)");
        }
        var local = { domain: domain, name: name, method: res.method, regex: new RegExp(regexString), handler: res.handler, path: res.path, params: res.params };
        resources.push(local);
    }
}

For the resource, for which the above "get" code is just one method on a resource, find all the methods, replace the URL with a regex. Instead of the "path" which would be "objects/:id", it creates "/resource/objects/(.*?)", stores the original path, the method to handle it, the http request method (GET, POST, PUT, DELETE currently supported), and the params array.

When a request is made, find the resource with the following code, if no resource is found, or no resources on the current domain that match the HTTP Method, it's a standard request.

this.getResource = function(domain, url, method){
    var domainResources = resources.filter(function(r){ return r.domain == domain && r.method == method });
    if (domainResources.length == 0) return null;
    var resource = null;
    domainResources.forEach(function(res){
        if (url == globalPart + res.name + res.path) resource = res;  // prefer exact matches first
        else if (resource == null && res.regex.test(url)){
            resource = res;
        }
    })

    return resource;
}

The next methods call the resource handler. For GET / DELETE calls, the requestData is just the querystring, for PUT / POST calls, this will be the form data as parsed by the POST parser in node.

this.extractParamMap = function(url, resourcePath){
    var m = null, map = {};
    var urlParts = url.split("/");
    var resParts = resourcePath.split("/");
    for (var i = 0; i < resParts.length; i++){
        if (resParts[i].indexOf(":") == 0){
            map[resParts[i].substring(1)] = urlParts[i];
        }
    }
    return map;
}

this.handleResource = function(resource, url, context, requestData, callback){
    var params = [];
    var paramMap = this.extractParamMap(url, globalPart + resource.name + resource.path);

    for (var i = 0; i < resource.params.length; i++){
        if (typeof(resource.params[i]) == "function"){
            params.push(resource.params[i](context));
        }
        else if (typeof(resource.params[i]) == "string"){
            params.push(paramMap[resource.params[i]]);
        }
    }

    params.push(requestData);
    params.push(callback);

    resource.handler.apply(resource, params);
}

The code within the webserver which was modified to process resources looks like this.  Determine if it's a resource or standard request. Call accordingly.

        if (req.method == "POST" || req.method == "PUT"){
            post.parseForm(req, function(formData){
                if (loadedResource != null){
                    resource.handleResource(loadedResource, url, site, formData, function(data){
                        var content = {};
                        content.contentType = "application/json";
                        content.content = JSON.stringify(data);
                        finishedCallback(content);
                    });
                }
                else if (handler != null){
                    query.form = formData;
                    handler.handlePost(path, query, site, req, function(content){
                        if (jsonRequest) content.contentType = "application/json";
                        finishedCallback(content);
                    });
                }
            })
        }
        else if (req.method == "GET" || req.method == "DELETE"){
            if (loadedResource != null){
                resource.handleResource(loadedResource, url, site, query.querystring, function(data){
                    var content = {};
                    content.contentType = "application/json";
                    content.content = JSON.stringify(data);
                    finishedCallback(content);
                })
            }
            else if (handler != null){
                handler.handle(path, query, site, null, req, function(content){
                    if (jsonRequest) content.contentType = "application/json";
                    finishedCallback(content);
                });
            }
        }
        else {
            finishedCallback({contentType: "text/html", content: url + " - No handler found", statusCode: 404});
        }

In AngularJS, with the $resource module, this is cake.

        return $resource("/resource/objects/:id", {}, 
            { 
                list: { method: "GET", isArray: true },
                get: { method: "GET" },
                save: { method: "POST" },
                update: { method: "PUT" },
                remove: { method: "DELETE" }
            }
        );

 

That's it!  Later on I might find I need other things, but that's all the code that was required for now for handling resource / REST style methods. I will have a site up in a few weeks / months that will use this heavily. Then you can see it in action!

You know you're a code snob when

Today I wrote a method that takes an updated object from the server and merges it with the current user's objects. These objects are in an hierarchy of client -> environment -> credentials.  So I wrote a function that sees that a credential has been updated (deleted, inserted, updated), finds the client and environment that it belongs to, if it doesn't have it, it "pushes" it, otherwise if it's not a delete, it updates it, and if it's a delete it removes it.  The other users can be editing clients or environments as well, so these have to work the same way.

The commit message for this change is as follows:

Update other users' data with data updated from the server.  Need to clean up the compare function, in client controllers line 65 to 118

Yep, 53 lines of non-trivial code and I'm all like "That is WAY too long..."   I will write a generic utility function that will do it for me. I found angular.copy to be very useful, I just need something to tell me what's changed.

I will need that function to keep track of users logging in and out as well, as it updates almost immediately for other users when someone logs out, and it uses way more code than I feel should be necessary as well.... If javascript had a universal "findIndex" method, it would be helpful, but I want to make it a 2-3 liner for these updates without writing my own "findIndex" function. More specialized...