From 3414760bdd7d0c06a2abfc5a1ff00de114b23e52 Mon Sep 17 00:00:00 2001 From: Paul Gschwendtner Date: Thu, 21 Nov 2019 21:26:13 +0100 Subject: [PATCH] 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 --- aio/content/guide/migration-injectable.md | 129 +++++++++++++--------- 1 file changed, 77 insertions(+), 52 deletions(-) diff --git a/aio/content/guide/migration-injectable.md b/aio/content/guide/migration-injectable.md index 25932d83b2..13ea0a3c88 100644 --- a/aio/content/guide/migration-injectable.md +++ b/aio/content/guide/migration-injectable.md @@ -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 \ No newline at end of file