|
| 1 | +# Undecorated classes migration (DI) |
| 2 | + |
| 3 | +This section discusses an Angular version 9 schematic that migrates |
| 4 | +two inheritance patterns that need to be updated to work with Ivy. |
| 5 | + |
| 6 | +## What does this migration do? |
| 7 | + |
| 8 | +This migration adds an empty `@Directive()` decorator to undecorated |
| 9 | +base classes that are extended by either directives or components. |
| 10 | + |
| 11 | + Before: |
| 12 | + ```ts |
| 13 | + export class BaseMenu { |
| 14 | + constructor(private vcr: ViewContainerRef) {} |
| 15 | + } |
| 16 | + |
| 17 | + @Directive({selector: '[settingsMenu]'}) |
| 18 | + export class SettingsMenu extends BaseMenu {} |
| 19 | + ``` |
| 20 | + |
| 21 | + After: |
| 22 | + ```ts |
| 23 | + @Directive() |
| 24 | + export class BaseMenu { |
| 25 | + constructor(private vcr: ViewContainerRef) {} |
| 26 | + } |
| 27 | + |
| 28 | + @Directive({selector: '[settingsMenu]'}) |
| 29 | + export class SettingsMenu extends BaseMenu {} |
| 30 | + ``` |
| 31 | + |
| 32 | +The schematic also copies any inherited directive or component metadata to the derived class. |
| 33 | + |
| 34 | +Before: |
| 35 | +```ts |
| 36 | +@Component({ |
| 37 | + selector: 'base-menu', |
| 38 | + template: '<div></div>' |
| 39 | +}) |
| 40 | +class BaseMenu {} |
| 41 | + |
| 42 | +export class SettingsMenu extends BaseMenu {} |
| 43 | +``` |
| 44 | + |
| 45 | +After: |
| 46 | +```ts |
| 47 | +@Component({ |
| 48 | + selector: 'base-menu', |
| 49 | + template: '<div></div>' |
| 50 | +}) |
| 51 | +class BaseMenu {} |
| 52 | + |
| 53 | +@Component({ |
| 54 | + selector: 'settings-menu', |
| 55 | + template: '<div></div>' |
| 56 | +}) |
| 57 | +export class SettingsMenu extends BaseMenu {} |
| 58 | +``` |
| 59 | + |
| 60 | +## Why is this migration necessary? |
| 61 | + |
| 62 | +When a class has a `@Directive()` or `@Component()` decorator, |
| 63 | +the Angular compiler generates extra code to inject dependencies into |
| 64 | +the constructor. When using inheritance, Ivy needs both the parent class |
| 65 | +and the child class to apply a decorator to generate the correct code. |
| 66 | + |
| 67 | +You can think of this change as two cases: a parent class is missing a |
| 68 | +decorator or a child class is missing a decorator. In both scenarios, |
| 69 | +Angular's run-time needs additional information from the compiler. |
| 70 | +This additional information comes from adding decorators. |
| 71 | + |
| 72 | + |
| 73 | +### Decorator missing from parent class |
| 74 | + |
| 75 | +When the decorator is missing from the parent class, |
| 76 | +the subclass will inherit a constructor from a class for |
| 77 | +which the compiler did not generate special constructor |
| 78 | +info (because it was not decorated as a directive). |
| 79 | +When Angular then tries to create the subclass, |
| 80 | +it doesn't have the correct info |
| 81 | +to create it. |
| 82 | + |
| 83 | +In View Engine, the compiler has global knowledge, so it |
| 84 | +can look up the missing data. However, the Ivy compiler |
| 85 | +only processes each directive in isolation. This means that |
| 86 | +compilation can be faster, but the compiler can't |
| 87 | +automatically infer the same |
| 88 | +information as before. Adding the `@Directive()` explicitly |
| 89 | +provides this information. |
| 90 | + |
| 91 | +In the future, add `@Directive()` to base classes that |
| 92 | +do not already have decorators and are extended by directives. |
| 93 | + |
| 94 | +### Decorator missing from child class |
| 95 | + |
| 96 | +When the child class is missing the decorator, the |
| 97 | +child class inherits from the |
| 98 | +parent class yet has no decorators of its own. |
| 99 | +Without a decorator, the compiler has no way of knowing |
| 100 | +that the class is a `@Directive` or `@Component`, so |
| 101 | +it doesn't generate the proper instructions for the directive. |
| 102 | + |
| 103 | + |
| 104 | +## What does it mean to have a `@Directive()` decorator with no metadata inside of it? |
| 105 | + |
| 106 | +The presence of the `@Directive` decorator causes Angular to generate |
| 107 | +extra code for the affected class. If that decorator includes no |
| 108 | +properties (metadata), |
| 109 | +the directive won't be matched to elements or instantiated |
| 110 | +directly, but other classes that _extend_ the |
| 111 | +directive class will inherit this generated code. You can think of |
| 112 | +this as an "abstract" directive. |
| 113 | + |
| 114 | +Adding an abstract directive to an `NgModule` will cause an error. |
| 115 | +A directive must have a `selector` property defined in order to match some element in a template. |
| 116 | + |
| 117 | +## When do I need a `@Directive()` decorator without a selector? |
| 118 | + |
| 119 | +If you're using dependency injection, or any Angular-specific |
| 120 | +feature, such as `@HostBinding()`, `@ViewChild()`, or `@Input()`, you need a |
| 121 | +`@Directive()` or `@Component()` decorator. |
| 122 | +The decorator lets the compiler know to generate the correct |
| 123 | +instructions to create that class and any classes that extend it. |
| 124 | +If you don't want to use that base class as a directive directly, leave |
| 125 | +the selector blank. If you do want it to be usable independently, |
| 126 | +fill in the metadata as usual. |
| 127 | + |
| 128 | +Classes that don't use Angular features don't need an Angular decorator. |
| 129 | + |
| 130 | +## I'm a library author. Should I add the `@Directive()` decorator to base classes? |
| 131 | + |
| 132 | + |
| 133 | +As support for selectorless decorators is introduced in |
| 134 | +Angular version 9, if you want to support Angular version 8 and earlier, you |
| 135 | +shouldn't add a selectorless `@Directive()` decorator. |
| 136 | +You can either add `@Directive()` with a selector or |
| 137 | +add an explicit constructor to affected subclasses. |
| 138 | + |
0 commit comments