Kicking The Tires on AngularDart

Posted on by Tero Parviainen (@teropa)

I've been playing around with AngularDart, the up-and-coming Dart web framework from the Angular team. Not only because we have a Dart Flight School event in Espoo this weekend, but also because it's an interesting piece of technology to me as an AngularJS developer: Many of the ideas and features that will eventually ship with AngularJS 2.0 are already in AngularDart today.

In this article I'll talk about a few of the observations I've made about AngularDart, mainly comparing it to AngularJS. For a prior article about similar subjects, do take a look at Victor Savkin's excellent "AngularDart for AngularJS Developers".

Application Components: From Functions To Annotated Classes

AngularJS services, controllers, and directives are all basically just JavaScript functions that you register in an Angular module. That's because in JavaScript, functions are the only real facility you have for code organization. While functions are certainly not a bad code organization tool, many people would still prefer to see something like classes. (And something like classes is actually likely to ship in ES6). Dart does have classes, and AngularDart uses them as the main unit for code organization.

If in AngularJS you might register a factory function like this:

JavaScript
angular.module('myApp').factory('dataService', function() {
  return {
    op1: function() { },
    op2: function() { }
  }
});

In AngularDart you introduce a plain Dart class for the same purpose:

Dart

class DataService {

  void op1() { }
  void op2() { }

}

Introducing a class does not alone make it a part of any Angular application though. We haven't yet registered it in a module, which is the equivalent of the factory('dataService', ...) part of the AngularJS example.

Module Definitions: From Scattered To Centralized

In AngularJS apps it seems to be common practice to register an application component to a module right where you introduce the component itself. You might have tens of invocations like this across your codebase:

JavaScript
angular.module('myApp').factory('dataService', function() { });

While this is convenient, it does mean the information about what a given module includes is scattered all over the place.

In AngularDart on the other hand, it feels much more convenient to centralize all your module configuration in one location. When you introduce your module, you also register all the things that go in it:

Dart

var mod = new Module();
mod.type(DataService);
mod.type(OtherService);
mod.type(SomeController);

you can also define a separate class for the module:

Dart

class MyApplicationModule {
  MyApplicationModule() {
    type(DataService);
    type(OtherService);
    type(SomeController);
  }
}
var mod = new MyApplicationModule();

To me this is very reminiscent of the way Guice modules are defined.

You can certainly use the centralized module configuration style in AngularJS too, but in AngularDart it seems to be the way you're supposed to be doing it, since the semi-global access point to your module is not just one angular.module invocation away.

Dependency Injection: From Name-based to Type-based

In AngularJS, you declare the dependencies of your application components by adding function arguments that have the same names as things provided by your modules or the Angular framework:

JavaScript
function($http, DataService) {

}

Using a brilliant, epic hack, Angular figures out what those arguments resolve to and injects them for you.

AngularDart embraces the Dart type system, and injects dependencies based on type information, rather than what your argument names happen to be:

Dart

class SomeController {
  Http _http;
  DataService _anyNameForDataService;

  SomeController(this._http, this._anyNameForDataService);
}

While I've never personally had any problems with AngularJS dependency injection, it is often criticized for being "weird". The AngularDart style of DI is likely to address some of those concerns.

Attaching Controllers: From HTML To Annotations

AngularJS controller are completely unaware of what HTML elements they are attached to. You just register a controller to the module, and it gets instantiated elsewhere: Usually by an ng-controller directive in HTML, or by a $routeProvider.

AngularDart makes the rules of attachment the controller's concern. You annotate your controller with NgController and specify a CSS selector that will define which HTML elements will instantiate that controller. You can match by attribute:

Dart

@NgController(selector: '[my-ctrl]')
class MyController {
}

or by class:

Dart

@NgController(selector: '.my-ctrl')
class MyController {
}

or by tag:

Dart

@NgController(selector: 'textarea')
class MyController {
}

AngularJS directives already work this way - AngularDart just expands the same mechanism to controllers too, which I find a welcome change.

Scope Attributes: From Explicit To Declared

AngularJS controllers take their Scope as an argument, and you explicitly expose the things you want to expose by attaching them to the scope object:

JavaScript
function MyController ($scope) {
  $scope.publicData = [1, 2, 3];

  $scope.publicFunction() {

  }
}

You can still do this in AngularDart, but there's also a way you can expose members of your controller class on the scope declaratively, by publishing the controller in the NgController annotation:

Dart

@NgController(selector: '[my-ctrl]', publishAs: 'ctrl')
class MyController {
  List publicData = [1, 2, 3];

  void publicFunction() {

  }
}

With publishAs you declare a prefix using which views can refer to the public fields and functions of the controller directly:

  • {{n}}

Interestingly, this hides the Scope object from application code completely. It becomes an internal framework facility rather than something you deal with directly as an app developer.

