30 March 2014

AngularDart: The future of AngularJS?

The AngularJS team have been working on a port to Dart called, naturally, AngularDart.
They've taken the opportunity to completely rewrite Angular, adding features and patterns which feel quite natural when written in Dart.
At ng-conf 2014, they talked about their intention to port these new features back into AngularJS as part of version 2.0.  So I've been curious to take a look at AngularDart as a sneak peek into the future of AngularJS.

What's Dart?

Dart is an open source language, backed by Google (In the same way Go is), which is designed to replace Javascript as the programming language for web browsers.  It has it's own VM called Dartium, similar to V8 for Javascript, and I believe has some of the original V8 developers working on it.

But since the only browser which comes with Dartium is a special build of Chromium made specifically for Dart development, the Dart SDK comes with dart2js: A transpiler that produces Javascript which can be used in any browser that supports ECMAScript 5.

And the performance from what dart2js produces is fairly impressive.  Though one should always take benchmarks with a grain of salt.

What makes Dart different from any other modern programming language?


There are more transpilers to Javascript than you can poke a stick at.

I think the answer is about the original purpose of the language.  The purpose of Dart was to create a new language for web browsers, which requires it to have a resemblance to Javascript, so that it can interoperate with it.  This is different from, say, Clojure which wasn't written specifically so it could work with Javascript; That was implemented later as ClojureScript.

CoffeeScript is probably the closest example to what Dart is trying to do.  The difference is that CoffeeScript's purpose was always to be compiled down to Javascript.  It was never intended to completely replace Javascript, just to smooth out it's flaws.

Dart also comes with it's own tools.  As well as the dart2js transpiler, you've got pub for package management (think npm and bower), dartanalyzer for linting, docgen for documentation generation, and dartfmt for code formatting.  I'm a big fan of platform enforced formatting.  It takes the decision, and therefore the argument, as far away from me as humanly possible.

First look at AngularDart

"Hello Dart"


The Dart SDK comes with Dart Editor, a customised Eclipse IDE.  The welcome page includes a link for an AngularDart sample (which you can find the source for at github.com/angular/angular.dart), but it just wouldn't be as fun to have everything pre-done for us... well, not the first time, at least.

Go "File -> New Application", pick "Web application" and give it a name (I went with 'intro').  You'll be given some basic boilerplate: a ".dart", ".css", and ".html file, and some files used by 'pub' for package management.  Click "Run" (the green 'play' button) to spin up the application in Dartium.



What you should see is a simple page with the words "Click Me!" which reverse themselves when clicked.
The code is pretty self-explanatory.  It uses "querySelector()" to pick the element, set the text, then add an event listener which reverses the text.  Next we'll change it to use AngularDart instead of the core library.

"Hello AngularDart"


First thing we need is to install the AngularDart package using "pub".
Open up the "pubspec.yaml" file.  You should see an existing "browser: any" dependency.  This means the application depends upon any (ie. latest stable) version of the "browser" package, which is a library for applications that run in a browser, as opposed to using the "io" package if you wanted to run it as a standalone application.  Add "angular: any" to the dependencies and then run "pub get".

Back to the code.
First, lets import AngularDart and bootstrap it:

If everything has gone fine, this should have zero effect on your application. But if you start getting "The built-in library 'dart:json' is not available on Dartium.", run "pub upgrade" to fix it. It means one of the dependencies is still trying to use "dart:json" instead of "dart:convert".

Now we want to replace that click event listener with a directive.  Directives in AngularDart are given a CSS selector, rather than a name plus a type restriction.  However that doesn't mean they support any kind of CSS selector.  You're generally restricted to an attribute or element name.  So we want to change the "#sample_text_id" in the HTML to "[sample-text-id]", since we can't use the 'id' attribute:

Now to write our directive.

Directives are structured differently in AngularDart.  Instead of POJ objects, they're classes with an "@NgDirective" annotation.  Annotations play a big part in AngularDart, and are likely going to do the same for AngularJS 2.0 (Which is causing some contention since annotations aren't part of the ES6 spec, but is supported by Traceur).

Another big difference with AngularDart directives is that you don't use a name and a "restrict" property to decide what elements or attributes they get triggered by.  Instead you add a "selector" with a CSS selector to the annotation.

And last of all we need to make sure the directive gets included in the application bootstrap process. We do this by setting our new "ReverseClickDirective" class as a type on a new module, which gets passed into the "ngBootstrap()" method call.  I'll go into the Module class a bit more later.

All I've done here is a bad recreation of "ng-click", but it gives you an idea of how different AngularDart is compared to AngularJS v1.*, and how different AngularJS v2.* is likely to look.

In depth AngularDart

