Angular Injectors - deependhamecha/angular GitHub Wiki
Injectors are created for NgModules automatically as part of the bootstrap process and are inherited through the component hierarchy.
Injectors are inherited, which means that if a given injector can't resolve a dependency, it asks the parent injector to resolve it. A component can get services
- from its own injector
- from the injectors of its component ancestors
- from the injector of its parent NgModule
- from the root injector.
Angular 2 offers us the following type of providers:
- Class Provider (useClass)
- FactoryProvider (useFactory)
- Aliased Class Provider (useExisting)
- Value Provider (useValue)
Angular DI has a hierarchical injection system, which means that nested injectors can create their own service instances. Angular regularly creates nested injectors. Whenever Angular creates a new instance of a component that has providers specified in @Component(), it also creates a new child injector for that instance. Similarly, when a new NgModule is lazy-loaded at run time, Angular can create an injector for it with its own providers.
A component's injector is a child of its parent component's injector, and inherits from all ancestor injectors all the way back to the application's root injector.
Note: The decorator requirement is imposed by TypeScript. TypeScript normally discards parameter type information when it transpiles the code to JavaScript. TypeScript preserves this information if the class has a decorator and the emitDecoratorMetadata compiler option is set true in TypeScript's tsconfig.json configuration file. The CLI configures tsconfig.json with emitDecoratorMetadata: true.
This means you're responsible for putting @Injectable() on your service classes.
The dependency value is an instance, and the class type serves as its own lookup key. Here you get a HeroService directly from the injector by supplying the HeroService type as the token: heroService: HeroService;
When a component or service declares a dependency, the class constructor takes that dependency as a parameter. You can tell Angular that the dependency is optional by annotating the constructor parameter with @Optional(). constructor(@Optional() private logger: Logger) {}
When using @Optional(), your code must be prepared for a null value.
##Two injector hierarchies There are two injector hierarchies in Angular:
ModuleInjector hierarchy—configure a ModuleInjector in this hierarchy using an @NgModule() or @Injectable() annotation. ElementInjector hierarchy—created implicitly at each DOM element. An ElementInjector is empty by default unless you configure it in the providers property on @Directive() or @Component().
Using the @Injectable() providedIn property is preferable to the @NgModule() providers array because with @Injectable() providedIn, optimization tools can perform tree-shaking, which removes services that your app isn't using and results in smaller bundle sizes.
VERY IMPORTANT(RECOLLECT NG-CONF): ModuleInjector is configured by the @NgModule.providers and NgModule.imports property. ModuleInjector is a flattening of all of the providers arrays which can be reached by following the NgModule.imports recursively. Child ModuleInjectors are created when lazy loading other @NgModules.
There are two more injectors above root, an additional ModuleInjector and NullInjector().
Consider how Angular bootstraps the app with the following in main.ts:
platformBrowserDynamic().bootstrapModule(AppModule).then(ref => {...}) The bootstrapModule() method creates a child injector of the platform injector which is configured by the AppModule. This is the root ModuleInjector.
The platformBrowserDynamic() method creates an injector configured by a PlatformModule, which contains platform-specific dependencies. This allows multiple apps to share a platform configuration. For example, a browser has only one URL bar, no matter how many apps you have running. You can configure additional platform-specific providers at the platform level by supplying extraProviders using the platformBrowser() function.
The next parent injector in the hierarchy is the NullInjector(), which is the top of the tree. If you've gone so far up the tree that you are looking for a service in the NullInjector(), you'll get an error unless you've used @Optional() because ultimately, everything ends at the NullInjector() and it returns an error or, in the case of @Optional(), null. For more information on @Optional(), see the @Optional() section of this guide.
The following diagram represents the relationship between the root ModuleInjector and its parent injectors as the previous paragraphs describe.
If you configure an app-wide provider in the @NgModule() of AppModule, it overrides one configured for root in the @Injectable() metadata.
providers: [
{
provide: RootLoggerService,
useClass: RootLoggerService
},
{
provide: SuperLoggerService, // SuperClass
useClass: RootLoggerService // SubClass
}
]
By default providers: [RootLoggerService]
is written as {provide: RootLoggerService, useClass: RootLoggerService}
.
root-logger.service.ts
@Injectable()
export class RootLoggerService {
count: number = 0;
constructor() { }
}
@Injectable()
export class SuperLoggerService {
count: number = 0;
}
export class Dude {
name: string;
age: number;
}
app.component.ts
export class AppComponent {
title = 'ng-try';
constructor(private rootLoggerService1: RootLoggerService, private rootLoggerService2: SuperLoggerService, private dude: Dude) {
this.rootLoggerService1.count++;
console.log("1: ", this.rootLoggerService1.count);
this.rootLoggerService2.count++;
console.log("2: ", this.rootLoggerService2.count);
}
}
app.component.html
<h1>rootLoggerService1: {{rootLoggerService1?.count}}</h1>
<h1>rootLoggerService2: {{rootLoggerService2?.count}}</h1>
<h1>Dude: {{dude.name}}</h1>
Output
rootLoggerService1: 1
rootLoggerService2: 1
Dude: Deepen
- As
useClass
creates alias in this scenario or if you check in angular documentation. In simple terms, you provide a token(Class, not an interface) in provide and useClass, a token(Class, not an interface). - Angular will create an instance of class mentioned in useClass. Remember, Class, as typescript cannot create instance of an interface.
- It will create two different instances.
- Now, coming to useExisting, if you provide this in configuration to providers:
providers: [
{
provide: RootLoggerService,
useClass: RootLoggerService
},
{
provide: SuperLoggerService, // SuperClass
useExisting: RootLoggerService // SubClass
}
]
Then it will create only one instance first and second you would reused, so your output will be following: Output
rootLoggerService1: 2
rootLoggerService2: 2
Dude: Deepen
{
provide: Dude, // You cannot use interface as type here
useValue: {name: 'Deepen', age: 28}
}
useValue needs a value rather than a class, as this will use the value and angular will not create an instance.