DOM Extension: From Transclusion To Components

In AngularJS, directives are used for two things: Decorating HTML elements with behavior (e.g. ngClick) and "extending" HTML with new kinds of elements (e.g. Angular UI tabs). AngularDart directives retain only the first of these responsibilities, and the second one is moved to a new framework facility called components.

Components are similar to directives and controllers, but they're really meant for building truly independent "widgets", of which a tab bar is a good example. Components may ship with their own HTML templates and CSS that's independent of other application code. They use the Shadow DOM internally, which gives them an environment encapsulated from the rest of the DOM.

Components replace the whole notion of directive transclusion in AngularJS. It remains to be seen whether they will be easier to grok than transclusions, which have gained this reputation of being difficult to understand. At least the new vocabulary is more approachable, and the fact that Shadow DOM is a W3C draft standard should help in the concepts becoming familiar to more people.

Isolate Scopes: From Mini-DSL To Annotated Fields

When you define an isolate scope for an AngularJS directive, you use this charming character-prefix mini-DSL to gain access to members of the parent scope:

JavaScript
scope: {
  'myAttribute': '=',
  'myReadOnlyAttribute': '@',
  'myOperation': '&nameOfExpression'
}

Isolate scopes in AngularDart directives replace this DSL with something arguably much more readable:

Dart

class MyDirective {

  @NgTwoWay('myAttribute')
  SomeClass myAttribute;

  @NgOneWay('myReadOnlyAttribute')
  SomeOtherClass myReadOnlyAttribute;

  @NgCallback('nameOfExpression')
  Function myOperation;

}

Promises: From Framework-provided To Language-Provided

AngularJS ships with its own implementation of promises, which includes a subset of the Q library. It is one of those facilities that seem inconsequential at first, but that you couldn't go without once you get used to it.

AngularDart doesn't have a promise implementation. That's because the Dart language itself has one that AngularDart can use, called Futures.

When you invoke an asynchronous API in AngularDart, you get back a Future object. The simplest thing you can do with a Future is attach a handler function for when the Future completes. That happens pretty much exactly like one might expect:

Dart

http.get('data.json').then((HttpResponse res) => res.data);

The other facilities that you have in $q are also there. For example, waiting for a bunch of Futures to complete in parallel (the equivalent of $q.all):

Dart

Future.wait([getData(), getOtherData()]).then((List responses) {
  // ...
});

Code Loading: From <script> Tags To Libraries And Imports

In JavaScript webapps you load code either by inserting a <script> tag to the page or by using a special library for code loading with something like AMD. In Dart things are a lot less ad-hoc: You actually cannot have more than one Dart <script> tag on a page. You just have the one, which loads a Dart file with a main() function. Whatever else gets loaded is controlled by what you import in that file.

Dart code is organized into units called libraries. Each library can have a number of classes and functions; some of them public, some of them private. Libraries may import other libraries or parts of other libraries. This gives the code a level of modularity that JavaScript has never really had. (Though ES6 will ship with a modules implementation, and of course there's always AMD).

Bootstrapping: From Automatic To Semi-Automatic

An AngularJS application can be boostrapped in a couple of ways: By doing it from JavaScript or by relying on AngularJS to discover an ng-app attribute set up in HTML.

In AngularDart you always bootstrap programmatically. AngularDart won't launch just because you include it in your project. You need to bootstrap it from the Dart main function (which gets invoked once the DOM content of the page has loaded).

Conclusion

It's been great fun playing with AngularDart. Many of its features greatly improve the Angular programming experience, and I can't wait to see them land in AngularJS.

While AngularDart is getting ready for 1.0, it is definitely still a bit rough around the edges. I've found small things here and there that haven't worked as expected, but to be fair it hasn't been anything a couple of pull requests couldn't fix.

The tooling around Dart is pretty good - the IDE seems solid and they've done a good job hiding some of the weirdness that Eclipse-based tools usually present. Pub, the package manager, does what you'd expect from a modern dependency manager and it's clearly picked up a few things from Bundler and Bower, such as lockfiles and Git-based dependency resolution.

Dart isn't really supported by any web browser that anyone uses, so the only way to actually ship a Dart webapp is to compile it to JavaScript. I haven't spent much time exploring this yet, but I think it's getting to a place where the compiled code is small and efficient enough to be used in real apps. I wouldn't mind being able to work seamlessly in multiple web browsers during development, though. Right now the only feasible option is to use the version of Chromium that ships with the Dart SDK.

Know Your AngularJS Inside Out

Build Your Own AngularJS

Build Your Own AngularJS helps you understand everything there is to understand about AngularJS (1.x). By creating your very own implementation of AngularJS piece by piece, you gain deep insight into what makes this framework tick. Say goodbye to fixing problems by trial and error and hello to reasoning your way through them

eBook Available Now

comments powered by Disqus