Skip to content
This repository has been archived by the owner on Sep 16, 2022. It is now read-only.

Transformer

Nate Bosch edited this page Sep 18, 2016 · 1 revision

Purpose

While developing an Angular 2 Dart app, the framework uses reflection via dart:mirrors to enable dependency injection and template binding among other features. This is convenient, but comes at the cost of slower execution and greatly increased deployed code size.

The Transformer is designed to analyze your Angular 2 Dart app and generate static code to replace the use of dart:mirrors, increasing speed and reducing code size. See the Design Doc for more information.

Using the transformer

See Assets and Transformers for general information on transformers.

Setup

Your project's pubspec

  1. Declare in your pubspec.yaml file that your application uses the angular2 transformer
  2. Provide the transformer with one or more entry_points
  3. (Optional) Provide one or more custom_annotations

Common transformer parameters

entry_points

entry_points are the file(s) where you call Angular 2's bootstrap method.

Due to the way these files are processed, you should pass bootstrap the Type literal of your root Component. Indirection will likely cause the transformer to generate incorrect code.

Good:

helper.dart:

void setup() {
 // Some initialization
}

entry_point.dart:

void main() {
  setup();
  bootstrap(MyComponent);
}

or

entry_point.dart:

@AngularEntrypoint()
void main() {
  group('Test an angular component in compiled mode', () {
    ...
  });
}

Bad:

helper.dart:

void setupThenBootstrap(Type t) {
  // Some initialization
  bootstrap(t);
}

entry_point.dart:

void main() {
  setupThenBootstrap(MyComponent);
}
resolved_identifiers

The new Angular 2 template compiler generates dependency injection code. This means that all the identifiers used in DI have to be collected by the Angular 2 transformer. As a result, the libraries containing these identifiers have to be transformed by the Angular 2 transformer. So if you see

Missing identifier "SomeClass" needed by "SomeComponent" from metadata map.

just enable the Angular 2 transformer for the library that contains SomeClass, and the issue will be fixed.

Currently, the following types of libraries are not transformed:

  • core libraries (e.g., dart:html)
  • libraries generated using protobufs

To be able to use symbols from these libraries, list identifier/asset pairs after the resolved_identifiers transformer option. For example:

- angular2:
  resolved_identifiers:
    Window: 'dart:html'
custom_annotations

custom_annotations are custom classes that you define which extend the built in Angular annotation classes (such as @Component and @Injectable). In order for the transformer to discover these you must list them in your transformer, below is an example:

transformers:
- angular2:
    custom_annotations:
      - name: MyComponent # The name of the class.
        import: 'package:my_package/my_component.dart' # The import that defines the class.
        superClass: Component # The class that this class extends.
platform_directives

A list of Directives which are considered to be present in every Component's list of dependencies. That is, any Component in the package may use any Directives in the list without explicitly declaring it.

The format of this parameter should be

package:<package>/path/to/library#symbol_to_use

Example:

in the package named "my_directives", lib/common_directives.dart contains:

...
const commonDirectives = const [CommonDirective1, CommonDirective2];
...

In your pubspec.yaml for package "my_app", you declare:

angular2:
    platform_directives: 'package:my_directives/common_directives.dart#commonDirectives'

Now all Components in the package "my_app" will behave as if they declared CommonDirective1 and CommonDirective2 as dependencies in their directives lists.

platform_pipes

A list of Pipes which are considered to be present in every Component's list of dependencies. That is, any Component in the package may use any Pipes in the list without explicitly declaring it.

The format of this parameter should be

package:<package>/path/to/library#symbol_to_use

Example:

in the package named "my_pipes", lib/common_pipes.dart contains:

...
const commonPipes = const [CommonPipe1, CommonPipe2];
...

In your pubspec.yaml for package "my_app", you declare:

angular2:
    platform_pipes: 'package:my_pipes/common_pipes.dart#commonPipes'

Now all Components in the package "my_app" will behave as if they declared CommonPipe1 and CommonPipe2 as dependencies in their pipes lists.

Advanced transformer parameters

These generally do not need to be modified.

format_code (default: false)

Whether to use the dart_style package to format code generated by the transformers. Existing app code that is updated will never be formatted, as doing so may invalidate generated source maps and/or other tool output.

reflect_properties_as_attributes (default: false)

Whether the change detection code should echo property values as attributes on DOM elements.

Dependencies

If you depend on @Injectable objects defined in other packages, those packages must also declare that they use the angular2 transformer (or the targeted codegen transformer). All Angular 2 @Directives and @Components are @Injectable objects.

Omitting the transformer in your dependencies results in errors like the following when running transformed code:

Cannot find reflection information on <@Injectable Type>

Your dependencies do not need to declare any entry_points.

Running pub

You can now run pub serve or pub build to see the transformer in action. In short, it will:

  • Create .ng_template.dart files defining initReflector methods
  • Remove the framework's use of dart:mirrors
  • Inject code to call the generated initReflector methods

See the Design Doc for details.

Debugging & Filing Issues

Troubleshooting

"Cannot find reflection information on <Type>" errors in transformed output

See Dependencies

Lifecycle Hooks are not being called

You must explicitly declare that your class implements lifecycle interfaces for the corresponding lifecycle hooks to be called, even if your class extends another class and does not override the lifecycle method!

class BaseClass implements OnInit {
  @override ngOnInit() => doImportantSetup();
}

class MyClass extends BaseClass /* Oh noes! Does not implement OnInit */ {
  // BaseClass#ngOnInit is never called!
}

See this doc and https://github.com/angular/angular/issues/6781 for discussion.

Transformer debug parameters:

You can add these parameters alongside entry_points in your pubspec.yaml file.

  • mirror_mode, defaults to none.
    • debug: Allow reflective access, but log a message if it is used
    • none: Deny reflective access, throw if it is used
    • verbose: Allow reflective access, log a stack trace if it is used
  • init_reflector, boolean, defaults to true. Whether to create calls to our generated initReflector code.

Using mirror_mode: debug or verbose allows you to run your application with reflective access but see where it is used. When filing a bug against the transformer relating to missing static registration code, it helps to include the information printed by mirror_mode: verbose.

Known Limitations

Directive aliases

Directive aliases allow you to give a name to a list of directives that are commonly used together. For example:

const formDirectives = const [NgControlName, NgControlGroup, ...];

Then, they can be used where the list of directives was expected:

@View(...
    directives: const [ formDirectives, NgFor ])
class MyComponent {}

The Dart transformer supports a restricted syntax for directive aliases:

  • The value must be a const list. This is because the value will be used within annotations.
  • They must be defined as a top-level const variable. In particular, static fields are not supported.
  • All entries in the list must be un-prefixed type literals. Support for prefixes is possible, but not implemented (see issue #3232).

The Code

  • The code for the transformer currently resides in modules_dart/transform.
  • Each phase of the transformer has its own subdirectory in lib/src/transform.