Sunday, October 20, 2013

HTTP Requests in Angular.dart


I have a rough outline of things that I want to explore in Angular.dart. Angular.dart is, of course, the Dart port of AngularJS, the framework that's all the rage. Next on that list is routing, but that is yet to be implemented (it is part of the active milestone, so maybe someday soon). With routing off the table, I move onto the next item on the list: HTTP requests.

The JavaScript version of Angular has a very Dart-like feel to it, thanks to its promises and the $http service that is injected into controllers. So I do not expect too many surprises. That said, I am not quite sure how this will translate the class based approach to controllers that I liked so much last night...

To find out, I start by creating a REST-like backend. I already have one of these for dart-comics, which is the companion code repository for the Dart for Hipsters book. The backend uses a dirt-simple noSQL persistent store, dart dirty. I copy the server from dart-comics to my angular-calendar repository in which I am currently working. I change the resources in the server from “comics” to “appointments” (which means I may want to convert this simple server into a package some day soon).

Anything not in the /appointments URL-space will be served up from my repository's web subdirectory, making this a drop-in replacement for pub serve. After stopping the simple HTTP server built into Dart Pub, I start this server:
➜  angular-calendar git:(master) ✗ dart server.dart
Server started on port: 8000
...
A quick check reveals that my Angular day-view for appointments is still working:



Before I try to use an Angular HTTP service, I think I will try this in plain-old Dart. As I mentioned, AngularJS has a Dart-like feel already—perhaps the HTTP service normally used in Angular applications is not necessary.

In the controller class, I start by removing the default “Wake up” entry that I had been associating with the appointments list. I also create a constructor that will be responsible for loading appointments from my REST-like server:
class AppointmentCtrl {
  List appointments;
  // ...
  AppointmentCtrl() {
    _loadAppointments();
  }
  // ...
}
In the private _loadAppointments() method, I perform a simple HTTP request for the /appointments resource:
  _loadAppointments() {
    HttpRequest.
      getString('/appointments').
      then((responseText){
        appointments = JSON.decode(responseText);
      });
  }
When the response comes back, I decode the JSON and assign the results to the list of appointments. It does not get much simpler than that.

Of course, I am now left with no appointments in the application since my noSQL store is empty. To persist entries that I make, I need to update the add() method from last night to include an HTTP POST:
  void add() {
    var newAppt = fromText(newAppointmentText);
    appointments.add(newAppt);
    newAppointmentText = null;
    HttpRequest.
      request(
        '/appointments', 
        method: 'POST', 
        sendData: JSON.encode(newAppt)
      );
  }
And, just like that, I am able to add records that persist in the server after a page reload:



Pfft. That was pretty easy. I seriously doubt the service approach is going to improve on this much, but I will give it a go anyway.

Working from the top down, I start in the main() entry point for my application by adding a new “type” to my Angular module—the ServerCtrl:
main() {
  var module = new AngularModule()
    ..type(ServerCtrl)
    ..type(AppointmentCtrl);

  bootstrapAngular([module]);
}
(n.b. bootstrapAngular() will be changing to ngBootstrap() in the next release)

The order in which the “types” are added to the module does not seem to matter. What does matter is the declared type for the constructor parameters of the classes. For the ServerCtrl, I declare an Http instance variable to be passed to the constructor:
class ServerCtrl {
  Http _http;
  ServerCtrl(this._http);
  // ...
}
That alone seems to be sufficient to get Angular.dart to inject the correct thing, which is pretty cool. I may have to dig into the code to figure out how they do that. Regardless, once I have my Http instance set, I can use it. For example, I can create an init() method that can be called by the appointments controller to initialize the list of appointments that have been persisted on the server:
class ServerCtrl {
  Http _http;
  ServerCtrl(this._http);

  init(AppointmentCtrl cal) {
    _http(method: 'GET', url: '/appointments').
      then((HttpResponse res) {
        res.data.forEach((d) {
          cal.appointments.add(d);
        });
      });
  }
}
Calling the _http object like that is a little more verbose than the getString() method on HttpRequest, though I do get the automatic conversion of the JSON response in the data property. And, if life in AngularJS has taught me anything, this should prove to be more testable as well (but that's a question for tomorrow).

I am not quite done, because now I need to get the AppointmentCtrl instance to call this init() method in the server controller. This turns out to be pretty easy thanks to that declared type trick. In the AppointmentCtrl constructor, I declare that it takes one argument—an instance of ServerCtrl. With that, Angular.dart will inject the active instance of ServerCtrl into the constructor so that I can invoke the init() method:
class AppointmentCtrl {
  // ...
  AppointmentCtrl(ServerCtrl server) {
    server.init(this);
  }
  // ...
}
And that works! Starting from the main() entry point, Angular is able to create an instance of my server controller and inject it into the constructor of the appointment controller when it creates an instance of that class. The appointment controller then injects itself into the server's init() method to load the initial list of appointments.

That is a lot of injection and I am not completely convinced that it is an improvement from a code clarity point of view. But it is a definite improvement in separating concerns, which should lead to improved testing and maintainability. I will put that theory to the test tomorrow.


Day #910

3 comments:

  1. This is very much work in progress, but are you familiar with: https://github.com/angular/angular.dart.tutorial/wiki

    ReplyDelete
    Replies
    1. Yup. That's some good stuff. Very promising and ought to really help newcomers in the future. It's an exciting time for Angular.dart :)

      Delete
  2. Very-very useful! The angular.dart.tutorial doesn't explain the dependency injection trick explicitly. Thanks

    ReplyDelete