Sunday, January 19, 2014

Day 1,001: Getting Started with Angular and Polymer (Dart)


Up today, I start experimenting with Angular.dart and Polymer.dart. For tonight, I would like to start with a simple Angular application and see what, if anything, I need to do in order to use my amazing <x-pizza> Polymer:



I actually need to go back and fix that Polymer up some, but I think the learning ground is more fertile at this point with Angular than with refactoring or converting to JavaScript. Still, I do need to do both before <x-pizza> is Patterns in Polymer worthy. Anyway…

Angular. I love it. I am probably a horrible person to ask an opinion on libraries and frameworks. Most people seem to be absolutely certain that some are better than others. I am unable to make decisions like that until I work with a library enough that it starts to affect how I think about other tools. By that time, I have almost always found some great use-cases for the tool at hand, leaving me hard pressed to dislike outright.

So, for a guy that co-authored Recipes with Backbone and who still loves coding in Backbone.js, I also love Angular. Mostly I am glad to know both as I think they have overlap, but also different use-cases.

The use case that I would like to explore in Angular tonight is an online pizza store. I have an <x-pizza> Polymer, which seems like it could be put to nice use throughout such an app.

I start with a pubspec.yaml that includes both the Angular and Polymer packages, but does not include the usual Polymer transformer:
name: angular_example
dependencies:
  angular: any
  polymer: any
dev_dependencies:
  unittest: any
# transformers:
# - polymer:
#     entry_points: web/index.html
I am unsure how the transformer would interact with Angular, so I leave that out of the equation tonight.

While working with raw Polymer, I have needed to include initialization code in the web page:
    <!-- Load component(s) -->
    <link rel="import" href="packages/angular_example/elements/x-pizza.html">
    <!-- Load Polymer -->
    <script type="application/dart">
      export 'package:polymer/init.dart';
    </script>
I do not think that will work in an Angular application, which needs to spin itself up without another Dart script gumming up the works. I don't think that will work, but I am not positive. To find out for sure, I set up my page with both Angular and Polymer initialization (Angular stuff goes in main.dart):
<!DOCTYPE html>
<html ng-app lang="en">
  <head>
    <title>Pizza Store</title>

    <script type="application/dart" src="main.dart"></script>

    <!-- Load component(s) -->
    <link rel="import" href="packages/angular_example/elements/x-pizza.html">
    <!-- Load Polymer -->
    <script type="application/dart">
      export 'package:polymer/init.dart';
    </script>
  </head>
  <body>
    <div class="container">
      <h1>Dart Bros. Pizza Shoppe</h1>
      <ng-view></ng-view>
    </div>
  </body>
</html>
But, when I load that page, I see the following in the console:
[ERROR] Only one Dart script tag allowed per document
Bother. But not too much of a bother.

Back when I was learning to test Polymer, I needed to initialize Polymer without using that script. That turned out to be pretty easy. Instead of exporting that one script, I import the Polymer packages into my main.dart Angular app and invoke initPolymer:
import 'package:polymer/polymer.dart';
import 'package:angular/angular.dart';
import 'package:angular/routing/module.dart';
import 'package:angular_example/store.dart';

main() {
  initPolymer();

  var store = new AngularModule()
    ..type(RouteInitializer, implementedBy: StoreRouter);

  ngBootstrap(module: store);
}
In addition to removing the export, I also need to ensure that the usual <link> imports of Polymer elements occur before the main.dart script:
<!DOCTYPE html>
<html ng-app lang="en">
  <head>
    <title>Pizza Store</title>
    <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>Dart Bros. Pizza Shoppe</h1>
      <ng-view></ng-view>
    </div>
  </body>
</html>
With that, I am ready to take a look at my Angular application. As I said, I am starting very simple here. So simple, in fact, that I have a router and nothing more. The router declares two pages: the default start page and the custom pizza builder page:
import 'package:angular/angular.dart';
import 'package:angular/routing/module.dart';

class StoreRouter implements RouteInitializer {
  Scope _scope;
  StoreRouter(this._scope);

  void init(Router router, ViewFactory view) {
    router.root
      ..addRoute(
          defaultRoute: true,
          name: 'start',
          enter: view('partials/home.html')
        )
      ..addRoute(
          name: 'custom-pizza',
          path: '/pizza/custom',
          enter: view('partials/custom.html')
        );
  }
}
I define my two partials. The home.html is a simple page that links to the route defined for building custom pizza:
<h3>This is the home of darty pizza</h3>

<p>
  Why not
 <a href="/pizza/custom">build your own, awesome pizza</a>!
</p>
When I first load the application, I see:



Similarly, I define the custom.html partial as:
<h3>Oooh! Let's make beautiful pizza together</h3>

<p>
  <x-pizza></x-pizza>
</p>
Which results in:



That was rather easy to get working! Of course, I have yet to do anything real with this. I am not taking advantage of any of Angular's features and would very much like to explore binding values from Polymers into Angular applications. But that is grist for another day. For tonight, I am quite happy with the progress so far.


Day #1,001

7 comments:

  1. Inspired by this post, I tried a different route, of using Angular Dart directly inside a Polymer element. What this means is that you set this up as a regular polymer dart application, and include the directives inside the code for the Polymer element you create. I managed it with something similar to what you have, minus the pizza part. The trick is to include the element parameter in

    ngBootstrap(module: store, element: $['entry']);

    with entry being

    <div id="entry">
    <ng-view></ng-view>
    </div>

    The only downside is routes don't seem to work for me in the same way as they do for you. I had to create a route with a path of '/index.html#/pizza/custom' before it would work.

    The upside is it can be done, with work arounds. At least for routes anyway. I also left the Tranformer in pubspec.yaml alone.

    ReplyDelete
    Replies
    1. Cool! You're getting ahead of me. Good to know that it can be done :)

      Delete
    2. Well I managed to get routes to work too, using a mutation observer on <ng-view> to get the list of AnchorElements and create a proxy AnchorElement in the main document, activating the proxies click event whenever the AnchorElements are clicked in the Polymer Element. I'm not sure this is the best thing to do, but it works. I think there a whole lot of issues due to Polymer Elements shadowRoot hiding things. Angular, and the third party libraries it uses, relies on elements being searchable in the main document. It doesn't deal with the shadow root at all, thus, I think, current solutions for Angular within Polymer will have to involve some sort of proxy, as one solution. Oh and did I mention any events from Anchorelements within the Polymer element look to Angular as if they are created by the Polymer Element. route_hierarchical intercepts click events on the window and checks to see which element they are from before deciding what to do. It is looking for AnchorElements as the source of the event before it'll invoke the route.

      Delete
    3. Yikes. That seems pretty involved. I may give it a go in JavaScript first and use that to compare with your experiences. What you describe sounds doable, but maybe not something for “Patterns.”

      Delete
    4. If anything it will have at least eliminated one possibility a bit more quickly for you, or put you on a side track :-)

      Delete
    5. I was going to look into this anyway, so I think you're helping me eliminate some work so that I can focus elsewhere. So thanks!

      Delete
  2. I forgot to mention I did the ngBootstrap(...) in created(). It also works in enteredView()

    ReplyDelete