Thursday, October 24, 2013

Routing in Angular.dart


I can resist no longer. Today: routing in Angular.dart. I don't know that routing in AngularJS is any more interesting than it is in other MV-whatever libraries and frameworks in JavaScript land, so I have no extra expectations with it in the Dart port. Still, routing is just cool, so today I have a go.

To date, I have been able to accomplish all of my Angular.dart exploration with the most recent Pub package (version 0.0.2 at the time of this writing). That lacks routing, but happily Dart makes it trivial to work with local repository versions of libraries.

I have already cloned the repository:
git clone https://github.com/angular/angular.dart.git
So I only need to pull down the latest commits. Nothing needs to happen to the repository to make it usable as a package. Back in my angular-calendar repo, I update pubspec.yaml to point to the local copy of Angular.dart (both reside in the same $HOME/repo directory):
name: angular_calendar
dependencies:
  angular:
    path: ../
  # Server dependencies
  dirty: any
  uuid: any
  ansicolor: any
dev_dependencies:
  unittest: any
I pub upgrade to upgrade to the most recent versions of dependencies—including any that the master branch of Angular.dart now require.

I have to update the my main.dart entry point to use the more angular-y ngBootstrap (instead of bootstrapAngular()):
import 'package:angular/angular.dart';
import 'package:angular_calendar/calendar.dart';
main() {
  var calendar = new AngularModule()
    ..type(AppointmentBackend)
    ..type(AppointmentController);

  ngBootstrap(module: calendar);
}
With the preliminaries out of the way, I need to inject a RouteInitializer into my Angular module. This is accomplished with the same type() method that all di.dart modules use, but with a slight twist. I pass the vanilla RouteInitializer class and then tell type() that it is implemented by my custom router class (that I need to write next):
import 'package:angular/angular.dart';
import 'package:angular/routing/module.dart';
import 'package:angular_calendar/calendar.dart';
main() {
  var calendar = new AngularModule()
    ..type(AppointmentBackend)
    ..type(AppointmentController)
    ..type(RouteInitializer, implementedBy: CalendarRouter);

  ngBootstrap(module: calendar);
}
I have also imported Angular routing to get this working. And yes, the implementedBy named parameter seems to be required.

As for the CalendarRouter, it does implement RouteInitializer, which mandates that I build my routes in an init() method:
class CalendarRouter implements RouteInitializer {
  void init(Router router, ViewFactory view) {
    router.root
      ..addRoute(
          defaultRoute: true,
          name: 'day-list',
          enter: view('partials/day_list.html')
        )
      ..addRoute(
          name: 'day-view',
          path: '/days/:dayId',
          enter: view('partials/day_view.html')
        );
  }
}
The router and view that are injected into my router are used to add routes to the application and associate views to each. The default route will enter the partials/day_list.html view. A route of something like /days/42 will enter the partials/day_view.html view (with the dayId parameter assigned.

The easiest way to get routing working is to change my entry page to include the <ng-view> tag, which turns routing on:
<!doctype html>
<html ng-app>
  <head>
    <title>Angular Calendar</title>
    <script type="application/dart" src="main.dart"></script>
    <script src="packages/browser/dart.js"></script>
    <link href="css/bootstrap.min.css" rel="stylesheet" media="screen">
  </head>
  <body>
    <div class="container">
    <h2>Angular Calendar</h2>

    <ng-view></ng-view>

    </div>
  </body>
</html>
I believe that it is necessary to add a <script> tag to get routing working in AngularJS. That is taken care of by the more appropriate code import in the Dart version of Angular.

With <ng-view> now routing and partials/day_list.html designated as my default route, I need that partial. I copy the controller-bound HTML that used to be in index.html into that file as:
<div appt-controller>
  <ul class="unstyled">
    <li ng-repeat="appt in day.appointments">
      <a href="/days/{{appt.id}}">{{appt.time}} {{appt.title}}</a>
      <a ng-click="day.remove(appt)">
        <i class="icon-remove" ></i>
      </a>
    </li>
  </ul>
  <form onsubmit="return false;" class="form-inline">
    <input ng-model="day.newAppointmentText" type="text" size="30"
           placeholder="15:00 Learn Dart">
    <input ng-click="day.add()" class="btn-primary" type="submit" value="add">
  </form>
</div>
That is still bound to the AppointmentController by virtue of the appt-controller selector on the containing <div> and the @NgDirective that is still on the controller:
@NgDirective(
  selector: '[appt-controller]',
  publishAs: 'day'
)
class AppointmentController {
  // ...
}
And that works! When I load up my application, I still get my day view of appointments:



I have linked the individual appointments to <a href="/days/{{appt.id}}">{{appt.time}} {{appt.title}}</a>, which ought to work with the day-view route that I created above. And it does. I define the partials/day_view.html template as:
<div day-view-controller>
  <dl class="dl-horizontal">
    <dt>Title</dt>
    <dd>{{appt.title}}</dd>
    <dt>Time</dt>
    <dd>{{appt.time}}</dd>
  </dl>
</div>
And inject a new DayViewController into my Angular module:
main() {
  var calendar = new AngularModule()
    ..type(AppointmentBackend)
    ..type(AppointmentController)
    ..type(DayViewController)
    ..type(RouteInitializer, implementedBy: CalendarRouter);

  ngBootstrap(module: calendar);
}
Lastly, I define a skeleton of that controller:
@NgDirective(
  selector: '[day-view-controller]',
  publishAs: 'appt'
)
class DayViewController {
  String title = 'Default Title';
  String time = '08:00';
}
And I have the route working:



It took a while for my to figure out the addRoute() format, but I think it makes pretty decent sense. I still rather miss the when() routes from AngularJS, but it is way to early to have a real opinion. I have been unable to figure out how to get routing parameters injected into my controllers (e.g. the dayId value), so I will pick up there tomorrow. There also seem to be alternate ways to establish routes that I may explore as well.

But all in all, routing in Angular.dart has not disappointed so far!


Day #914

2 comments:

  1. "
    With routing and since partials/view_list.html is my default route, I need that partial. I copy the controller-bound HTML that used to be in index.html into that file as:
    "

    There is typo. view_list.html should be day_list.html

    ReplyDelete