So you think "hmm, that's some what interesting.  Where should I go from here?".
A wise man once said "Luke, read the source".
The documentation for AngularDart is pretty sparse right now (understandably, since it's still in beta). So your best bet is to go straight to the source code, most of which is pretty well documented with inline comments; especially the public API.

Dependency Injection


In AngularDart, the DI framework has been separated from Angular into it's own package.  But it's still reference quite heavily in the Angular code, so you'll need to understand the DI framework to follow the AngularDart source code.  Remember the "Module" class with it's "type" method that was passed to the "ngBootstrap" method?  That was from the DI package, not from Angular.

Lets start with the Module class because it's really the most important class.  Open up "packages/di/module.dart".  Remember the "value()", "factory()", "service()", "constant()" and "provider()" module methods in AngularJS?  Here they are again, but instead you have:

  • value(Type id, value, {Type withAnnotation, Visibility visibility})
    The "value()" you know and love, but with a twist.
    Instead of "id" being a string, it's a Type (ie. class).  This makes perfect sense in Dart because it supports an (optional) type checking system with classes.
    However, if you're not dealing with a class or you're dealing with multiple instances of the same class you can use annotations, combined with the "withAnnotation" parameter.

    The "visibility" parameter is a function which takes 2 injectors, the requesting and the defining, and returns whether or not the requesting injector has visibility of the instance created by the defining injector.  This is a new DI concept for Angular which we haven't seen in AngularJS v1.*, but was hinted at by Vojta Jina in this talk at ng-conf.  This idea of multiple injectors which can share, or not share, certain injectables is pretty cool.
  • type(Type id, {Type withAnnotation, Type implementedBy, Visibility visibility})
    We've seen type before when we declared our directive class during bootstrap.  It's pretty much what you would expect - give it a class, and expect to get an instance of that class on injection.
    As an added bonus, you can also specify an "implementedBy" subclass to use when the "id" class is required.
  • factory(Type id, FactoryFn factoryFn, {Type withAnnotation, Visibility visibility})
    If you're not a fan of "new", you can use a factory function instead.

    A "FactoryFn" accepts an injector as a parameter, for loading dependencies, and returns the injectable value.
  • install(Module module)
    Used to extend an existing module.
What about "constant()" and "provider()"?
AngularDart has taken the whole "config() phase vs run() phase" concept and thrown it away, making those methods redundant.  If you really need to perform extra configuration on your modules before the application starts running, then you should do it before calling "ngBootstrap()".

Okay, that handles registering injectables.  But how do I declare dependencies?
"factory()" already has this handled because it gets an instance of the injector.  So it just needs to call "injector.get(MyDependency)", and away it goes.
What about "type()"?  Well that's the beauty of a static type system.  The DI framework uses reflection to determine the types expected by the class constructor, and injects those in.  Look at this:


That's how the DI framework works. If it's instantiated through the DI framework, then it will attempt to provide all the dependencies required by it's constructor.

Controllers, Filters, and Directives (Oh my?)

The missing methods from our old AngularJS modules are "controller()", "filter()", and "directive()".

The truth is that they've already been covered by "Module.type()" because, to the DI framework, they're just classes.  The way to declare them differently is to use annotations.

Filters use an "@NgFilter(name)" annotation, where the name is how they're named in the template, and expose a "call(input, params...)" method.

Controllers use an "@NgController(selector, publishAs)" annotation, which is actually a subclass of the @NgDirective annotation class.  "selector" is a CSS selector which is used to apply the controller to the HTML view.  And "publishAs" is the name that the controller instance can be referenced as from the template.  Remember the "ng-controller='x as y'" syntax in AngularJS? same thing. Properties from the controller instance are exposed on the view, and any dependencies declared in the constructor are injected in, including Scope for creating watchers and generating events.

I already showed you a basic directive example, but there's a lot more to be learnt.  If you want to go deeper, I suggest doing the same thing as for AngularJS: Look at the builtin directives.  I also suggest looking at the classes for the annotations too.
There's lots of interesting things, like implementing "NgAttachAware" and "NgDetachAware" to run "attach()" and "detach()" methods when scopes are first created and destroyed.

Components?

There is one new feature of AngularDart which I haven't covered which is a 'component'.
AngularDart components are related to web-components and make use of the Shadow DOM feature in modern browsers; Two things which I am not completely familiar with.  So I'm going to leave them alone for now rather than do them a disservice through my own ignorance.

Final Thoughts

First, Dart.  I'm so-so about Dart.
On one hand, trying to replace JavaScript with something more up to date is a noble ambition.
However Dart, and it's sponsor Google, have failed to win the rest of the web over.  The popular vote is in improving Javascript, rather than outright replacing it, through new standards like ES6 and ES7.
What's the best path?  I can't say.  But the writing on the wall tells me the support for Javascript is (strangely) growing, due in no small part to the success stories being heard about NodeJS.

As for AngularDart, the differences from AngularJS seem to depend on 2 main features: static typing with reflection, and annotations.
ES6 will introduce classes, but there's no suggestion that there will be any optional type checking added to go with it.  That means we can't just pass a class or function into the DI framework as they are (at least, not post-minification).  We'll still need that separate list of injectable types, but instead of strings it should be possible to use classes.
Annotations on the other hand aren't mentioned in any of the ES6 specifications at all.  They're an entirely separate feature which just happens to be in Dart and supported by Traceur.  So if AngularJS v2.* does use annotations, we're either going to be forced to use Traceur, regardless of the browser support for ES6, or we'll have to write them by hand:


Doing them by hand isn't so bad, but it does "kill the mood" some what.  The idea is to improve the syntax, but ends up making it worse.

Aside from those 2 worries, I'm quite keen to see what happens with AngularJS v2.*.
Now to get those last few IE8 users to upgrade...

No comments:

Post a Comment