mirror of
https://github.com/angular/angular
synced 2026-05-24 09:28:37 +00:00
docs: add new debugging and troubleshooting di guide
(cherry picked from commit 13e019a1bb)
This commit is contained in:
parent
c2cedd1954
commit
390efd51e7
11 changed files with 1250 additions and 13 deletions
|
|
@ -341,6 +341,12 @@ export const DOCS_SUB_NAVIGATION_DATA: NavigationItem[] = [
|
|||
path: 'guide/di/di-in-action',
|
||||
contentPath: 'guide/di/di-in-action',
|
||||
},
|
||||
{
|
||||
label: 'Debugging and troubleshooting DI',
|
||||
path: 'guide/di/debugging-and-troubleshooting-di',
|
||||
contentPath: 'guide/di/debugging-and-troubleshooting-di',
|
||||
status: 'new',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
|
|
|||
1018
adev/src/content/guide/di/debugging-and-troubleshooting-di.md
Normal file
1018
adev/src/content/guide/di/debugging-and-troubleshooting-di.md
Normal file
File diff suppressed because it is too large
Load diff
59
adev/src/content/reference/errors/NG0204.md
Normal file
59
adev/src/content/reference/errors/NG0204.md
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
# Invalid Injection Token
|
||||
|
||||
This error occurs when Angular cannot resolve a dependency for a class during dependency injection. This most commonly affects classes using constructor injection, where Angular relies on TypeScript metadata to determine parameter types.
|
||||
|
||||
The most common causes are:
|
||||
|
||||
1. A service class is missing the `@Injectable()` decorator
|
||||
2. An `InjectionToken` lacks a proper provider definition
|
||||
3. A constructor parameter cannot be resolved
|
||||
|
||||
NOTE: The `inject()` function takes an explicit token, so the "unresolvable parameter" scenario does not apply to it directly. However, if the injected class itself is missing `@Injectable()` and has its own constructor dependencies, the error can still occur.
|
||||
|
||||
## Common scenarios
|
||||
|
||||
### Missing `@Injectable()` decorator
|
||||
|
||||
When a class has constructor dependencies but lacks the `@Injectable()` decorator, Angular cannot resolve its parameters:
|
||||
|
||||
```ts {header: 'Missing @Injectable() decorator'}
|
||||
export class UserClient {
|
||||
constructor(private http: HttpClient) {} // Angular can't resolve this
|
||||
}
|
||||
```
|
||||
|
||||
Add the `@Injectable()` decorator to fix this:
|
||||
|
||||
```ts
|
||||
@Injectable({providedIn: 'root'})
|
||||
export class UserClient {
|
||||
constructor(private http: HttpClient) {}
|
||||
}
|
||||
```
|
||||
|
||||
### Unresolvable constructor parameters
|
||||
|
||||
This error also appears when Angular cannot determine the type of a constructor parameter:
|
||||
|
||||
```ts
|
||||
@Injectable({providedIn: 'root'})
|
||||
export class DataStore {
|
||||
// Angular can't resolve 'config' without a provider
|
||||
constructor(private config: AppConfig) {}
|
||||
}
|
||||
```
|
||||
|
||||
Ensure all constructor parameters either have providers configured or use `@Optional()` for optional dependencies.
|
||||
|
||||
## Debugging the error
|
||||
|
||||
The error message includes details about which token could not be resolved:
|
||||
|
||||
- `Can't resolve all parameters for X: (?, ?, ?)` — The `?` marks indicate unresolvable parameters. Check that the class has `@Injectable()` and all dependencies have providers.
|
||||
- `Token X is missing a ɵprov definition` — An `InjectionToken` was used without configuring a provider. Register the token with a value using `{provide: TOKEN, useValue: ...}` or add a default factory to the token definition.
|
||||
|
||||
Work backwards from the error's stack trace to identify where the problematic injection occurs, then verify that:
|
||||
|
||||
1. The class has `@Injectable()` decorator
|
||||
2. All constructor parameters have registered providers
|
||||
3. Any `InjectionToken` has a configured provider or default value
|
||||
76
adev/src/content/reference/errors/NG0205.md
Normal file
76
adev/src/content/reference/errors/NG0205.md
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
# Injector has already been destroyed
|
||||
|
||||
This error occurs when you attempt to retrieve a service from an injector that has already been destroyed. This typically happens when code tries to access dependencies after a component, directive, or module has been destroyed.
|
||||
|
||||
## Common scenarios
|
||||
|
||||
### Accessing services in callbacks after destruction
|
||||
|
||||
When a component is destroyed, its injector is also destroyed. If an async callback later tries to access services, this error occurs:
|
||||
|
||||
```ts
|
||||
@Component({
|
||||
/*...*/
|
||||
})
|
||||
export class UserProfile implements OnInit {
|
||||
private userClient = inject(UserClient);
|
||||
|
||||
ngOnInit() {
|
||||
setTimeout(() => {
|
||||
// ERROR: If component was destroyed before timeout fires,
|
||||
// the injector is no longer available
|
||||
this.userClient.fetchData();
|
||||
}, 5000);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Accessing services after unsubscribing
|
||||
|
||||
Similar issues occur with observables if cleanup happens in the wrong order:
|
||||
|
||||
```ts
|
||||
@Component({
|
||||
/*...*/
|
||||
})
|
||||
export class DataView implements OnDestroy {
|
||||
private dataStore = inject(DataStore);
|
||||
|
||||
ngOnDestroy() {
|
||||
// Problematic: attempting to use the injector during destruction
|
||||
// after other cleanup may have occurred
|
||||
this.dataStore.cleanup();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Debugging the error
|
||||
|
||||
To fix this error:
|
||||
|
||||
1. **Check async operations** — Ensure callbacks, promises, and subscriptions are cancelled when the component is destroyed. Use `takeUntilDestroyed()` or `DestroyRef` for cleanup.
|
||||
|
||||
2. **Capture dependencies early** — Store references to services in class fields rather than accessing the injector in callbacks.
|
||||
|
||||
3. **Guard against destroyed state** — For operations that might outlive the component, check if the component is still active before accessing services.
|
||||
|
||||
```ts
|
||||
@Component({
|
||||
/*...*/
|
||||
})
|
||||
export class UserProfile implements OnInit {
|
||||
private destroyRef = inject(DestroyRef);
|
||||
private userClient = inject(UserClient);
|
||||
|
||||
ngOnInit() {
|
||||
// Use takeUntilDestroyed to automatically cancel when destroyed
|
||||
interval(5000)
|
||||
.pipe(takeUntilDestroyed(this.destroyRef))
|
||||
.subscribe(() => {
|
||||
this.userClient.fetchData();
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The stack trace indicates where the destroyed injector was accessed. Work backwards to identify the async operation that outlived its component.
|
||||
75
adev/src/content/reference/errors/NG0207.md
Normal file
75
adev/src/content/reference/errors/NG0207.md
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
# EnvironmentProviders in wrong context
|
||||
|
||||
This error occurs when `EnvironmentProviders` are used in a context that only accepts regular providers, such as a component's `providers` array. Environment providers are designed for application-wide configuration and can only be used in environment injectors (like the root injector configured in `bootstrapApplication` or route configurations).
|
||||
|
||||
## Common scenarios
|
||||
|
||||
### Using `provideHttpClient()` in component providers
|
||||
|
||||
Functions like `provideHttpClient()` return `EnvironmentProviders`, which cannot be used at the component level:
|
||||
|
||||
```ts
|
||||
@Component({
|
||||
providers: [
|
||||
provideHttpClient(), // ERROR: EnvironmentProviders can't be used here
|
||||
],
|
||||
})
|
||||
export class UserProfile {}
|
||||
```
|
||||
|
||||
### Using `importProvidersFrom()` in component providers
|
||||
|
||||
The `importProvidersFrom()` function also returns `EnvironmentProviders`:
|
||||
|
||||
```ts
|
||||
@Component({
|
||||
providers: [
|
||||
importProvidersFrom(SomeModule), // ERROR: can't be used for component providers
|
||||
],
|
||||
})
|
||||
export class DataView {}
|
||||
```
|
||||
|
||||
## Debugging the error
|
||||
|
||||
Move the environment providers to an appropriate location:
|
||||
|
||||
### For application-wide providers
|
||||
|
||||
Configure environment providers in `bootstrapApplication`:
|
||||
|
||||
```ts
|
||||
bootstrapApplication(App, {
|
||||
providers: [provideHttpClient(), importProvidersFrom(SomeModule)],
|
||||
});
|
||||
```
|
||||
|
||||
### For route-specific providers
|
||||
|
||||
Use the `providers` array in route configurations:
|
||||
|
||||
```ts
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: 'admin',
|
||||
component: AdminView,
|
||||
providers: [provideHttpClient(withInterceptors([authInterceptor]))],
|
||||
},
|
||||
];
|
||||
```
|
||||
|
||||
### For component-level services
|
||||
|
||||
If you need component-scoped services, use regular providers instead of environment providers:
|
||||
|
||||
```ts
|
||||
@Component({
|
||||
providers: [
|
||||
UserClient, // Regular provider - this works
|
||||
{provide: API_URL, useValue: '/api'}, // Value provider - this works
|
||||
],
|
||||
})
|
||||
export class UserProfile {}
|
||||
```
|
||||
|
||||
The error message specifies which provider caused the issue. Check that all items in your component's `providers` array are regular providers, not environment providers returned by functions like `provideHttpClient()`, `provideRouter()`, or `importProvidersFrom()`.
|
||||
|
|
@ -8,6 +8,9 @@
|
|||
| `NG0200` | [Circular Dependency in DI](errors/NG0200) |
|
||||
| `NG0201` | [No Provider Found](errors/NG0201) |
|
||||
| `NG0203` | [`inject()` must be called from an injection context](errors/NG0203) |
|
||||
| `NG0204` | [Invalid Injection Token](errors/NG0204) |
|
||||
| `NG0205` | [Injector has already been destroyed](errors/NG0205) |
|
||||
| `NG0207` | [EnvironmentProviders in wrong context](errors/NG0207) |
|
||||
| `NG0209` | [Invalid multi provider](errors/NG0209) |
|
||||
| `NG0300` | [Selector Collision](errors/NG0300) |
|
||||
| `NG0301` | [Export Not Found](errors/NG0301) |
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ export const enum RuntimeErrorCode {
|
|||
// (undocumented)
|
||||
INFINITE_CHANGE_DETECTION = 103,
|
||||
// (undocumented)
|
||||
INJECTOR_ALREADY_DESTROYED = 205,
|
||||
INJECTOR_ALREADY_DESTROYED = -205,
|
||||
// (undocumented)
|
||||
INVALID_APP_ID = 211,
|
||||
// (undocumented)
|
||||
|
|
@ -90,7 +90,7 @@ export const enum RuntimeErrorCode {
|
|||
// (undocumented)
|
||||
INVALID_INHERITANCE = 903,
|
||||
// (undocumented)
|
||||
INVALID_INJECTION_TOKEN = 204,
|
||||
INVALID_INJECTION_TOKEN = -204,
|
||||
// (undocumented)
|
||||
INVALID_MULTI_PROVIDER = -209,
|
||||
// (undocumented)
|
||||
|
|
@ -152,7 +152,7 @@ export const enum RuntimeErrorCode {
|
|||
// (undocumented)
|
||||
PROVIDED_BOTH_ZONE_AND_ZONELESS = 408,
|
||||
// (undocumented)
|
||||
PROVIDER_IN_WRONG_CONTEXT = 207,
|
||||
PROVIDER_IN_WRONG_CONTEXT = -207,
|
||||
// (undocumented)
|
||||
PROVIDER_NOT_FOUND = -201,
|
||||
// (undocumented)
|
||||
|
|
|
|||
|
|
@ -37,9 +37,9 @@ export const enum RuntimeErrorCode {
|
|||
PROVIDER_NOT_FOUND = -201,
|
||||
INVALID_FACTORY_DEPENDENCY = 202,
|
||||
MISSING_INJECTION_CONTEXT = -203,
|
||||
INVALID_INJECTION_TOKEN = 204,
|
||||
INJECTOR_ALREADY_DESTROYED = 205,
|
||||
PROVIDER_IN_WRONG_CONTEXT = 207,
|
||||
INVALID_INJECTION_TOKEN = -204,
|
||||
INJECTOR_ALREADY_DESTROYED = -205,
|
||||
PROVIDER_IN_WRONG_CONTEXT = -207,
|
||||
MISSING_INJECTION_TOKEN = 208,
|
||||
INVALID_MULTI_PROVIDER = -209,
|
||||
MISSING_DOCUMENT = 210,
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ describe('DestroyRef', () => {
|
|||
|
||||
expect(() => {
|
||||
destroyRef.onDestroy(() => {});
|
||||
}).toThrowError('NG0205: Injector has already been destroyed.');
|
||||
}).toThrowError(/NG0205: Injector has already been destroyed./);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -15,10 +15,10 @@ import {
|
|||
ɵɵdefineInjector,
|
||||
ɵɵinject,
|
||||
} from '../../src/core';
|
||||
import {ERROR_DETAILS_PAGE_BASE_URL} from '../../src/error_details_base_url';
|
||||
import {createInjector} from '../../src/di/create_injector';
|
||||
import {InternalInjectFlags} from '../../src/di/interface/injector';
|
||||
import {R3Injector} from '../../src/di/r3_injector';
|
||||
import {ERROR_DETAILS_PAGE_BASE_URL} from '../../src/error_details_base_url';
|
||||
|
||||
describe('InjectorDef-based createInjector()', () => {
|
||||
class CircularA {
|
||||
|
|
@ -461,14 +461,14 @@ describe('InjectorDef-based createInjector()', () => {
|
|||
it('does not allow injection after destroy', () => {
|
||||
(injector as R3Injector).destroy();
|
||||
expect(() => injector.get(DeepService)).toThrowError(
|
||||
'NG0205: Injector has already been destroyed.',
|
||||
/NG0205: Injector has already been destroyed./,
|
||||
);
|
||||
});
|
||||
|
||||
it('does not allow double destroy', () => {
|
||||
(injector as R3Injector).destroy();
|
||||
expect(() => (injector as R3Injector).destroy()).toThrowError(
|
||||
'NG0205: Injector has already been destroyed.',
|
||||
/NG0205: Injector has already been destroyed./,
|
||||
);
|
||||
});
|
||||
|
||||
|
|
@ -506,7 +506,7 @@ describe('InjectorDef-based createInjector()', () => {
|
|||
static ɵinj = ɵɵdefineInjector({providers: [MissingArgumentType]});
|
||||
}
|
||||
expect(() => createInjector(ErrorModule).get(MissingArgumentType)).toThrowError(
|
||||
"NG0204: Can't resolve all parameters for MissingArgumentType: (?).",
|
||||
/NG0204: Can't resolve all parameters for MissingArgumentType: \(\?\)./,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -33,13 +33,13 @@ import {NgModuleType} from '../../src/render3';
|
|||
import {getNgModuleDef} from '../../src/render3/def_getters';
|
||||
import {ComponentFixture, inject, TestBed} from '../../testing';
|
||||
|
||||
import {ERROR_DETAILS_PAGE_BASE_URL} from '../../src/error_details_base_url';
|
||||
import {InternalNgModuleRef, NgModuleFactory} from '../../src/linker/ng_module_factory';
|
||||
import {
|
||||
clearModulesForTest,
|
||||
setAllowDuplicateNgModuleIdsForTest,
|
||||
} from '../../src/linker/ng_module_registration';
|
||||
import {stringify} from '../../src/util/stringify';
|
||||
import {ERROR_DETAILS_PAGE_BASE_URL} from '../../src/error_details_base_url';
|
||||
|
||||
class Engine {}
|
||||
|
||||
|
|
@ -542,7 +542,7 @@ describe('NgModule', () => {
|
|||
|
||||
it('should throw when no type and not @Inject (class case)', () => {
|
||||
expect(() => createInjector([NoAnnotations])).toThrowError(
|
||||
"NG0204: Can't resolve all parameters for NoAnnotations: (?).",
|
||||
/NG0204: Can't resolve all parameters for NoAnnotations: \(\?\)./,
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue