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:
parent
48a1acab52
commit
3414760bdd
|
@ -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`.
|
||||
|
||||
An example diff might look like the following:
|
||||
**Example for missing `@Injectable()`**
|
||||
|
||||
**Before:**
|
||||
_Before migration:_
|
||||
```typescript
|
||||
export class MyService {...}
|
||||
export class MyOtherService {...}
|
||||
export class MyThirdClass {...}
|
||||
export class MyFourthClass {...}
|
||||
export class MyFifthClass {...}
|
||||
|
||||
```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
|
Loading…
Reference in New Issue