Annotate Once: AngularDart

Introducing Component Inheritance

AngularDart allows developers to create components and directives by annotating class definitions with metadata. This metadata instructs the compiler to generate code necessary for dependency injection, template construction, data binding, and more.

An example of metadata used to declare a simple component.

Until recently, the AngularDart compiler did not process metadata on supertypes. This meant sharing common functionality between different components and directives via inheritance required redeclaring all of the metadata in each class.

Let’s see what this pattern looked like.

Throughout this article, the following example will be revisited to demonstrate the benefits of metadata inheritance. This is a simplified example drawn from the official Material Design components for AngularDart used at Google.

A simple directive that adds button functionality to its host element.

Here we have a simple directive that adds button functionality to its host element. If the host element is clicked, or focused when the enter key is pressed, it will emit an event via the trigger output. It can also be disabled to prevent emitting events. This logic can be reused to create a button component using inheritance.

Notice the need to redeclare the input, output, and host listeners.

Now we have a component which inherits button behavior from the directive, but notice the need to redeclare the input, output, and host listeners in the derived component annotation?

As of version 4.0, redeclaring metadata on derived types is no longer necessary!

The derived component now implicitly has its ancestor’s behavior.

Now MyButtonComponent implicitly has the same input, output and host listeners as MyButtonDirective, similar to how a class normally inherits properties and methods. Now that you’ve seen a simple — and hopefully compelling — example, let’s look at what exactly is inheritable and how it works.

What is inheritable?

The following annotations are now inherited from all supertypes, including superclasses, interfaces, and mixins.

  • @ContentChild / @ContentChildren
  • @HostBinding
  • @HostListener
  • @Input
  • @Output
  • @ViewChild / @ViewChildren

This means it’s no longer necessary to redeclare any of the above annotations in a derived class.

What isn’t inheritable?

Templates, styles, and other @Directive parameters are not inherited. Only metadata that is bound to a property or method is inherited. This decision was made because derived types can override accessors and methods, referencing their original implementation via super, whereas no such mechanism exists for the values bound to other metadata.

Inherited metadata is immutable

It’s still valid to redeclare annotations in derived types; however, modifying the inherited metadata or binding it to a different property is not permitted.

Changing an input binding name is prohibited.

This restriction may be lifted in the future, but for now is necessary to simplify tooling and ensure components and directives are easily substitutable.

Metadata base types

An interesting consequence arises as a result of metadata inheritance: classes that are not directives or components themselves, may now be annotated to serve as base types or mixins for others. Let’s further develop our example to demonstrate.

There’s an issue with the current button component implementation: the button isn’t focusable. This means it’s not accessible, and can’t be triggered via the key press listener. This issue is also present with the button directive if its host element isn’t focusable. We can solve this problem by making the button directive set the tabindex property on the host element.

MyButtonDirective could implement this directly, but let’s imagine we have other components that will want focusable behavior as well. Instead, we’ll create an abstract class that can be extended or mixed in by directives or components.

Allows the host element to receive keyboard focus, when not disabled.

We can now simply extend or mix in HasTabIndex to make the button directive — and consequently the derived component — focusable.

The button directive — and its subtypes — are now focusable!

Conclusion

We’ve seen how inheritable metadata reduces code duplication among components and directives with shared functionality. Furthermore, it should improve readability by allowing metadata to live next to its associated field or method. Hopefully you’ll find this feature helpful in developing and maintaining your own projects.