189 lines
6.4 KiB
Markdown
189 lines
6.4 KiB
Markdown
# Undecorated Classes Migration
|
|
|
|
## What does this migration do?
|
|
|
|
This migration adds an empty `@Directive()` decorator to undecorated
|
|
base classes that:
|
|
|
|
- use Angular features
|
|
- are extended by directives or components
|
|
|
|
For example, in the diff below, a `@Directive()` decorator is added to `BaseMenu` because `BaseMenu` uses dependency injection.
|
|
|
|
|
|
**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 {}
|
|
```
|
|
|
|
In the event that a directive or component is extended by a class without a decorator, the schematic copies any inherited directive or component metadata to the derived class.
|
|
|
|
**Before:**
|
|
```ts
|
|
@Component({
|
|
selector: 'base-menu',
|
|
template: '<div></div>'
|
|
})
|
|
class BaseMenu {}
|
|
|
|
export class SettingsMenu extends BaseMenu {}
|
|
```
|
|
|
|
**After:**
|
|
```ts
|
|
@Component({
|
|
selector: 'base-menu',
|
|
template: '<div></div>'
|
|
})
|
|
class BaseMenu {}
|
|
|
|
@Component({
|
|
selector: 'base-menu',
|
|
template: '<div></div>'
|
|
})
|
|
export class SettingsMenu extends BaseMenu {}
|
|
```
|
|
|
|
This schematic also decorates classes that use Angular field decorators, including:
|
|
- `@Input()`
|
|
- `@Output()`
|
|
- `@HostBinding()`
|
|
- `@HostListener()`
|
|
- `@ViewChild()` / `@ViewChildren()`
|
|
- `@ContentChild()` / `@ContentChildren()`
|
|
|
|
|
|
**Before:**
|
|
```ts
|
|
class Base {
|
|
@Output()
|
|
countChanged = new EventEmitter<number>();
|
|
}
|
|
|
|
@Directive({
|
|
selector: '[myDir]'
|
|
})
|
|
class Dir extends Base {
|
|
}
|
|
```
|
|
|
|
**After:**
|
|
```ts
|
|
@Directive() // schematic adds @Directive()
|
|
class Base {
|
|
@Output()
|
|
countChanged = new EventEmitter<number>();
|
|
}
|
|
|
|
@Directive({
|
|
selector: '[myDir]'
|
|
})
|
|
class Dir extends Base {
|
|
}
|
|
```
|
|
|
|
|
|
## Why is this migration necessary?
|
|
|
|
### Migrating classes that use DI
|
|
|
|
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 runtime 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.
|
|
|
|
### Migrating classes that use field decorators
|
|
|
|
In ViewEngine, base classes with field decorators like `@Input()` worked even when the class did not have a `@Directive()` or `@Component()` decorator.
|
|
For example:
|
|
|
|
```ts
|
|
class Base {
|
|
@Input()
|
|
foo: string;
|
|
}
|
|
|
|
@Directive(...)
|
|
class Dir extends Base {
|
|
ngOnChanges(): void {
|
|
// notified when bindings to [foo] are updated
|
|
}
|
|
}
|
|
```
|
|
|
|
However, this example won't compile with Ivy because the `Base` class _requires_ either a `@Directive()` or `@Component()` decorator to generate code for inputs, outputs, queries, and host bindings.
|
|
|
|
Always requiring a class decorator leads to two main benefits for Angular:
|
|
|
|
1. The previous behavior was inconsistent.
|
|
Some Angular features required a decorator (dependency injection), but others did not.
|
|
Now, all Angular features consistently require a class decorator.
|
|
|
|
1. Supporting undecorated classes increases the code size and complexity of Angular.
|
|
Always requiring class decorators allows the framework to become smaller and simpler for all users.
|
|
|
|
|
|
## 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 move the Angular-specific features to affected subclasses.
|
|
|
|
## What about applications using non-migrated libraries?
|
|
|
|
`ngcc` should transform any non-migrated libraries to generate the proper code.
|