Skip to content
This repository has been archived by the owner on Feb 22, 2018. It is now read-only.

The Angular Transformer

Rado Kirov edited this page Jan 27, 2015 · 6 revisions

Angular Transformer

Dart's package manager pub supports a system of pluggable processing scripts called transformers. You can find more info about them at https://www.dartlang.org/tools/pub/assets-and-transformers.html

The angular.dart library comes with a transformer of its own. Its purpose is to generate static structures that remove the need for reflection (dart:mirrors) at runtime. This guarantees your application JS size is minimal after dart2js compilation.

To use the Angular transformer include the following line in your pubspec.yaml:

transformers:
- angular

What does it do?

The transformer goes through the following steps in sequential order:

  • Call into observe transformer.
  • Find all dart files referenced with <script type="application/dart">.
  • Create injection information for all classes used by dependency injection.
  • Transform relative urls.
  • Create setters and getters for all expressions.
  • Extract all metadata annotations.
  • Transform your application entry-point to use staticApplicationFactory.

The code entry point is lib/transformer.dart. Let's go through each step in detail.

Observable transformer

Angular.dart's change detection supports package:observe @Observable objects, so we need to call into their transformer first.

Find all .dart references in html

Dart's transformer library will go through all /lib files, but it misses .dart files directly referenced from files in /web, thus necessitating this step. This transformer generates files that look like *.dart.html_reference.

Create Injection Information

Dependency injection needs to be able to reflect on the constructor of an injectable class. For example if I have a class Car that depends on an Engine and Brakes classes, dependency injection needs:

  • a mapping from Car to the list of required types [Engine, Brakes].
  • a closure to actually call when the requirements are found - (anEngine, someBreaks) => new Car(anEngine, someBreaks).

This transformer creates these mappings for all classes annotated with @Injectable() and stores them in <your_file>_generated_type_factories_map.dart.

This transformer is part of the di package and angular calls into it.

Transform relative urls

A relative url in the templateUrl field of a @Component annotation, poses a question - relative to what? Without any transformation it would be relative to the current page when we send the XHR to fetch it. The transformation makes it relative to the current .dart file. The transformations are recorded in <your_file>_static_type_to_uri_mapper.dart.

Create setters/getters for all expressions

Angular ships with an interpreter that evaluates your template expressions. There is no eval in dart, so the interpreter needs to have closures to call for performing field gets and sets. For example, if you have <div>{{c = a.b}}</div> in your template, the transformer will generate maps of getters like "a": (o) => o.a and setters like "c": (o, v) => o.c = v. This allows the interpreter to perform what amounts to eval("c = a.b").

The output of this transformer is in <your_file>_static_expressions.dart.

Extract metadata

Class metadata annotations like @Component are not accessible during runtime without reflection. This transformer creates a map between types and their corresponding angular annotations. It is mostly used for @Component and @Decorator, and @Injectable annotations.

For example if you write:

@Component(selector: 'cmp')
class MyComponent

it would generate the following key-value pairing

MyComponent: [const Component(selector('cmp')]

The output of this transformer is in <your_file>_static_metadata.dart.

Transform your application

All the previous transformers output static structures into *_static_*.dart files. This final step hooks the static structures into your angular application. Because transformers cannot operate on libraries (like angular), it transforms the point in your application code where you call into the applicationFactory and replaces it with a staticApplicationFactory that has all the static structures generated.

Common runtime issues caused by transformers

Missing getter: (o) => o.foo. This means the expression extraction did not do its job correctly. Verify the getter is present/missing from *_static_expressions.dart. Run pub build and look for Unable to find *.html messages. Debug lib/tools/transformer/expression_generator.dart. Known issues: #1592.

For example if in web/main.dart we have:

@Component(
  templateUrl: 'myTemplate.html'

The transformer will look for myTemplate.html at the root of the library, while pub serve will serve it from web. If you replace it with web/myTemplate.html, the transformer will find it, but pub serve will not serve it. PR #1652 will fix this issue.

Workarounds include:

  • Using packages relative urls - templateUrl: 'packages/my_package/path_to_template' (omitting lib).
  • listing the templates in pubspec.yaml,
transformers:
- angular:
    html_files:
      - <my_undetected_file>

Debugging transformers

Currently (late 2014) pub does not support interactive debugging of transformers. Add plenty of print statements.