4.5 KiB
Undecorated classes migration (DI)
This section discusses an Angular version 9 schematic that migrates two inheritance patterns that need to be updated to work with Ivy.
What does this migration do?
This migration adds an empty @Directive()
decorator to undecorated
base classes that are extended by either directives or components.
Before:
export class BaseMenu {
constructor(private vcr: ViewContainerRef) {}
}
@Directive({selector: '[settingsMenu]'})
export class SettingsMenu extends BaseMenu {}
After:
@Directive()
export class BaseMenu {
constructor(private vcr: ViewContainerRef) {}
}
@Directive({selector: '[settingsMenu]'})
export class SettingsMenu extends BaseMenu {}
The schematic also copies any inherited directive or component metadata to the derived class.
Before:
@Component({
selector: 'base-menu',
template: '<div></div>'
})
class BaseMenu {}
export class SettingsMenu extends BaseMenu {}
After:
@Component({
selector: 'base-menu',
template: '<div></div>'
})
class BaseMenu {}
@Component({
selector: 'settings-menu',
template: '<div></div>'
})
export class SettingsMenu extends BaseMenu {}
Why is this migration necessary?
When a class has a @Directive()
or @Component()
decorator,
the Angular compiler generates extra code to inject dependencies into
the constructor. When using inheritance, Ivy needs both the parent class
and the child class to apply a decorator to generate the correct code.
You can think of this change as two cases: a parent class is missing a decorator or a child class is missing a decorator. In both scenarios, Angular's run-time needs additional information from the compiler. This additional information comes from adding decorators.
Decorator missing from parent class
When the decorator is missing from the parent class, the subclass will inherit a constructor from a class for which the compiler did not generate special constructor info (because it was not decorated as a directive). When Angular then tries to create the subclass, it doesn't have the correct info to create it.
In View Engine, the compiler has global knowledge, so it
can look up the missing data. However, the Ivy compiler
only processes each directive in isolation. This means that
compilation can be faster, but the compiler can't
automatically infer the same
information as before. Adding the @Directive()
explicitly
provides this information.
In the future, add @Directive()
to base classes that
do not already have decorators and are extended by directives.
Decorator missing from child class
When the child class is missing the decorator, the
child class inherits from the
parent class yet has no decorators of its own.
Without a decorator, the compiler has no way of knowing
that the class is a @Directive
or @Component
, so
it doesn't generate the proper instructions for the directive.
What does it mean to have a @Directive()
decorator with no metadata inside of it?
The presence of the @Directive
decorator causes Angular to generate
extra code for the affected class. If that decorator includes no
properties (metadata),
the directive won't be matched to elements or instantiated
directly, but other classes that extend the
directive class will inherit this generated code. You can think of
this as an "abstract" directive.
Adding an abstract directive to an NgModule
will cause an error.
A directive must have a selector
property defined in order to match some element in a template.
When do I need a @Directive()
decorator without a selector?
If you're using dependency injection, or any Angular-specific
feature, such as @HostBinding()
, @ViewChild()
, or @Input()
, you need a
@Directive()
or @Component()
decorator.
The decorator lets the compiler know to generate the correct
instructions to create that class and any classes that extend it.
If you don't want to use that base class as a directive directly, leave
the selector blank. If you do want it to be usable independently,
fill in the metadata as usual.
Classes that don't use Angular features don't need an Angular decorator.
I'm a library author. Should I add the @Directive()
decorator to base classes?
As support for selectorless decorators is introduced in
Angular version 9, if you want to support Angular version 8 and earlier, you
shouldn't add a selectorless @Directive()
decorator.
You can either add @Directive()
with a selector or
add an explicit constructor to affected subclasses.