docs: update injectable migration guide to reflect latest schematic changes (#33960)

Since we created the migration guide for the `missing-injectable` schematic, the schematic
changed in various ways. e.g. the migration no longer migrates classes passed to `useExisting`

Additionally the migration has been expanded to handle another Ivy breaking change where
providers like `{provide: X}` will be intepreted as `{provide: X, useClass: X}`. This pattern should
be documented in the migration guide.

PR Close #33960
This commit is contained in:
Paul Gschwendtner 2019-11-21 21:26:13 +01:00 committed by Matias Niemelä
parent 48a1acab52
commit 3414760bdd
1 changed files with 77 additions and 52 deletions

View File

@ -1,64 +1,65 @@
# Migration to Add Missing `@Injectable()` Decorators
## What does this schematic do?
### What does this schematic do?
This schematic adds an `@Injectable()` decorator to a class
if the class has been added as a provider anywhere in the application.
1. This schematic adds an `@Injectable()` decorator to classes which are provided in the
application but are not decorated.
2. The schematic updates providers which follow the `{provide: SomeToken}` pattern
to explicitly specify `useValue: undefined`.
**Example for missing `@Injectable()`**
An example diff might look like the following:
_Before migration:_
```typescript
export class MyService {...}
export class MyOtherService {...}
export class MyThirdClass {...}
export class MyFourthClass {...}
export class MyFifthClass {...}
**Before:**
```ts
export class TypeCase {...}
@NgModule({
providers: [
MyService,
{provide: SOME_TOKEN, useClass: MyOtherService},
// The following classes do not need to be decorated because they
// are never instantiated and just serve as DI tokens.
{provide: MyThirdClass, useValue: ...},
{provide: MyFourthClass, useFactory: ...},
{provide: MyFifthClass, useExisting: ...},
]
})
```
**After:**
_After migration:_
```ts
@Injectable()
export class TypeCase {...}
export class MyService {...}
@Injectable()
export class MyOtherService {...}
export class MyThirdClass {...}
export class MyFourthClass {...}
export class MySixthClass {...}
...
```
Note that `MyThirdClass`, `MyFourthClass` and `MyFifthClass` do not need to be decorated
with `@Injectable()` because they are never instantiated, but just used as a [DI token][DI_TOKEN].
There are a few cases where the decorator won't be added. For example:
**Example for provider needing `useValue: undefined`**
- It already has another decorator such as `@Component()`, `@Directive()` or `@Pipe()`. These decorators already cause the compiler to generate the necessary information.
- The provider definition has `useValue`, `useFactory`, or `useExisting`. In
these cases, the framework doesn't need the `@Injectable()` decorator to create the class
because it can just use the value,
factory function, or existing instance that was provided.
This example shows a provider following the `{provide: X}` pattern. The provider needs to be
migrated to a more explicit definition where `useValue: undefined` is specified.
For example, for the following module definition, the schematic will check
`TypeCase`, `ProvideCase`, `ExistingClass`, and `SomeClass` to ensure they
are marked with the `@Injectable()` decorator and add one if not.
_Before migration_:
```typescript
{provide: MyToken}
```
_After migration_:
```typescript
{provide: MyToken, useValue: undefined}
```
```ts
@NgModule({
providers: [
// TypeCase needs @Injectable()
TypeCase,
// ProvideCase needs @Injectable()
{provide: ProvideCase},
// No @Injectable() needed because the value will be used
{provide: ValueCase, useValue: 0},
// No @Injectable() needed because factory will be used
{provide: FactoryCase, useFactory: ()=> null},
// ExistingClass needs @Injectable()
{provide: ExistingToken, useExisting: ExistingClass},
// SomeClass needs @Injectable()
{provide: ClassToken, useClass: SomeClass},
// No @Injectable() needed because it has a @Pipe() decorator
PipeCase,
]
})
```
## Why is this migration necessary?
### Why is adding `@Injectable()` necessary?
In our docs, we've always recommended adding `@Injectable()`
decorators to any class that is provided or injected in your application.
@ -71,15 +72,39 @@ to strictly require the decorator because doing so enables further
optimization of both the compiler and the runtime. This schematic
adds any `@Injectable()` decorators that may be missing to future-proof your app.
### Why is adding `useValue: undefined` necessary?
Consider the following pattern:
## When should I be adding `@Injectable()` decorators to classes?
```typescript
@NgModule({
providers: [{provide: MyService}]
})
```
Any class that is provided or injected somewhere must have an `@Injectable()` decorator. The decorator is necessary for the framework to properly create an instance of that class through DI.
Providers using this pattern will behave as if they provide `MyService` as [DI token][DI_TOKEN]
with the value of `undefined`. This is not the case in Ivy where such providers will be interpreted
as if `useClass: MyService` is specified. This means that these providers will behave differently
when updating to version 9 and above. To ensure that the provider behaves the same as before, the
DI value should be explicitly set to `undefined`.
However, as noted above, classes that already have another class decorator like `@Pipe` do not need both decorators. The existing class decorator will cause the compiler to generate the proper information.
### When should I be adding `@Injectable()` decorators to classes?
Any class that is provided must have an `@Injectable()` decorator. The decorator is necessary
for the framework to properly create an instance of that class through DI.
## Should I update my library?
However, classes which are already decorated with `@Pipe`, `@Component` or `@Directive` do not
need both decorators. The existing class decorator already instructs the compiler to generate the
needed information.
Yes, if your library has any tokens that are meant to be injected, they should be updated with the `@Injectable()` decorator. In a future version of Angular, a missing `@Injectable()` decorator will always throw an error.
### Should I update my library?
Yes, if your library has any classes that are meant to be injected, they should be updated with
the `@Injectable()` decorator. In a future version of Angular, a missing `@Injectable()` decorator
will always throw an error.
Additionally, providers in your library that follow the described `{provide: X}` pattern should be
updated to specify an explicit value. Without explicit value, these providers can behave
differently based on the Angular version in applications consuming your library.
[DI_TOKEN]: guide/glossary#di-token