docs: add examples for DI change in Ivy (#33643)

PR Close #33643
This commit is contained in:
Kara Erickson 2019-11-07 09:45:37 -08:00 committed by Matias Niemelä
parent 9f211ba0f5
commit a37bf9a516
2 changed files with 104 additions and 2 deletions

View File

@ -112,3 +112,105 @@ Option 2:
<div #foo></div> <!-- matches in both old runtime and new runtime --> <div #foo></div> <!-- matches in both old runtime and new runtime -->
</comp> </comp>
``` ```
{@a undecorated-classes}
## All Classes That Use Angular DI Must Have An Angular Class-level Decorator
### Basic example of change:
In the previous rendering engine, the following would work:
```
export class DataService {
constructor(@Inject('CONFIG') public config: DataConfig) {}
}
@Injectable()
export class AppService extends DataService {...}
```
In Ivy, it will throw an error because `DataService` is using Angular dependency injection, but is missing an `@Injectable` decorator.
The following would also work in the previous rendering engine, but in Ivy would require a `@Directive` decorator because it uses DI:
```
export class BaseMenu {
constructor(private vcr: ViewContainerRef) {}
}
@Directive({selector: '[settingsMenu]'})
export class SettingsMenu extends BaseMenu {}
```
The same is true if your directive class extends a decorated directive, but does not have a decorator of its own.
If you're using the CLI, there are two automated migrations that should transition your code for you ([this one](guide/migration-injectable) and [this one](guide/migration-undecorated-classes)).
However, as you're adding new code in version 9, you may run into this difference.
### Background
When a class has an Angular decorator like `@Injectable` or `@Directive`, the Angular compiler generates extra code to support injecting dependencies into the constructor of your class.
When using inheritance, Ivy needs both the parent class and the child class to apply a decorator to generate the correct code.
Otherwise, 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, and Angular won't have the dependency info it needs to create it properly.
In the previous rendering engine, the compiler had global knowledge, so in some cases (such as AOT mode or the presence of certain injection flags), it could look up the missing data.
However, the Ivy compiler only processes each class in isolation.
This means that compilation has the potential to be faster (and opens the framework up for optimizations and features going forward), but the compiler can't automatically infer the same information as before.
Adding the proper decorator explicitly provides this information.
### Example of error
In JIT mode, the framework will throw the following error:
```
ERROR: This constructor is not compatible with Angular Dependency Injection because dependency at index 'X' is invalid.
This can happen if the dependency type is a primitive like a string or if an ancestor of this class is missing an Angular decorator.
Please check that 1) the type for dependency X is correct and 2) the correct Angular decorators are defined for this class and its ancestors.
```
In AOT mode, you'll see something like:
```
X inherits its constructor from Y, but the latter does not have an Angular decorator of its own.
Dependency injection will not be able to resolve the parameters of Y's constructor. Either add a
@Directive decorator to Y, or add an explicit constructor to X.
```
In some cases, the framework may not be able to detect the missing decorator.
In these cases, you'll generally see a runtime error thrown when there is a property access attempted on the missing dependency.
If dependency was `foo`, you'd see an error when accessing something like `foo.bar`:
```
Uncaught TypeError: Cannot read property 'bar' of undefined
```
If you see an error like this, and the `undefined` value refers to something that should have been injected, it may be this change.
### Recommended fix
- Add an `@Injectable` decorator to anything you plan to provide or inject.
```
@Injectable()
export class DataService {
constructor(@Inject('CONFIG') public config: DataConfig) {}
}
@Injectable()
export class AppService extends DataService {...}
```
- Add a [selectorless `@Directive` decorator](guide/migration-undecorated-classes#what-does-it-mean-to-have-a-directive-decorator-with-no-metadata-inside-of-it) to any class that extends a directive or any class from which a directive inherits.
```
@Directive() // selectorless, so it's not usable directly
export class BaseMenu {
constructor(private vcr: ViewContainerRef) {}
}
@Directive({selector: '[settingsMenu]'})
export class SettingsMenu extends BaseMenu {}
```

View File

@ -21,9 +21,9 @@ If the errors are gone, switch back to Ivy by removing the changes to the `tscon
{@a common-changes} {@a common-changes}
### Changes You May See ### Changes You May See
* By default, `@ContentChildren` queries will only search direct child nodes in the DOM hierarchy (previously, they would search any nesting level in the DOM as long as another directive wasn't matched above it). ([details](guide/ivy-compatibility-examples#content-children-descendants)) * By default, `@ContentChildren` queries will only search direct child nodes in the DOM hierarchy (previously, they would search any nesting level in the DOM as long as another directive wasn't matched above it). [details](guide/ivy-compatibility-examples#content-children-descendants)
* All classes that use Angular DI must have an Angular decorator like `@Directive()` or `@Injectable` (previously, undecorated classes were allowed if an ancestor class or subclass had a decorator). * All classes that use Angular DI must have an Angular decorator like `@Directive()` or `@Injectable` (previously, undecorated classes were allowed in AOT mode only or if injection flags were used). [details](guide/ivy-compatibility-examples#undecorated-classes)
* Unbound inputs for directives (e.g. name in `<my-comp name="">`) are now set upon creation of the view, before change detection runs (previously, all inputs were set during change detection). * Unbound inputs for directives (e.g. name in `<my-comp name="">`) are now set upon creation of the view, before change detection runs (previously, all inputs were set during change detection).