Friday, September 26, 2014

An Angular App Using a Polymer Element (Dart)


Thanks to a tip from James Hurford, I have a far better approach to working with the “wildly” incompatible Polymer.dart and Angular.dart libraries. Instead of hacking on a forked version of Angular.dart, as I did last night, I instead use Dart Pub's dependency_overrides option.

I am simply not going to start hacking on Angular.dart to get Polymer elements working inside an Angular.dart application—especially not as a solution for Patterns in Polymer. Overriding the minimal dependencies found from last night gives me the best chance of working with the most recent Polymer and Angular libraries to see if it is as all possible.

So I add those dependencies to my project's pubspec.yaml:
name: angular_example
dependencies:
  angular: any
  polymer: any
  angular_node_bind: any
dependency_overrides:
  args: '>=0.10.0 <0.12.0'
  code_transformers: '>=0.1.4+2 <0.3.0'
  html5lib: '>=0.10.0 <0.13.0'
dev_dependencies:
  unittest: any
transformers:
- polymer:
    entry_points:
    - web/index.html
    - test/index.html
I delete my pubspec.lock to be absolutely sure that I am getting a fresh run at this (a pub upgrade should do the same thing). Now when I run pub get, I find:
$ pub install
...
+ angular 0.14.0
...
+ polymer 0.15.0
...
Warning: You are using these overridden dependencies:
! args 0.11.0+1 (0.12.0+2 available)
! code_transformers 0.2.3+1
! html5lib 0.12.0
For reference, Angular.dart would otherwise restrict those three overridden dependencies as:
  args: '>=0.10.0 <0.11.0'
  code_transformers: '>=0.1.4+2 <0.2.0'
  html5lib: '>=0.10.0 <0.11.0'
OK, I have the Dart versions of Polymer and Angular installed in the same application. Now what?

Well, for tonight, I would be satisfied with a working version of the old Angular pizza building application that uses a custom Polymer element. This worked when it went into Patterns in Polymer. Hopefully it can live again.

I modify the Polymer setup as I know that version 0.15 likes it and retain the Angular tags and attributes:
<!doctype html>
<html ng-app lang="en">
  <head>
    <script src="packages/web_components/platform.js"></script>
    <link rel="import" href="packages/angular_example/elements/x-pizza.html">
    <script type="application/dart" src="main.dart"></script>
  </head>
  <body>
    <div class="container">
      <h1>Ye Olde Dart Pizza Shoppe</h1>
      <ng-view></ng-view>
    </div>
  </body>
</html>
Aside from the ng-app Angular attribute and the <ng-view> Angular tag, there is one other difference between this and a pure Polymer application: there is no export of package:polymer/init.dart to initialize Polymer. Instead that is done inside main.dart, which also initialized Angular.

At least it used to initialize Angular. Now it just generates errors in the Dartium console:
Exception: No top-level method 'ngBootstrap' declared.

NoSuchMethodError: method not found: 'ngBootstrap'
Receiver: top-level
Arguments: [...] 
No biggie. It seems that the Angular bootstrap process has changed. For now, I leave the initialization inside Polymer's run():
main() {
  initPolymer().run((){
    applicationFactory()
      .addModule(new PizzaStoreApp())
      .run();
  });
}
No doubt other stuff has changed, but I start with the old constructor for PizzaStoreApp and will work my way from there. What I find next is:
Exception: Module.DEFAULT_REFLECTOR not initialized for dependency injection.http://goo.gl/XFXx9G
This appears to be due to the lack of the Angular transformer in my pubspec.yaml. Despite what the documentation says, it would appear that this does more than aid building JavaScript. I add the Angular transformer:
name: angular_example
dependencies:
  angular: any
  polymer: any
  angular_node_bind: any
dependency_overrides:
  args: '>=0.10.0 <0.12.0'
  code_transformers: '>=0.1.4+2 <0.3.0'
  html5lib: '>=0.10.0 <0.13.0'
dev_dependencies:
  unittest: any
transformers:
- angular
- polymer:
    entry_points:
    - web/index.html
    - test/index.html
After a pub serve restart, there is much work to perform to update the Angular portion of my application to 0.14. But I eventually get it.

The application is intentionally “complex-ish” to ensure that I am not just getting by with a trivial example. Mostly this involves routing.

The main.dart file for my Angular & Polymer application starts with the usual imports for both libraries:
import 'package:polymer/polymer.dart';
import 'package:angular/angular.dart';
import 'package:angular/application_factory.dart';
import 'package:angular/routing/module.dart';
There used to be much initialization inside the main application module. Now it is just:
class PizzaStoreApp extends Module {
  PizzaStoreApp() {
    // Stuff used to happen here, now routing and binding happens later...
  }
}
I am unsure if I even need this anymore, but I will answer than another day.

The routing uses two partials, defined as:
void storeRouteInitializer(Router router, RouteViewFactory views) {
  views.configure({
    'start': ngRoute(
        defaultRoute: true,
        view: 'partials/home.html'
      ),
    'custom-pizza': ngRoute(
        path: '/pizza/custom',
        view: 'partials/custom.html'
      )
  });
}
That is relatively self-explanatory. I have two routes: the default route and the custom pizza route. Both use partial templates at the specified locations.

Finally, the Polymer and Angular initialization looks like:
main() {
  initPolymer().run((){
    var app = new PizzaStoreApp()
      ..bind(NgRoutingUsePushState, toValue: new NgRoutingUsePushState.value(false))
      ..bind(RouteInitializerFn, toValue: storeRouteInitializer);

    applicationFactory()
      .addModule(app)
      .run();
  });
}
It took a little time to figure out where the bind() calls go and what gets supplied to addModule(), but it mostly makes sense. What took me quite a while to figure out was how to disable push state. I really hate push state—especially for developing / reloading single page applications. But eventually I figured out how to bind the false value of NgRouteingUsePushState. There may be a cleaner way to accomplish this, but I am not going to worry about that now.

Because I have an Angular.dart 0.14 + Polymer.dart 0.15 application working:

Well, not quite working. I still need to double bind variables in Angular to Polymer attributes. But I am thrilled to have gotten this far tonight.


Day #195

1 comment:

  1. Ah so it does work. I'm tempted to reopen my quest to get Angular working inside Polymer now. Then, that's a scenario I really see no advantage to having, so probably not anytime soon :-)

    ReplyDelete