diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 3fbc959652..a969ce970d 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -842,6 +842,7 @@ testing/** @angular/fw-test /aio/content/guide/workspace-config.md @angular/fw-docs-packaging @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes /aio/content/guide/deprecations.md @angular/fw-docs-packaging @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes /aio/content/guide/migration-renderer.md @angular/fw-docs-packaging @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes +/aio/content/guide/migration-undecorated-classes.md @angular/fw-docs-packaging @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes # ================================================ diff --git a/aio/content/guide/deprecations.md b/aio/content/guide/deprecations.md index df0f5634ab..f9e7a1ef05 100644 --- a/aio/content/guide/deprecations.md +++ b/aio/content/guide/deprecations.md @@ -397,6 +397,9 @@ This includes both packages: `@angular/platform-webworker` and See the [dedicated migration guide for Renderer](guide/migration-renderer). +{@a undecorated-classes} +### Migrating undecorated classes + See the [dedicated migration guide for undecorated classes](guide/migration-undecorated-classes). {@a removed} ## Removed APIs diff --git a/aio/content/guide/migration-undecorated-classes.md b/aio/content/guide/migration-undecorated-classes.md new file mode 100644 index 0000000000..dcbf40ffad --- /dev/null +++ b/aio/content/guide/migration-undecorated-classes.md @@ -0,0 +1,138 @@ +# 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: + ```ts + export class BaseMenu { + constructor(private vcr: ViewContainerRef) {} + } + + @Directive({selector: '[settingsMenu]'}) + export class SettingsMenu extends BaseMenu {} + ``` + + After: + ```ts + @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: +```ts +@Component({ + selector: 'base-menu', + template: '
' +}) +class BaseMenu {} + +export class SettingsMenu extends BaseMenu {} +``` + +After: +```ts +@Component({ + selector: 'base-menu', + template: '
' +}) +class BaseMenu {} + +@Component({ + selector: 'settings-menu', + template: '
' +}) +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. +